mirror of
https://github.com/moodle/moodle.git
synced 2025-08-05 08:56:36 +02:00
Merge branch 'MDL-70817-311-8' of git://github.com/junpataleta/moodle into MOODLE_311_STABLE
This commit is contained in:
commit
4025e4d726
34 changed files with 1535 additions and 89 deletions
|
@ -139,6 +139,13 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
|||
new lang_string('coursehelpshowgrades'), 1, array(0 => new lang_string('no'), 1 => new lang_string('yes'))));
|
||||
$temp->add(new admin_setting_configselect('moodlecourse/showreports', new lang_string('showreports'), '', 0,
|
||||
array(0 => new lang_string('no'), 1 => new lang_string('yes'))));
|
||||
$temp->add(new admin_setting_configselect('moodlecourse/showactivitydates',
|
||||
new lang_string('showactivitydates'),
|
||||
new lang_string('showactivitydates_help'), 1, [
|
||||
0 => new lang_string('no'),
|
||||
1 => new lang_string('yes')
|
||||
]
|
||||
));
|
||||
|
||||
// Files and uploads.
|
||||
$temp->add(new admin_setting_heading('filesanduploadshdr', new lang_string('filesanduploads'), ''));
|
||||
|
@ -163,6 +170,15 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
|||
$temp->add(new admin_setting_configselect('moodlecourse/enablecompletion', new lang_string('completion', 'completion'),
|
||||
new lang_string('enablecompletion_help', 'completion'), 1, array(0 => new lang_string('no'), 1 => new lang_string('yes'))));
|
||||
|
||||
// Display completion conditions.
|
||||
$temp->add(new admin_setting_configselect('moodlecourse/showcompletionconditions',
|
||||
new lang_string('showcompletionconditions', 'completion'),
|
||||
new lang_string('showcompletionconditions_help', 'completion'), 1, [
|
||||
0 => new lang_string('no'),
|
||||
1 => new lang_string('yes')
|
||||
]
|
||||
));
|
||||
|
||||
// Groups.
|
||||
$temp->add(new admin_setting_heading('groups', new lang_string('groups', 'group'), ''));
|
||||
$choices = array();
|
||||
|
|
198
completion/classes/cm_completion_details.php
Normal file
198
completion/classes/cm_completion_details.php
Normal file
|
@ -0,0 +1,198 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the class for building the user's activity completion details.
|
||||
*
|
||||
* @package core_completion
|
||||
* @copyright Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace core_completion;
|
||||
|
||||
use cm_info;
|
||||
use completion_info;
|
||||
|
||||
/**
|
||||
* Class for building the user's activity completion details.
|
||||
*
|
||||
* @package core_completion
|
||||
* @copyright Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class cm_completion_details {
|
||||
/** @var completion_info The completion info instance for this cm's course. */
|
||||
protected $completioninfo = null;
|
||||
|
||||
/** @var cm_info The course module information. */
|
||||
protected $cminfo = null;
|
||||
|
||||
/** @var int The user ID. */
|
||||
protected $userid = 0;
|
||||
|
||||
/** @var bool Whether to return automatic completion details. */
|
||||
protected $returndetails = true;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param completion_info $completioninfo The completion info instance for this cm's course.
|
||||
* @param cm_info $cminfo The course module information.
|
||||
* @param int $userid The user ID.
|
||||
* @param bool $returndetails Whether to return completion details or not.
|
||||
*/
|
||||
public function __construct(completion_info $completioninfo, cm_info $cminfo, int $userid, bool $returndetails = true) {
|
||||
$this->completioninfo = $completioninfo;
|
||||
$this->cminfo = $cminfo;
|
||||
$this->userid = $userid;
|
||||
$this->returndetails = $returndetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the completion details for a user.
|
||||
*
|
||||
* @return array An array of completion details for a user containing the completion requirement's description and status.
|
||||
*/
|
||||
public function get_details(): array {
|
||||
if (!$this->is_automatic()) {
|
||||
// No details need to be returned for modules that don't have automatic completion tracking enabled.
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!$this->returndetails) {
|
||||
// We don't need to return completion details.
|
||||
return [];
|
||||
}
|
||||
|
||||
$completiondata = $this->completioninfo->get_data($this->cminfo, false, $this->userid);
|
||||
$hasoverride = !empty($this->overridden_by());
|
||||
|
||||
$details = [];
|
||||
|
||||
// Completion rule: Student must view this activity.
|
||||
if (!empty($this->cminfo->completionview)) {
|
||||
if (!$hasoverride) {
|
||||
$status = COMPLETION_INCOMPLETE;
|
||||
if ($completiondata->viewed == COMPLETION_VIEWED) {
|
||||
$status = COMPLETION_COMPLETE;
|
||||
}
|
||||
} else {
|
||||
$status = $completiondata->completionstate;
|
||||
}
|
||||
|
||||
$details['completionview'] = (object)[
|
||||
'status' => $status,
|
||||
'description' => get_string('detail_desc:view', 'completion'),
|
||||
];
|
||||
}
|
||||
|
||||
// Completion rule: Student must receive a grade.
|
||||
if (!is_null($this->cminfo->completiongradeitemnumber)) {
|
||||
if (!$hasoverride) {
|
||||
$status = $completiondata->completiongrade ?? COMPLETION_INCOMPLETE;
|
||||
} else {
|
||||
$status = $completiondata->completionstate;
|
||||
}
|
||||
|
||||
$details['completionusegrade'] = (object)[
|
||||
'status' => $status,
|
||||
'description' => get_string('detail_desc:receivegrade', 'completion'),
|
||||
];
|
||||
}
|
||||
|
||||
// Custom completion rules.
|
||||
$cmcompletionclass = activity_custom_completion::get_cm_completion_class($this->cminfo->modname);
|
||||
if (!isset($completiondata->customcompletion) || !$cmcompletionclass) {
|
||||
// Return early if there are no custom rules to process or the cm completion class implementation is not available.
|
||||
return $details;
|
||||
}
|
||||
|
||||
/** @var activity_custom_completion $cmcompletion */
|
||||
$cmcompletion = new $cmcompletionclass($this->cminfo, $this->userid);
|
||||
foreach ($completiondata->customcompletion as $rule => $status) {
|
||||
$details[$rule] = (object)[
|
||||
'status' => !$hasoverride ? $status : $completiondata->completionstate,
|
||||
'description' => $cmcompletion->get_custom_rule_description($rule),
|
||||
];
|
||||
}
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the overall completion state of this course module.
|
||||
*
|
||||
* @return int The overall completion state for this course module.
|
||||
*/
|
||||
public function get_overall_completion(): int {
|
||||
$completiondata = $this->completioninfo->get_data($this->cminfo, false, $this->userid);
|
||||
return (int)$completiondata->completionstate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this activity module has completion enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_completion(): bool {
|
||||
return $this->completioninfo->is_enabled($this->cminfo) != COMPLETION_DISABLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this activity module instance tracks completion automatically.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_automatic(): bool {
|
||||
return $this->cminfo->completion == COMPLETION_TRACKING_AUTOMATIC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the user ID that has overridden the completion state of this activity for the user.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function overridden_by(): ?int {
|
||||
$completiondata = $this->completioninfo->get_data($this->cminfo);
|
||||
return isset($completiondata->overrideby) ? (int)$completiondata->overrideby : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether completion is being tracked for this user.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_tracked_user(): bool {
|
||||
return $this->completioninfo->is_tracked_user($this->userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an instance of this class.
|
||||
*
|
||||
* @param cm_info $cminfo The course module info instance.
|
||||
* @param int $userid The user ID that we're fetching completion details for.
|
||||
* @param bool $returndetails Whether to return completion details or not.
|
||||
* @return cm_completion_details
|
||||
*/
|
||||
public static function get_instance(cm_info $cminfo, int $userid, bool $returndetails = true): cm_completion_details {
|
||||
$course = $cminfo->get_course();
|
||||
$completioninfo = new completion_info($course);
|
||||
return new self($completioninfo, $cminfo, $userid, $returndetails);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
@core @core_completion
|
||||
Feature: Allow teachers to edit the visibility of completion conditions in a course
|
||||
In order to show students the course completion conditions in a course
|
||||
As a teacher
|
||||
I need to be able to edit completion conditions settings
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | enablecompletion | showcompletionconditions |
|
||||
| Course 1 | C1 | 1 | 1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following "activities" exist:
|
||||
| activity | course | idnumber | name | completion | completionsubmit |
|
||||
| choice | C1 | c1m | Test choice manual| 1 | 0 |
|
||||
| choice | C1 | c1a | Test choice auto | 2 | 1 |
|
||||
|
||||
Scenario: Completion condition displaying for manual and auto completion
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test choice manual"
|
||||
And I should see "Mark as done"
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "Test choice auto"
|
||||
Then I should see "Make a choice" in the "[data-region=completionrequirements]" "css_element"
|
||||
# TODO MDL-70821: Check completion conditions display on course homepage.
|
||||
|
||||
Scenario: Completion condition displaying setting can be disabled at course level
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
When I set the following fields to these values:
|
||||
| Show completion conditions | No |
|
||||
And I click on "Save and display" "button"
|
||||
And I follow "Test choice auto"
|
||||
# Completion conditions are always shown in the module's view page.
|
||||
Then I should see "Make a choice" in the "[data-region=completionrequirements]" "css_element"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test choice manual"
|
||||
And I should see "Mark as done"
|
||||
|
||||
Scenario: Default show completion conditions value in course form when default show completion conditions admin setting is set to No
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Courses > Course default settings" in site administration
|
||||
When I set the following fields to these values:
|
||||
| Show completion conditions | No |
|
||||
And I click on "Save changes" "button"
|
||||
And I navigate to "Courses > Add a new course" in site administration
|
||||
Then the field "showcompletionconditions" matches value "No"
|
||||
|
||||
Scenario: Default show completion conditions value in course form when default show completion conditions admin setting is set to Yes
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Courses > Course default settings" in site administration
|
||||
When I set the following fields to these values:
|
||||
| Show completion conditions | Yes |
|
||||
And I click on "Save changes" "button"
|
||||
And I navigate to "Courses > Add a new course" in site administration
|
||||
Then the field "showcompletionconditions" matches value "Yes"
|
285
completion/tests/cm_completion_details_test.php
Normal file
285
completion/tests/cm_completion_details_test.php
Normal file
|
@ -0,0 +1,285 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains unit tests for core_completion/cm_completion_details.
|
||||
*
|
||||
* @package core_completion
|
||||
* @copyright Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace core_completion;
|
||||
|
||||
use advanced_testcase;
|
||||
use cm_info;
|
||||
use completion_info;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/completionlib.php');
|
||||
|
||||
/**
|
||||
* Class for unit testing core_completion/cm_completion_details.
|
||||
*
|
||||
* @package core_completion
|
||||
* @copyright Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class cm_completion_details_test extends advanced_testcase {
|
||||
|
||||
/** @var completion_info A completion object. */
|
||||
protected $completioninfo = null;
|
||||
|
||||
/**
|
||||
* Fetches a mocked cm_completion_details instance.
|
||||
*
|
||||
* @param int|null $completion The completion tracking mode for the module.
|
||||
* @param array $completionoptions Completion options (e.g. completionview, completionusegrade, etc.)
|
||||
* @return cm_completion_details
|
||||
*/
|
||||
protected function setup_data(?int $completion, array $completionoptions = []): cm_completion_details {
|
||||
if (is_null($completion)) {
|
||||
$completion = COMPLETION_TRACKING_AUTOMATIC;
|
||||
}
|
||||
|
||||
// Mock a completion_info instance so we can simply mock the returns of completion_info::get_data() later.
|
||||
$this->completioninfo = $this->getMockBuilder(completion_info::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
// Mock return of completion_info's is_enabled() method to match the expected completion tracking for the module.
|
||||
$this->completioninfo->expects($this->any())
|
||||
->method('is_enabled')
|
||||
->willReturn($completion);
|
||||
|
||||
// Build a mock cm_info instance.
|
||||
$mockcminfo = $this->getMockBuilder(cm_info::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['__get'])
|
||||
->getMock();
|
||||
|
||||
// Mock the return of the magic getter method when fetching the cm_info object's customdata and instance values.
|
||||
$mockcminfo->expects($this->any())
|
||||
->method('__get')
|
||||
->will($this->returnValueMap([
|
||||
['completion', $completion],
|
||||
['instance', 1],
|
||||
['modname', 'somenonexistentmod'],
|
||||
['completionview', $completionoptions['completionview'] ?? COMPLETION_VIEW_NOT_REQUIRED],
|
||||
['completiongradeitemnumber', $completionoptions['completionusegrade'] ?? null],
|
||||
]));
|
||||
|
||||
return new cm_completion_details($this->completioninfo, $mockcminfo, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for test_has_completion().
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function has_completion_provider(): array {
|
||||
return [
|
||||
'Automatic' => [
|
||||
COMPLETION_TRACKING_AUTOMATIC, true
|
||||
],
|
||||
'Manual' => [
|
||||
COMPLETION_TRACKING_MANUAL, true
|
||||
],
|
||||
'None' => [
|
||||
COMPLETION_TRACKING_NONE, false
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for has_completion().
|
||||
*
|
||||
* @dataProvider has_completion_provider
|
||||
* @param int $completion The completion tracking mode.
|
||||
* @param bool $expectedresult Expected result.
|
||||
*/
|
||||
public function test_has_completion(int $completion, bool $expectedresult) {
|
||||
$cmcompletion = $this->setup_data($completion);
|
||||
|
||||
$this->assertEquals($expectedresult, $cmcompletion->has_completion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for test_is_automatic().
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function is_automatic_provider(): array {
|
||||
return [
|
||||
'Automatic' => [
|
||||
COMPLETION_TRACKING_AUTOMATIC, true
|
||||
],
|
||||
'Manual' => [
|
||||
COMPLETION_TRACKING_MANUAL, false
|
||||
],
|
||||
'None' => [
|
||||
COMPLETION_TRACKING_NONE, false
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for is_available().
|
||||
*
|
||||
* @dataProvider is_automatic_provider
|
||||
* @param int $completion The completion tracking mode.
|
||||
* @param bool $expectedresult Expected result.
|
||||
*/
|
||||
public function test_is_automatic(int $completion, bool $expectedresult) {
|
||||
$cmcompletion = $this->setup_data($completion);
|
||||
|
||||
$this->assertEquals($expectedresult, $cmcompletion->is_automatic());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_get_overall_completion().
|
||||
* @return array[]
|
||||
*/
|
||||
public function overall_completion_provider(): array {
|
||||
return [
|
||||
'Complete' => [COMPLETION_COMPLETE],
|
||||
'Incomplete' => [COMPLETION_INCOMPLETE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for get_overall_completion().
|
||||
*
|
||||
* @dataProvider overall_completion_provider
|
||||
* @param int $state
|
||||
*/
|
||||
public function test_get_overall_completion(int $state) {
|
||||
$cmcompletion = $this->setup_data(COMPLETION_TRACKING_AUTOMATIC);
|
||||
|
||||
$this->completioninfo->expects($this->once())
|
||||
->method('get_data')
|
||||
->willReturn((object)['completionstate' => $state]);
|
||||
|
||||
$this->assertEquals($state, $cmcompletion->get_overall_completion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_get_details().
|
||||
* @return array[]
|
||||
*/
|
||||
public function get_details_provider() {
|
||||
return [
|
||||
'No completion tracking' => [
|
||||
COMPLETION_TRACKING_NONE, null, null, []
|
||||
],
|
||||
'Manual completion tracking' => [
|
||||
COMPLETION_TRACKING_MANUAL, null, null, []
|
||||
],
|
||||
'Automatic, require view, not viewed' => [
|
||||
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, null, [
|
||||
'completionview' => (object)[
|
||||
'status' => COMPLETION_INCOMPLETE,
|
||||
'description' => get_string('detail_desc:view', 'completion'),
|
||||
]
|
||||
]
|
||||
],
|
||||
'Automatic, require view, viewed' => [
|
||||
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, null, [
|
||||
'completionview' => (object)[
|
||||
'status' => COMPLETION_COMPLETE,
|
||||
'description' => get_string('detail_desc:view', 'completion'),
|
||||
]
|
||||
]
|
||||
],
|
||||
'Automatic, require grade, incomplete' => [
|
||||
COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_INCOMPLETE, [
|
||||
'completionusegrade' => (object)[
|
||||
'status' => COMPLETION_INCOMPLETE,
|
||||
'description' => get_string('detail_desc:receivegrade', 'completion'),
|
||||
]
|
||||
]
|
||||
],
|
||||
'Automatic, require grade, complete' => [
|
||||
COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_COMPLETE, [
|
||||
'completionusegrade' => (object)[
|
||||
'status' => COMPLETION_COMPLETE,
|
||||
'description' => get_string('detail_desc:receivegrade', 'completion'),
|
||||
]
|
||||
]
|
||||
],
|
||||
'Automatic, require view (complete) and grade (incomplete)' => [
|
||||
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, [
|
||||
'completionview' => (object)[
|
||||
'status' => COMPLETION_COMPLETE,
|
||||
'description' => get_string('detail_desc:view', 'completion'),
|
||||
],
|
||||
'completionusegrade' => (object)[
|
||||
'status' => COMPLETION_INCOMPLETE,
|
||||
'description' => get_string('detail_desc:receivegrade', 'completion'),
|
||||
]
|
||||
]
|
||||
],
|
||||
'Automatic, require view (incomplete) and grade (complete)' => [
|
||||
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, COMPLETION_COMPLETE, [
|
||||
'completionview' => (object)[
|
||||
'status' => COMPLETION_INCOMPLETE,
|
||||
'description' => get_string('detail_desc:view', 'completion'),
|
||||
],
|
||||
'completionusegrade' => (object)[
|
||||
'status' => COMPLETION_COMPLETE,
|
||||
'description' => get_string('detail_desc:receivegrade', 'completion'),
|
||||
]
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for \core_completion\cm_completion_details::get_details().
|
||||
*
|
||||
* @dataProvider get_details_provider
|
||||
* @param int $completion The completion tracking mode.
|
||||
* @param int|null $completionview Completion status of the "view" completion condition.
|
||||
* @param int|null $completiongrade Completion status of the "must receive grade" completion condition.
|
||||
* @param array $expecteddetails Expected completion details returned by get_details().
|
||||
*/
|
||||
public function test_get_details(int $completion, ?int $completionview, ?int $completiongrade, array $expecteddetails) {
|
||||
$options = [];
|
||||
$getdatareturn = (object)[
|
||||
'viewed' => $completionview,
|
||||
'completiongrade' => $completiongrade,
|
||||
];
|
||||
|
||||
if (!is_null($completionview)) {
|
||||
$options['completionview'] = true;
|
||||
}
|
||||
if (!is_null($completiongrade)) {
|
||||
$options['completionusegrade'] = true;
|
||||
}
|
||||
|
||||
$cmcompletion = $this->setup_data($completion, $options);
|
||||
|
||||
$this->completioninfo->expects($this->any())
|
||||
->method('get_data')
|
||||
->willReturn($getdatareturn);
|
||||
|
||||
$this->assertEquals($expecteddetails, $cmcompletion->get_details());
|
||||
}
|
||||
}
|
2
course/amd/build/manual_completion_toggle.min.js
vendored
Normal file
2
course/amd/build/manual_completion_toggle.min.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
define ("core_course/manual_completion_toggle",["exports","core/templates","core/notification","core_course/repository"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=e(b);c=e(c);function e(a){return a&&a.__esModule?a:{default:a}}function f(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function g(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function g(a){f(i,d,e,g,h,"next",a)}function h(a){f(i,d,e,g,h,"throw",a)}g(void 0)})}}var h={MANUAL_TOGGLE:"button[data-action=toggle-manual-completion]"},i={TOGGLE_MARK_DONE:"manual:mark-done",TOGGLE_UNDO:"manual:undo"},j=!1,k=function(){if(j){return}document.addEventListener("click",function(a){var b=a.target.closest(h.MANUAL_TOGGLE);if(b){a.preventDefault();l(b).catch(c.default.exception)}});j=!0};a.init=k;var l=function(){var a=g(regeneratorRuntime.mark(function a(e){var f,g,h,j,k,l,m,n;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:f=e.innerHTML;e.setAttribute("disabled","disabled");g=e.getAttribute("data-toggletype");h=e.getAttribute("data-cmid");j=e.getAttribute("data-activityname");k=g===i.TOGGLE_MARK_DONE;a.next=8;return b.default.render("core/loading",{});case 8:l=a.sent;a.next=11;return b.default.replaceNodeContents(e,l,"");case 11:a.prev=11;a.next=14;return(0,d.toggleManualCompletion)(h,k);case 14:m={cmid:h,activityname:j,overallcomplete:k,overallincomplete:!k,istrackeduser:!0};a.next=17;return b.default.renderForPromise("core_course/completion_manual",m);case 17:n=a.sent;a.next=20;return b.default.replaceNode(e,n.html,n.js);case 20:a.next=27;break;case 22:a.prev=22;a.t0=a["catch"](11);e.removeAttribute("disabled");e.innerHTML=f;c.default.exception(a.t0);case 27:case"end":return a.stop();}}},a,null,[[11,22]])}));return function(){return a.apply(this,arguments)}}()});
|
||||
//# sourceMappingURL=manual_completion_toggle.min.js.map
|
1
course/amd/build/manual_completion_toggle.min.js.map
Normal file
1
course/amd/build/manual_completion_toggle.min.js.map
Normal file
File diff suppressed because one or more lines are too long
2
course/amd/build/repository.min.js
vendored
2
course/amd/build/repository.min.js
vendored
|
@ -1,2 +1,2 @@
|
|||
define ("core_course/repository",["jquery","core/ajax"],function(a,b){return{getEnrolledCoursesByTimelineClassification:function getEnrolledCoursesByTimelineClassification(a,c,d,e){var f={classification:a};if("undefined"!=typeof c){f.limit=c}if("undefined"!=typeof d){f.offset=d}if("undefined"!=typeof e){f.sort=e}return b.call([{methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:f}])[0]},getLastAccessedCourses:function getLastAccessedCourses(a,c,d,e){var f={};if("undefined"!=typeof a){f.userid=a}if("undefined"!=typeof c){f.limit=c}if("undefined"!=typeof d){f.offset=d}if("undefined"!=typeof e){f.sort=e}return b.call([{methodname:"core_course_get_recent_courses",args:f}])[0]},getUsersFromCourseModuleID:function getEnrolledUsersFromCourseModuleID(a,c){return b.call([{methodname:"core_course_get_enrolled_users_by_cmid",args:{cmid:a,groupid:c}}])[0]}}});
|
||||
define ("core_course/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a,c,d,e){var f={classification:a};if("undefined"!=typeof c){f.limit=c}if("undefined"!=typeof d){f.offset=d}if("undefined"!=typeof e){f.sort=e}return b.default.call([{methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:f}])[0]},d=function(a,c,d,e){var f={};if("undefined"!=typeof a){f.userid=a}if("undefined"!=typeof c){f.limit=c}if("undefined"!=typeof d){f.offset=d}if("undefined"!=typeof e){f.sort=e}return b.default.call([{methodname:"core_course_get_recent_courses",args:f}])[0]},e=function(a,c){return b.default.call([{methodname:"core_course_get_enrolled_users_by_cmid",args:{cmid:a,groupid:c}}])[0]},f=function(a,c){return b.default.call([{methodname:"core_completion_update_activity_completion_status_manually",args:{cmid:a,completed:c}}])[0]};a.default={getEnrolledCoursesByTimelineClassification:c,getLastAccessedCourses:d,getUsersFromCourseModuleID:e,toggleManualCompletion:f};return a.default});
|
||||
//# sourceMappingURL=repository.min.js.map
|
||||
|
|
File diff suppressed because one or more lines are too long
122
course/amd/src/manual_completion_toggle.js
Normal file
122
course/amd/src/manual_completion_toggle.js
Normal file
|
@ -0,0 +1,122 @@
|
|||
// 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/>.
|
||||
|
||||
/**
|
||||
* Provides the functionality for toggling the manual completion state of a course module through
|
||||
* the manual completion button.
|
||||
*
|
||||
* @module core_course/manual_completion_toggle
|
||||
* @package core_course
|
||||
* @copyright 2021 Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import Templates from 'core/templates';
|
||||
import Notification from 'core/notification';
|
||||
import {toggleManualCompletion} from 'core_course/repository';
|
||||
|
||||
/**
|
||||
* Selectors in the manual completion template.
|
||||
*
|
||||
* @type {{MANUAL_TOGGLE: string}}
|
||||
*/
|
||||
const SELECTORS = {
|
||||
MANUAL_TOGGLE: 'button[data-action=toggle-manual-completion]',
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle type values for the data-toggletype attribute in the core_course/completion_manual template.
|
||||
*
|
||||
* @type {{TOGGLE_UNDO: string, TOGGLE_MARK_DONE: string}}
|
||||
*/
|
||||
const TOGGLE_TYPES = {
|
||||
TOGGLE_MARK_DONE: 'manual:mark-done',
|
||||
TOGGLE_UNDO: 'manual:undo',
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the event listener has already been registered for this module.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
let registered = false;
|
||||
|
||||
/**
|
||||
* Registers the click event listener for the manual completion toggle button.
|
||||
*/
|
||||
export const init = () => {
|
||||
if (registered) {
|
||||
return;
|
||||
}
|
||||
document.addEventListener('click', (e) => {
|
||||
const toggleButton = e.target.closest(SELECTORS.MANUAL_TOGGLE);
|
||||
if (toggleButton) {
|
||||
e.preventDefault();
|
||||
toggleManualCompletionState(toggleButton).catch(Notification.exception);
|
||||
}
|
||||
});
|
||||
registered = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles the manual completion state of the module for the given user.
|
||||
*
|
||||
* @param {HTMLElement} toggleButton
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const toggleManualCompletionState = async(toggleButton) => {
|
||||
// Make a copy of the original content of the button.
|
||||
const originalInnerHtml = toggleButton.innerHTML;
|
||||
|
||||
// Disable the button to prevent double clicks.
|
||||
toggleButton.setAttribute('disabled', 'disabled');
|
||||
|
||||
// Get button data.
|
||||
const toggleType = toggleButton.getAttribute('data-toggletype');
|
||||
const cmid = toggleButton.getAttribute('data-cmid');
|
||||
const activityname = toggleButton.getAttribute('data-activityname');
|
||||
// Get the target completion state.
|
||||
const completed = toggleType === TOGGLE_TYPES.TOGGLE_MARK_DONE;
|
||||
|
||||
// Replace the button contents with the loading icon.
|
||||
const loadingHtml = await Templates.render('core/loading', {});
|
||||
await Templates.replaceNodeContents(toggleButton, loadingHtml, '');
|
||||
|
||||
try {
|
||||
// Call the webservice to update the manual completion status.
|
||||
await toggleManualCompletion(cmid, completed);
|
||||
|
||||
// All good so far. Refresh the manual completion button to reflect its new state by re-rendering the template.
|
||||
const templateContext = {
|
||||
cmid: cmid,
|
||||
activityname: activityname,
|
||||
overallcomplete: completed,
|
||||
overallincomplete: !completed,
|
||||
istrackeduser: true, // We know that we're tracking completion for this user given the presence of this button.
|
||||
};
|
||||
const renderObject = await Templates.renderForPromise('core_course/completion_manual', templateContext);
|
||||
|
||||
// Replace the toggle button with the newly loaded template.
|
||||
await Templates.replaceNode(toggleButton, renderObject.html, renderObject.js);
|
||||
|
||||
} catch (exception) {
|
||||
// In case of an error, revert the original state and appearance of the button.
|
||||
toggleButton.removeAttribute('disabled');
|
||||
toggleButton.innerHTML = originalInnerHtml;
|
||||
|
||||
// Show the exception.
|
||||
Notification.exception(exception);
|
||||
}
|
||||
};
|
|
@ -20,102 +20,121 @@
|
|||
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/ajax'], function($, Ajax) {
|
||||
|
||||
/**
|
||||
* Get the list of courses that the logged in user is enrolled in for a given
|
||||
* timeline classification.
|
||||
*
|
||||
* @param {string} classification past, inprogress, or future
|
||||
* @param {int} limit Only return this many results
|
||||
* @param {int} offset Skip this many results from the start of the result set
|
||||
* @param {string} sort Column to sort by and direction, e.g. 'shortname asc'
|
||||
* @return {object} jQuery promise resolved with courses.
|
||||
*/
|
||||
var getEnrolledCoursesByTimelineClassification = function(classification, limit, offset, sort) {
|
||||
var args = {
|
||||
classification: classification
|
||||
};
|
||||
import Ajax from 'core/ajax';
|
||||
|
||||
if (typeof limit !== 'undefined') {
|
||||
args.limit = limit;
|
||||
}
|
||||
|
||||
if (typeof offset !== 'undefined') {
|
||||
args.offset = offset;
|
||||
}
|
||||
|
||||
if (typeof sort !== 'undefined') {
|
||||
args.sort = sort;
|
||||
}
|
||||
|
||||
var request = {
|
||||
methodname: 'core_course_get_enrolled_courses_by_timeline_classification',
|
||||
args: args
|
||||
};
|
||||
|
||||
return Ajax.call([request])[0];
|
||||
/**
|
||||
* Get the list of courses that the logged in user is enrolled in for a given
|
||||
* timeline classification.
|
||||
*
|
||||
* @param {string} classification past, inprogress, or future
|
||||
* @param {int} limit Only return this many results
|
||||
* @param {int} offset Skip this many results from the start of the result set
|
||||
* @param {string} sort Column to sort by and direction, e.g. 'shortname asc'
|
||||
* @return {object} jQuery promise resolved with courses.
|
||||
*/
|
||||
const getEnrolledCoursesByTimelineClassification = (classification, limit, offset, sort) => {
|
||||
const args = {
|
||||
classification: classification
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of courses that the user has most recently accessed.
|
||||
*
|
||||
* @method getLastAccessedCourses
|
||||
* @param {int} userid User from which the courses will be obtained
|
||||
* @param {int} limit Only return this many results
|
||||
* @param {int} offset Skip this many results from the start of the result set
|
||||
* @param {string} sort Column to sort by and direction, e.g. 'shortname asc'
|
||||
* @return {promise} Resolved with an array of courses
|
||||
*/
|
||||
var getLastAccessedCourses = function(userid, limit, offset, sort) {
|
||||
var args = {};
|
||||
if (typeof limit !== 'undefined') {
|
||||
args.limit = limit;
|
||||
}
|
||||
|
||||
if (typeof userid !== 'undefined') {
|
||||
args.userid = userid;
|
||||
}
|
||||
if (typeof offset !== 'undefined') {
|
||||
args.offset = offset;
|
||||
}
|
||||
|
||||
if (typeof limit !== 'undefined') {
|
||||
args.limit = limit;
|
||||
}
|
||||
if (typeof sort !== 'undefined') {
|
||||
args.sort = sort;
|
||||
}
|
||||
|
||||
if (typeof offset !== 'undefined') {
|
||||
args.offset = offset;
|
||||
}
|
||||
|
||||
if (typeof sort !== 'undefined') {
|
||||
args.sort = sort;
|
||||
}
|
||||
|
||||
var request = {
|
||||
methodname: 'core_course_get_recent_courses',
|
||||
args: args
|
||||
};
|
||||
|
||||
return Ajax.call([request])[0];
|
||||
const request = {
|
||||
methodname: 'core_course_get_enrolled_courses_by_timeline_classification',
|
||||
args: args
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of users enrolled in this cmid.
|
||||
*
|
||||
* @param {Number} cmid Course Module from which the users will be obtained
|
||||
* @param {Number} groupID Group ID from which the users will be obtained
|
||||
* @returns {Promise} Promise containing a list of users
|
||||
*/
|
||||
var getEnrolledUsersFromCourseModuleID = function(cmid, groupID) {
|
||||
var request = {
|
||||
methodname: 'core_course_get_enrolled_users_by_cmid',
|
||||
args: {
|
||||
cmid: cmid,
|
||||
groupid: groupID,
|
||||
},
|
||||
};
|
||||
return Ajax.call([request])[0];
|
||||
};
|
||||
|
||||
return Ajax.call([request])[0];
|
||||
/**
|
||||
* Get the list of courses that the user has most recently accessed.
|
||||
*
|
||||
* @method getLastAccessedCourses
|
||||
* @param {int} userid User from which the courses will be obtained
|
||||
* @param {int} limit Only return this many results
|
||||
* @param {int} offset Skip this many results from the start of the result set
|
||||
* @param {string} sort Column to sort by and direction, e.g. 'shortname asc'
|
||||
* @return {promise} Resolved with an array of courses
|
||||
*/
|
||||
const getLastAccessedCourses = (userid, limit, offset, sort) => {
|
||||
const args = {};
|
||||
|
||||
if (typeof userid !== 'undefined') {
|
||||
args.userid = userid;
|
||||
}
|
||||
|
||||
if (typeof limit !== 'undefined') {
|
||||
args.limit = limit;
|
||||
}
|
||||
|
||||
if (typeof offset !== 'undefined') {
|
||||
args.offset = offset;
|
||||
}
|
||||
|
||||
if (typeof sort !== 'undefined') {
|
||||
args.sort = sort;
|
||||
}
|
||||
|
||||
const request = {
|
||||
methodname: 'core_course_get_recent_courses',
|
||||
args: args
|
||||
};
|
||||
|
||||
return {
|
||||
getEnrolledCoursesByTimelineClassification: getEnrolledCoursesByTimelineClassification,
|
||||
getLastAccessedCourses: getLastAccessedCourses,
|
||||
getUsersFromCourseModuleID: getEnrolledUsersFromCourseModuleID,
|
||||
return Ajax.call([request])[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of users enrolled in this cmid.
|
||||
*
|
||||
* @param {Number} cmid Course Module from which the users will be obtained
|
||||
* @param {Number} groupID Group ID from which the users will be obtained
|
||||
* @returns {Promise} Promise containing a list of users
|
||||
*/
|
||||
const getEnrolledUsersFromCourseModuleID = (cmid, groupID) => {
|
||||
var request = {
|
||||
methodname: 'core_course_get_enrolled_users_by_cmid',
|
||||
args: {
|
||||
cmid: cmid,
|
||||
groupid: groupID,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return Ajax.call([request])[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the completion state of an activity with manual completion.
|
||||
*
|
||||
* @param {Number} cmid The course module ID.
|
||||
* @param {Boolean} completed Whether to set as complete or not.
|
||||
* @returns {object} jQuery promise
|
||||
*/
|
||||
const toggleManualCompletion = (cmid, completed) => {
|
||||
const request = {
|
||||
methodname: 'core_completion_update_activity_completion_status_manually',
|
||||
args: {
|
||||
cmid,
|
||||
completed,
|
||||
}
|
||||
};
|
||||
return Ajax.call([request])[0];
|
||||
};
|
||||
|
||||
export default {
|
||||
getEnrolledCoursesByTimelineClassification,
|
||||
getLastAccessedCourses,
|
||||
getUsersFromCourseModuleID: getEnrolledUsersFromCourseModuleID,
|
||||
toggleManualCompletion,
|
||||
};
|
||||
|
|
154
course/classes/output/activity_information.php
Normal file
154
course/classes/output/activity_information.php
Normal file
|
@ -0,0 +1,154 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* File containing the class activity information renderable.
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2021 Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_course\output;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use cm_info;
|
||||
use completion_info;
|
||||
use context;
|
||||
use core\activity_dates;
|
||||
use core_completion\cm_completion_details;
|
||||
use core_user;
|
||||
use core_user\fields;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
|
||||
/**
|
||||
* The activity information renderable class.
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2021 Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class activity_information implements renderable, templatable {
|
||||
|
||||
/** @var cm_info The course module information. */
|
||||
protected $cminfo = null;
|
||||
|
||||
/** @var array The array of relevant dates for this activity. */
|
||||
protected $activitydates = [];
|
||||
|
||||
/** @var cm_completion_details The user's completion details for this activity. */
|
||||
protected $cmcompletion = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param cm_info $cminfo The course module information.
|
||||
* @param cm_completion_details $cmcompletion The course module information.
|
||||
* @param array $activitydates The activity dates.
|
||||
*/
|
||||
public function __construct(cm_info $cminfo, cm_completion_details $cmcompletion, array $activitydates) {
|
||||
$this->cminfo = $cminfo;
|
||||
$this->cmcompletion = $cmcompletion;
|
||||
$this->activitydates = $activitydates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output Renderer base.
|
||||
* @return stdClass
|
||||
*/
|
||||
public function export_for_template(renderer_base $output): stdClass {
|
||||
$data = $this->build_completion_data();
|
||||
|
||||
$data->cmid = $this->cminfo->id;
|
||||
$data->activityname = $this->cminfo->name;
|
||||
$data->activitydates = $this->activitydates;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the completion data for export.
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
protected function build_completion_data(): stdClass {
|
||||
$data = new stdClass();
|
||||
|
||||
$data->hascompletion = $this->cmcompletion->has_completion();
|
||||
$data->isautomatic = $this->cmcompletion->is_automatic();
|
||||
|
||||
// Get the name of the user overriding the completion condition, if available.
|
||||
$data->overrideby = null;
|
||||
$overrideby = $this->cmcompletion->overridden_by();
|
||||
$overridebyname = null;
|
||||
if (!empty($overrideby)) {
|
||||
$userfields = fields::for_name();
|
||||
$overridebyrecord = core_user::get_user($overrideby, 'id ' . $userfields->get_sql()->selects, MUST_EXIST);
|
||||
$data->overrideby = fullname($overridebyrecord);
|
||||
}
|
||||
|
||||
// We'll show only the completion conditions and not the completion status if we're not tracking completion for this user
|
||||
// (e.g. a teacher, admin).
|
||||
$data->istrackeduser = $this->cmcompletion->is_tracked_user();
|
||||
|
||||
// Overall completion states.
|
||||
$overallcompletion = $this->cmcompletion->get_overall_completion();
|
||||
$data->overallcomplete = $overallcompletion == COMPLETION_COMPLETE;
|
||||
$data->overallincomplete = $overallcompletion == COMPLETION_INCOMPLETE;
|
||||
|
||||
// Set an accessible description for manual completions with overridden completion state.
|
||||
if (!$data->isautomatic && $data->overrideby) {
|
||||
$setbydata = (object)[
|
||||
'activityname' => $this->cminfo->name,
|
||||
'setby' => $data->overrideby,
|
||||
];
|
||||
$setbylangkey = $data->overallcomplete ? 'completion_setby:manual:done' : 'completion_setby:manual:markdone';
|
||||
$data->accessibledescription = get_string($setbylangkey, 'course', $setbydata);
|
||||
}
|
||||
|
||||
// Build automatic completion details.
|
||||
$details = [];
|
||||
foreach ($this->cmcompletion->get_details() as $key => $detail) {
|
||||
// Set additional attributes for the template.
|
||||
$detail->key = $key;
|
||||
$detail->statuscomplete = in_array($detail->status, [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS]);
|
||||
$detail->statuscompletefail = $detail->status == COMPLETION_COMPLETE_FAIL;
|
||||
$detail->statusincomplete = $detail->status == COMPLETION_INCOMPLETE;
|
||||
|
||||
// Add an accessible description to be used for title and aria-label attributes for overridden completion details.
|
||||
if ($data->overrideby) {
|
||||
$setbydata = (object)[
|
||||
'condition' => $detail->description,
|
||||
'setby' => $data->overrideby,
|
||||
];
|
||||
$detail->accessibledescription = get_string('completion_setby:auto', 'course', $setbydata);
|
||||
}
|
||||
|
||||
// We don't need the status in the template.
|
||||
unset($detail->status);
|
||||
|
||||
$details[] = $detail;
|
||||
}
|
||||
$data->completiondetails = $details;
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -278,6 +278,11 @@ class course_edit_form extends moodleform {
|
|||
$mform->addHelpButton('showreports', 'showreports');
|
||||
$mform->setDefault('showreports', $courseconfig->showreports);
|
||||
|
||||
// Show activity dates.
|
||||
$mform->addElement('selectyesno', 'showactivitydates', get_string('showactivitydates'));
|
||||
$mform->addHelpButton('showactivitydates', 'showactivitydates');
|
||||
$mform->setDefault('showactivitydates', $courseconfig->showactivitydates);
|
||||
|
||||
// Files and uploads.
|
||||
$mform->addElement('header', 'filehdr', get_string('filesanduploads'));
|
||||
|
||||
|
@ -312,6 +317,12 @@ class course_edit_form extends moodleform {
|
|||
$mform->addElement('selectyesno', 'enablecompletion', get_string('enablecompletion', 'completion'));
|
||||
$mform->setDefault('enablecompletion', $courseconfig->enablecompletion);
|
||||
$mform->addHelpButton('enablecompletion', 'enablecompletion', 'completion');
|
||||
|
||||
$showcompletionconditions = $courseconfig->showcompletionconditions ?? COMPLETION_SHOW_CONDITIONS;
|
||||
$mform->addElement('selectyesno', 'showcompletionconditions', get_string('showcompletionconditions', 'completion'));
|
||||
$mform->addHelpButton('showcompletionconditions', 'showcompletionconditions', 'completion');
|
||||
$mform->setDefault('showcompletionconditions', $showcompletionconditions);
|
||||
$mform->hideIf('showcompletionconditions', 'enablecompletion', 'eq', COMPLETION_HIDE_CONDITIONS);
|
||||
} else {
|
||||
$mform->addElement('hidden', 'enablecompletion');
|
||||
$mform->setType('enablecompletion', PARAM_INT);
|
||||
|
|
|
@ -2188,6 +2188,19 @@ class core_course_renderer extends plugin_renderer_base {
|
|||
return $this->coursecat_tree($chelper, $tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the activity information.
|
||||
*
|
||||
* Defer to template.
|
||||
*
|
||||
* @param \core_course\output\activity_information $page
|
||||
* @return string html for the page
|
||||
*/
|
||||
public function render_activity_information(\core_course\output\activity_information $page) {
|
||||
$data = $page->export_for_template($this->output);
|
||||
return $this->output->render_from_template('core_course/activity_info', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the activity navigation.
|
||||
*
|
||||
|
|
30
course/templates/activity_date.mustache
Normal file
30
course/templates/activity_date.mustache
Normal file
|
@ -0,0 +1,30 @@
|
|||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_course/activity_date
|
||||
|
||||
Template for displaying the activity's dates.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"label": "Opens:",
|
||||
"timestamp": 1293876000
|
||||
}
|
||||
}}
|
||||
<div>
|
||||
<strong>{{label}}</strong> {{#userdate}} {{timestamp}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}
|
||||
</div>
|
68
course/templates/activity_info.mustache
Normal file
68
course/templates/activity_info.mustache
Normal file
|
@ -0,0 +1,68 @@
|
|||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_course/activity_info
|
||||
|
||||
Container to display activity information such as:
|
||||
- Activity dates
|
||||
- Activity completion requirements (automatic completion)
|
||||
- Manual completion button
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"hascompletion": true,
|
||||
"isautomatic": true,
|
||||
"istrackeduser": true,
|
||||
"activitydates": [
|
||||
{
|
||||
"label": "Opens:",
|
||||
"timestamp": 1293876000
|
||||
}
|
||||
],
|
||||
"completiondetails": [
|
||||
{
|
||||
"statuscomplete": 1,
|
||||
"description": "Viewed"
|
||||
},
|
||||
{
|
||||
"statusincomplete": 1,
|
||||
"description": "Receive a grade"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
<div data-region="activity-information" class="activity-information">
|
||||
<div class="mb-1">
|
||||
{{#activitydates}}
|
||||
{{>core_course/activity_date}}
|
||||
{{/activitydates}}
|
||||
</div>
|
||||
<div>
|
||||
{{#hascompletion}}
|
||||
{{#isautomatic}}
|
||||
<div class="automatic-completion-conditions" data-region ="completionrequirements" role="list" aria-label="{{#str}}completionrequirements, core_course{{/str}}">
|
||||
{{#completiondetails}}
|
||||
{{> core_course/completion_automatic }}
|
||||
{{/completiondetails}}
|
||||
</div>
|
||||
{{/isautomatic}}
|
||||
{{^isautomatic}}
|
||||
{{> core_course/completion_manual }}
|
||||
{{/isautomatic}}
|
||||
{{/hascompletion}}
|
||||
</div>
|
||||
</div>
|
63
course/templates/completion_automatic.mustache
Normal file
63
course/templates/completion_automatic.mustache
Normal file
|
@ -0,0 +1,63 @@
|
|||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_course/completion_automatic
|
||||
|
||||
Template for displaying an automatic completion rule and its status.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"statuscomplete": 1,
|
||||
"description": "View",
|
||||
"istrackeduser": true,
|
||||
"accessibledescription": "Done: View (Set by Admin User)"
|
||||
}
|
||||
}}
|
||||
{{#istrackeduser}}
|
||||
{{#statuscomplete}}
|
||||
<span class="badge badge-success rounded mb-1" role="listitem" {{!
|
||||
}}{{#accessibledescription}}{{!
|
||||
}}title="{{.}}" {{!
|
||||
}}aria-label="{{.}}" {{!
|
||||
}}{{/accessibledescription}}>
|
||||
<strong>{{#str}}completion_automatic:done, core_course{{/str}}</strong> <span class="font-weight-normal">{{description}}</span>
|
||||
</span>
|
||||
{{/statuscomplete}}
|
||||
{{#statuscompletefail}}
|
||||
<span class="badge badge-danger rounded mb-1" role="listitem" {{!
|
||||
}}{{#accessibledescription}}{{!
|
||||
}}title="{{.}}" {{!
|
||||
}}aria-label="{{.}}" {{!
|
||||
}}{{/accessibledescription}}>
|
||||
<strong>{{#str}}completion_automatic:failed, core_course{{/str}}</strong> <span class="font-weight-normal">{{description}}</span>
|
||||
</span>
|
||||
{{/statuscompletefail}}
|
||||
{{#statusincomplete}}
|
||||
<span class="badge badge-secondary rounded mb-1" role="listitem" {{!
|
||||
}}{{#accessibledescription}}{{!
|
||||
}}title="{{.}}" {{!
|
||||
}}aria-label="{{.}}" {{!
|
||||
}}{{/accessibledescription}}>
|
||||
<strong>{{#str}}completion_automatic:todo, core_course{{/str}}</strong> <span class="font-weight-normal">{{description}}</span>
|
||||
</span>
|
||||
{{/statusincomplete}}
|
||||
{{/istrackeduser}}
|
||||
{{^istrackeduser}}
|
||||
<span class="badge badge-secondary rounded mb-1" role="listitem">
|
||||
<span class="font-weight-normal">{{description}}</span>
|
||||
</span>
|
||||
{{/istrackeduser}}
|
67
course/templates/completion_manual.mustache
Normal file
67
course/templates/completion_manual.mustache
Normal file
|
@ -0,0 +1,67 @@
|
|||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_course/completion_manual
|
||||
|
||||
Template for displaying the manual completion button.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"cmid": 0,
|
||||
"overallcomplete": true,
|
||||
"overallincomplete": false
|
||||
}
|
||||
}}
|
||||
{{#istrackeduser}}
|
||||
{{#overallcomplete}}
|
||||
<button class="btn btn-outline-success" data-action="toggle-manual-completion" data-toggletype="manual:undo" data-cmid="{{cmid}}" data-activityname="{{activityname}}" {{!
|
||||
}}{{#accessibledescription}}{{!
|
||||
}}title="{{.}}" {{!
|
||||
}}aria-label="{{.}}" {{!
|
||||
}}{{/accessibledescription}}{{!
|
||||
}}{{^accessibledescription}}{{!
|
||||
}}title="{{#str}}completion_manual:aria:done, course, {{activityname}} {{/str}}" {{!
|
||||
}}aria-label="{{#str}}completion_manual:aria:done, course, {{activityname}} {{/str}}" {{!
|
||||
}}{{/accessibledescription}}>
|
||||
<i class="fa fa-check" aria-hidden="true"></i> {{#str}} completion_manual:done, core_course {{/str}}
|
||||
</button>
|
||||
{{/overallcomplete}}
|
||||
{{#overallincomplete}}
|
||||
<button class="btn btn-outline-secondary" data-action="toggle-manual-completion" data-toggletype="manual:mark-done" data-cmid="{{cmid}}" data-activityname="{{activityname}}" {{!
|
||||
}}{{#accessibledescription}}{{!
|
||||
}}title="{{.}}" {{!
|
||||
}}aria-label="{{.}}" {{!
|
||||
}}{{/accessibledescription}}{{!
|
||||
}}{{^accessibledescription}}{{!
|
||||
}}title="{{#str}}completion_manual:aria:markdone, course, {{activityname}} {{/str}}" {{!
|
||||
}}aria-label="{{#str}}completion_manual:aria:markdone, course, {{activityname}} {{/str}}" {{!
|
||||
}}{{/accessibledescription}}>
|
||||
{{#str}} completion_manual:markdone, core_course {{/str}}
|
||||
</button>
|
||||
{{/overallincomplete}}
|
||||
{{/istrackeduser}}
|
||||
{{^istrackeduser}}
|
||||
<button class="btn btn-outline-secondary" disabled>
|
||||
{{#str}} completion_manual:markdone, core_course {{/str}}
|
||||
</button>
|
||||
{{/istrackeduser}}
|
||||
|
||||
{{#js}}
|
||||
require(['core_course/manual_completion_toggle'], toggle => {
|
||||
toggle.init()
|
||||
});
|
||||
{{/js}}
|
64
course/tests/behat/course_activity_dates.feature
Normal file
64
course/tests/behat/course_activity_dates.feature
Normal file
|
@ -0,0 +1,64 @@
|
|||
@core @core_course
|
||||
Feature: Allow teachers to edit the visibility of activity dates in a course
|
||||
In order to show students the activity dates in a course
|
||||
As a teacher
|
||||
I need to be able to edit activity dates settings
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following "activities" exist:
|
||||
| activity | course | idnumber | name | intro | timeopen | timeclose |
|
||||
| choice | C1 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## |
|
||||
|
||||
Scenario: Activity dates setting can be enabled to display activity dates in a course
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
When I set the following fields to these values:
|
||||
| Show activity dates | Yes |
|
||||
And I click on "Save and display" "button"
|
||||
And I follow "Test choice"
|
||||
Then I should see "Opened:" in the "[data-region=activity-information]" "css_element"
|
||||
And I should see "Closes:" in the "[data-region=activity-information]" "css_element"
|
||||
# TODO MDL-70821: Check activity dates display on course homepage.
|
||||
|
||||
Scenario: Activity dates setting can be disabled to hidden activity dates in a course
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
When I set the following fields to these values:
|
||||
| Show activity dates | No |
|
||||
And I click on "Save and display" "button"
|
||||
And I follow "Test choice"
|
||||
# Activity dates are always shown in the module's view page.
|
||||
Then I should see "Opened:"
|
||||
And I should see "Closes:"
|
||||
And I am on "Course 1" course homepage
|
||||
And I should not see "Opened:"
|
||||
And I should not see "Closes:"
|
||||
|
||||
Scenario: Default activity dates setting default value can changed to No
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Courses > Course default settings" in site administration
|
||||
When I set the following fields to these values:
|
||||
| Show activity dates | No |
|
||||
And I click on "Save changes" "button"
|
||||
And I navigate to "Courses > Add a new course" in site administration
|
||||
Then the field "showactivitydates" matches value "No"
|
||||
|
||||
Scenario: Default activity dates setting default value can changed to Yes
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Courses > Course default settings" in site administration
|
||||
When I set the following fields to these values:
|
||||
| Show activity dates | Yes |
|
||||
And I click on "Save changes" "button"
|
||||
And I navigate to "Courses > Add a new course" in site administration
|
||||
Then the field "showactivitydates" matches value "Yes"
|
|
@ -141,6 +141,8 @@ $string['defaultcompletionupdated'] = 'Changes saved';
|
|||
$string['deletecompletiondata'] = 'Delete completion data';
|
||||
$string['dependencies'] = 'Dependencies';
|
||||
$string['dependenciescompleted'] = 'Completion of other courses';
|
||||
$string['detail_desc:receivegrade'] = 'Receive a grade';
|
||||
$string['detail_desc:view'] = 'View';
|
||||
$string['hiddenrules'] = 'Some settings specific to <b>{$a}</b> have been hidden. To view unselect other activities';
|
||||
$string['editcoursecompletionsettings'] = 'Edit course completion settings';
|
||||
$string['enablecompletion'] = 'Enable completion tracking';
|
||||
|
@ -219,6 +221,8 @@ $string['seedetails'] = 'See details';
|
|||
$string['select'] = 'Select';
|
||||
$string['self'] = 'Self';
|
||||
$string['selfcompletion'] = 'Self completion';
|
||||
$string['showcompletionconditions'] = 'Show completion conditions';
|
||||
$string['showcompletionconditions_help'] = 'The activity completion conditions are displayed in the list of activities on the course page.';
|
||||
$string['showinguser'] = 'Showing user';
|
||||
$string['unenrolingfromcourse'] = 'Unenrolling from course';
|
||||
$string['unenrolment'] = 'Unenrolment';
|
||||
|
|
|
@ -46,6 +46,17 @@ $string['aria:favourite'] = 'Course is starred';
|
|||
$string['aria:favouritestab'] = 'Starred activities';
|
||||
$string['aria:recommendedtab'] = 'Recommended activities';
|
||||
$string['aria:modulefavourite'] = 'Star {$a} activity';
|
||||
$string['completion_automatic:done'] = 'Done:';
|
||||
$string['completion_automatic:failed'] = 'Failed:';
|
||||
$string['completion_automatic:todo'] = 'To do:';
|
||||
$string['completion_manual:aria:done'] = '{$a} is marked as done. Press to undo.';
|
||||
$string['completion_manual:aria:markdone'] = 'Mark {$a} as done';
|
||||
$string['completion_manual:done'] = 'Done';
|
||||
$string['completion_manual:markdone'] = 'Mark as done';
|
||||
$string['completion_setby:auto'] = 'Done: {$a->condition} (set by {$a->setby})';
|
||||
$string['completion_setby:manual:done'] = '{$a->activityname} is marked by {$a->setby} as done. Press to undo.';
|
||||
$string['completion_setby:manual:markdone'] = '{$a->activityname} is marked by {$a->setby} as not done. Press to mark as done.';
|
||||
$string['completionrequirements'] = 'Completion requirements for this activity';
|
||||
$string['coursealreadyfinished'] = 'Course already finished';
|
||||
$string['coursenotyetstarted'] = 'The course has not yet started';
|
||||
$string['coursenotyetfinished'] = 'The course has not yet finished';
|
||||
|
|
|
@ -1904,6 +1904,8 @@ $string['shortnameuser'] = 'User short name';
|
|||
$string['shortsitename'] = 'Short name for site (eg single word)';
|
||||
$string['show'] = 'Show';
|
||||
$string['showactions'] = 'Show actions';
|
||||
$string['showactivitydates'] = 'Show activity dates';
|
||||
$string['showactivitydates_help'] = 'The activity dates are displayed in the list of activities on the course page.';
|
||||
$string['showadvancededitor'] = 'Advanced';
|
||||
$string['showadvancedsettings'] = 'Show advanced settings';
|
||||
$string['showall'] = 'Show all {$a}';
|
||||
|
|
|
@ -141,6 +141,15 @@ define('COMPLETION_AGGREGATION_ALL', 1);
|
|||
*/
|
||||
define('COMPLETION_AGGREGATION_ANY', 2);
|
||||
|
||||
/**
|
||||
* Completion conditions will be displayed to user.
|
||||
*/
|
||||
define('COMPLETION_SHOW_CONDITIONS', 1);
|
||||
|
||||
/**
|
||||
* Completion conditions will be hidden from user.
|
||||
*/
|
||||
define('COMPLETION_HIDE_CONDITIONS', 0);
|
||||
|
||||
/**
|
||||
* Utility function for checking if the logged in user can view
|
||||
|
|
|
@ -103,6 +103,8 @@
|
|||
<FIELD NAME="completionnotify" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Notify users when they complete this course"/>
|
||||
<FIELD NAME="cacherev" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Incrementing revision for validating the course content cache"/>
|
||||
<FIELD NAME="originalcourseid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="the id of the source course when a new course originates from a restore of another course on the same site."/>
|
||||
<FIELD NAME="showactivitydates" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether to display activity dates to user. 0 = do not display, 1 = display activity dates"/>
|
||||
<FIELD NAME="showcompletionconditions" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Whether to display completion conditions to user. 0 = do not display, 1 = display conditions"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
|
|
|
@ -409,6 +409,7 @@ $functions = array(
|
|||
'description' => 'Update completion status for the current user in an activity, only for activities with manual tracking.',
|
||||
'type' => 'write',
|
||||
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
|
||||
'ajax' => true,
|
||||
),
|
||||
'core_completion_override_activity_completion_status' => array(
|
||||
'classname' => 'core_completion_external',
|
||||
|
|
|
@ -2521,5 +2521,33 @@ function xmldb_main_upgrade($oldversion) {
|
|||
upgrade_main_savepoint(true, 2021031200.01);
|
||||
}
|
||||
|
||||
if ($oldversion < 2021033100.00) {
|
||||
// Define field 'showactivitydates' to be added to course table.
|
||||
$table = new xmldb_table('course');
|
||||
$field = new xmldb_field('showactivitydates', XMLDB_TYPE_INTEGER, '1', null,
|
||||
XMLDB_NOTNULL, null, '0', 'originalcourseid');
|
||||
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2021033100.00);
|
||||
}
|
||||
|
||||
if ($oldversion < 2021033100.01) {
|
||||
// Define field 'showcompletionconditions' to be added to course.
|
||||
$table = new xmldb_table('course');
|
||||
$field = new xmldb_field('showcompletionconditions', XMLDB_TYPE_INTEGER, '1', null,
|
||||
XMLDB_NOTNULL, null, '1', 'completionnotify');
|
||||
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2021033100.01);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,9 @@
|
|||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use core_completion\cm_completion_details;
|
||||
use core_course\output\activity_information;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
|
@ -899,6 +902,21 @@ class core_renderer extends renderer_base {
|
|||
return '<div role="main">'.$this->unique_main_content_token.'</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about an activity.
|
||||
*
|
||||
* @param cm_info $cminfo The course module information.
|
||||
* @param cm_completion_details $completiondetails The completion details for this activity module.
|
||||
* @param array $activitydates The dates for this activity module.
|
||||
* @return string the activity information HTML.
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function activity_information(cm_info $cminfo, cm_completion_details $completiondetails, array $activitydates): string {
|
||||
$activityinfo = new activity_information($cminfo, $completiondetails, $activitydates);
|
||||
$renderer = $this->page->get_renderer('core', 'course');
|
||||
return $renderer->render($activityinfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns standard navigation between activities in a course.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
@mod @mod_choice @core_completion
|
||||
Feature: Automatic completion in the choice activity
|
||||
In order for me to know what to do to complete the choice activity
|
||||
As a student
|
||||
I need to be able to see the completion requirements of the choice activity
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
And the following "course" exists:
|
||||
| fullname | Course 1 |
|
||||
| shortname | C1 |
|
||||
| category | 0 |
|
||||
| enablecompletion | 1 |
|
||||
And the following "activity" exists:
|
||||
| activity | choice |
|
||||
| name | What to drink? |
|
||||
| intro | Friday drinks, anyone? |
|
||||
| course | C1 |
|
||||
| idnumber | choice1 |
|
||||
| completion | 2 |
|
||||
| completionview | 1 |
|
||||
| completionsubmit | 1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
|
||||
Scenario: Viewing a choice activity with automatic completion as a student
|
||||
Given I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "What to drink?"
|
||||
Then I should see "Done: View" in the "[data-region=completionrequirements]" "css_element"
|
||||
And I should see "To do: Make a choice" in the "[data-region=completionrequirements]" "css_element"
|
||||
And I set the field "Beer" to "1"
|
||||
And I press "Save my choice"
|
||||
And I should see "Done: View" in the "[data-region=completionrequirements]" "css_element"
|
||||
And I should see "Done: Make a choice" in the "[data-region=completionrequirements]" "css_element"
|
||||
|
||||
Scenario: Viewing a choice activity with automatic completion as a teacher
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "What to drink?"
|
||||
And I should see "View" in the "[data-region=completionrequirements]" "css_element"
|
||||
And I should see "Make a choice" in the "[data-region=completionrequirements]" "css_element"
|
||||
|
||||
@javascript
|
||||
Scenario: Overriding automatic choice completion for a user
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Reports > Activity completion" in current page administration
|
||||
And I click on "Student 1, What to drink?: Not completed" "link"
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "What to drink?"
|
||||
Then "span[aria-label=\"Done: View (set by Teacher 1)\"]" "css_element" should exist
|
||||
And "span[aria-label=\"Done: Make a choice (set by Teacher 1)\"]" "css_element" should exist
|
|
@ -0,0 +1,96 @@
|
|||
@mod @mod_choice @core_completion
|
||||
Feature: Manual completion in the choice activity
|
||||
To avoid navigating from the choice activity to the course homepage to mark the choice activity as complete
|
||||
As a student
|
||||
I need to be able to mark the choice activity as complete within the choice activity itself
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
And the following "course" exists:
|
||||
| fullname | Course 1 |
|
||||
| shortname | C1 |
|
||||
| category | 0 |
|
||||
| enablecompletion | 1 |
|
||||
And the following "activity" exists:
|
||||
| activity | choice |
|
||||
| name | What to drink? |
|
||||
| intro | Friday drinks, anyone? |
|
||||
| course | C1 |
|
||||
| idnumber | choice1 |
|
||||
| completion | 1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
|
||||
@javascript
|
||||
Scenario: Toggle manual completion as a student
|
||||
Given I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "What to drink?"
|
||||
And "Mark as done" "button" should exist
|
||||
When I press "Mark as done"
|
||||
And I wait until the page is ready
|
||||
Then "Done" "button" should exist
|
||||
But "Mark as done" "button" should not exist
|
||||
# Just make sure that the change persisted.
|
||||
And I reload the page
|
||||
And I wait until the page is ready
|
||||
And I should not see "Mark as done"
|
||||
And I should see "Done"
|
||||
And I press "Done"
|
||||
And I wait until the page is ready
|
||||
And "Mark as done" "button" should exist
|
||||
But "Done" "button" should not exist
|
||||
# Just make sure that the change persisted.
|
||||
And I reload the page
|
||||
And I wait until the page is ready
|
||||
And "Mark as done" "button" should exist
|
||||
But "Done" "button" should not exist
|
||||
|
||||
Scenario: Viewing a choice activity with manual completion as a teacher
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "What to drink?"
|
||||
Then the "Mark as done" "button" should be disabled
|
||||
|
||||
@javascript
|
||||
Scenario: Overriding a manual choice completion for a user to done
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Reports > Activity completion" in current page administration
|
||||
And I click on "Student 1, What to drink?: Not completed" "link"
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "What to drink?"
|
||||
Then "button[aria-label=\"What to drink? is marked by Teacher 1 as done. Press to undo.\"]" "css_element" should exist
|
||||
And I press "Done"
|
||||
And I wait until the page is ready
|
||||
And "button[aria-label=\"Mark What to drink? as done\"]" "css_element" should exist
|
||||
|
||||
@javascript
|
||||
Scenario: Overriding a manual choice completion for a user to not done
|
||||
Given I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "What to drink?"
|
||||
And I press "Mark as done"
|
||||
And I wait until the page is ready
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Reports > Activity completion" in current page administration
|
||||
And I click on "Student 1, What to drink?: Completed" "link"
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "What to drink?"
|
||||
Then "button[aria-label=\"What to drink? is marked by Teacher 1 as not done. Press to mark as done.\"]" "css_element" should exist
|
||||
And I press "Mark as done"
|
||||
And I wait until the page is ready
|
||||
And "button[aria-label=\"What to drink? is marked as done. Press to undo.\"]" "css_element" should exist
|
|
@ -19,6 +19,7 @@ $PAGE->set_url($url);
|
|||
if (! $cm = get_coursemodule_from_id('choice', $id)) {
|
||||
print_error('invalidcoursemodule');
|
||||
}
|
||||
$cm = cm_info::create($cm);
|
||||
|
||||
if (! $course = $DB->get_record("course", array("id" => $cm->course))) {
|
||||
print_error('coursemisconf');
|
||||
|
@ -99,6 +100,11 @@ choice_view($choice, $course, $cm, $context);
|
|||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(format_string($choice->name), 2, null);
|
||||
|
||||
// Render the activity information.
|
||||
$completiondetails = \core_completion\cm_completion_details::get_instance($cm, $USER->id);
|
||||
$activitydates = \core\activity_dates::get_dates_for_module($cm, $USER->id);
|
||||
echo $OUTPUT->activity_information($cm, $completiondetails, $activitydates);
|
||||
|
||||
if ($notify and confirm_sesskey()) {
|
||||
if ($notify === 'choicesaved') {
|
||||
echo $OUTPUT->notification(get_string('choicesaved', 'choice'), 'notifysuccess');
|
||||
|
|
|
@ -1191,3 +1191,9 @@ span.editinstructions {
|
|||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.automatic-completion-conditions {
|
||||
.badge {
|
||||
font-size: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,15 @@ select {
|
|||
width: auto;
|
||||
}
|
||||
|
||||
.path-mod {
|
||||
div.activity-information {
|
||||
border-bottom: $border-width solid $table-border-color;
|
||||
padding-top: map-get($spacers, 2);
|
||||
padding-bottom: map-get($spacers, 3);
|
||||
margin-bottom: map-get($spacers, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Choice module
|
||||
|
||||
.path-mod-choice {
|
||||
|
|
|
@ -14120,6 +14120,9 @@ span.editinstructions {
|
|||
.activity-navigation #next-activity-link {
|
||||
white-space: pre-wrap; }
|
||||
|
||||
.automatic-completion-conditions .badge {
|
||||
font-size: 100%; }
|
||||
|
||||
/* Anchor link offset fix. This makes hash links scroll 60px down to account for the fixed header. */
|
||||
:target::before {
|
||||
content: " ";
|
||||
|
@ -16752,6 +16755,12 @@ textarea[data-auto-rows] {
|
|||
select {
|
||||
width: auto; }
|
||||
|
||||
.path-mod div.activity-information {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 0.5rem; }
|
||||
|
||||
.path-mod-choice .horizontal .choices .option {
|
||||
display: inline-block; }
|
||||
|
||||
|
|
|
@ -14335,6 +14335,9 @@ span.editinstructions {
|
|||
.activity-navigation #next-activity-link {
|
||||
white-space: pre-wrap; }
|
||||
|
||||
.automatic-completion-conditions .badge {
|
||||
font-size: 100%; }
|
||||
|
||||
/* Anchor link offset fix. This makes hash links scroll 60px down to account for the fixed header. */
|
||||
:target::before {
|
||||
content: " ";
|
||||
|
@ -16979,6 +16982,12 @@ textarea[data-auto-rows] {
|
|||
select {
|
||||
width: auto; }
|
||||
|
||||
.path-mod div.activity-information {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 0.5rem; }
|
||||
|
||||
.path-mod-choice .horizontal .choices .option {
|
||||
display: inline-block; }
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2021033000.00; // 20201109 = branching date YYYYMMDD - do not modify!
|
||||
$version = 2021033100.01; // 20201109 = branching date YYYYMMDD - do not modify!
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '3.11dev+ (Build: 20210330)';// Human-friendly version name
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue