Merge branch 'MDL-70817-311-8' of git://github.com/junpataleta/moodle into MOODLE_311_STABLE

This commit is contained in:
Adrian Greeve 2021-04-06 12:55:32 +08:00
commit 4025e4d726
34 changed files with 1535 additions and 89 deletions

View file

@ -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();

View 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);
}
}

View file

@ -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"

View 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());
}
}

View 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

File diff suppressed because one or more lines are too long

View file

@ -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

View 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);
}
};

View file

@ -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,
};

View 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;
}
}

View file

@ -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);

View file

@ -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.
*

View 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>

View 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>

View 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}}

View 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}}

View 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"

View file

@ -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';

View file

@ -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';

View file

@ -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}';

View file

@ -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

View file

@ -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"/>

View file

@ -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',

View file

@ -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;
}

View file

@ -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.
*

View file

@ -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

View file

@ -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

View file

@ -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');

View file

@ -1191,3 +1191,9 @@ span.editinstructions {
white-space: pre-wrap;
}
}
.automatic-completion-conditions {
.badge {
font-size: 100%;
}
}

View file

@ -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 {

View file

@ -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; }

View file

@ -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; }

View file

@ -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