MDL-58267 completion: allow to set default activity completion

Part of MDL-58138 epic
This commit is contained in:
Marina Glancy
2017-04-19 08:54:35 +08:00
committed by Jake Dallimore
parent 06cdda468a
commit 7f53e8aa22
42 changed files with 1125 additions and 407 deletions
@@ -130,6 +130,9 @@ class backup_course_task extends backup_task {
// Generate the course competencies.
$this->add_step(new backup_course_competencies_structure_step('course_competencies', 'competencies.xml'));
// Annotate activity completion defaults.
$this->add_step(new backup_completion_defaults_structure_step('course_completion_defaults', 'completiondefaults.xml'));
// Generate the inforef file (must be after ALL steps gathering annotations of ANY type)
$this->add_step(new backup_inforef_structure_step('course', 'inforef.xml'));
+46
View File
@@ -2612,3 +2612,49 @@ class backup_course_completion_structure_step extends backup_structure_step {
}
}
/**
* Backup completion defaults for each module type.
*
* @package core_backup
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_completion_defaults_structure_step extends backup_structure_step {
/**
* To conditionally decide if one step will be executed or no
*/
protected function execute_condition() {
// No completion on front page.
if ($this->get_courseid() == SITEID) {
return false;
}
return true;
}
/**
* The structure of the course completion backup
*
* @return backup_nested_element
*/
protected function define_structure() {
$cc = new backup_nested_element('course_completion_defaults');
$defaults = new backup_nested_element('course_completion_default', array('id'), array(
'modulename', 'completion', 'completionview', 'completionusegrade', 'completionexpected', 'customrules'
));
// Use module name instead of module id so we can insert into another site later.
$sourcesql = "SELECT d.id, m.name as modulename, d.completion, d.completionview, d.completionusegrade,
d.completionexpected, d.customrules
FROM {course_completion_defaults} d join {modules} m on d.module = m.id
WHERE d.course = ?";
$defaults->set_source_sql($sourcesql, array(backup::VAR_COURSEID));
$cc->add_child($defaults);
return $cc;
}
}
@@ -118,6 +118,9 @@ class restore_course_task extends restore_task {
// Course competencies.
$this->add_step(new restore_course_competencies_structure_step('course_competencies', 'competencies.xml'));
// Activity completion defaults.
$this->add_step(new restore_completion_defaults_structure_step('course_completion_defaults', 'completiondefaults.xml'));
// At the end, mark it as built
$this->built = true;
}
+77
View File
@@ -5428,3 +5428,80 @@ abstract class restore_questions_activity_structure_step extends restore_activit
}
}
}
/**
* Restore completion defaults for each module type
*
* @package core_backup
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_completion_defaults_structure_step extends restore_structure_step {
/**
* To conditionally decide if this step must be executed.
*/
protected function execute_condition() {
global $CFG;
// Completion disabled in this site, don't execute.
if (empty($CFG->enablecompletion)) {
return false;
}
// No completion on the front page.
if ($this->get_courseid() == SITEID) {
return false;
}
// No default completion info found, don't execute.
$fullpath = $this->task->get_taskbasepath();
$fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
if (!file_exists($fullpath)) {
return false;
}
// Arrived here, execute the step.
return true;
}
/**
* Function that will return the structure to be processed by this restore_step.
*
* @return restore_path_element[]
*/
protected function define_structure() {
return [new restore_path_element('completion_defaults', '/course_completion_defaults/course_completion_default')];
}
/**
* Processor for path element 'completion_defaults'
*
* @param stdClass|array $data
*/
protected function process_completion_defaults($data) {
global $DB;
$data = (array)$data;
$oldid = $data['id'];
unset($data['id']);
// Find the module by name since id may be different in another site.
if (!$mod = $DB->get_record('modules', ['name' => $data['modulename']])) {
return;
}
unset($data['modulename']);
// Find the existing record.
$newid = $DB->get_field('course_completion_defaults', 'id',
['course' => $this->task->get_courseid(), 'module' => $mod->id]);
if (!$newid) {
$newid = $DB->insert_record('course_completion_defaults',
['course' => $this->task->get_courseid(), 'module' => $mod->id] + $data);
} else {
$DB->update_record('course_completion_defaults', ['id' => $newid] + $data);
}
// Save id mapping for restoring associated events.
$this->set_mapping('course_completion_defaults', $oldid, $newid);
}
}
+6 -266
View File
@@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Bulk activity completion form
* Bulk edit activity completion form
*
* @package core_completion
* @copyright 2017 Marina Glancy
@@ -24,26 +24,18 @@
defined('MOODLE_INTERNAL') || die;
require_once ($CFG->libdir.'/formslib.php');
require_once($CFG->libdir.'/completionlib.php');
require_once($CFG->dirroot.'/course/modlib.php');
/**
* Bulk activity completion form
* Bulk edit activity completion form
*
* @package core_completion
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_completion_bulkedit_form extends moodleform {
class core_completion_bulkedit_form extends core_completion_edit_base_form {
/** @var cm_info[] list of selected course modules */
protected $cms = [];
/** @var array Do not use directly, call $this->get_module_names() */
protected $_modnames = null;
/** @var moodleform_mod Do not use directly, call $this->get_module_form() */
protected $_moduleform = null;
/** @var bool */
protected $hascustomrules = false;
/**
* Returns list of types of selected modules
@@ -61,231 +53,27 @@ class core_completion_bulkedit_form extends moodleform {
return $this->_modnames;
}
/**
* Returns true if all selected modules support tracking view.
*
* @return bool
*/
protected function support_views() {
foreach ($this->get_module_names() as $modname => $modfullname) {
if (!plugin_supports('mod', $modname, FEATURE_COMPLETION_TRACKS_VIEWS, false)) {
return false;
}
}
return true;
}
/**
* Returns true if all selected modules support grading.
*
* @return bool
*/
protected function support_grades() {
foreach ($this->get_module_names() as $modname => $modfullname) {
if (!plugin_supports('mod', $modname, FEATURE_GRADE_HAS_GRADE, false)) {
return false;
}
}
return true;
}
/**
* Returns an instance of component-specific module form for the first selected module
*
* @return moodleform_mod|null
*/
protected function get_module_form() {
global $CFG, $PAGE;
if ($this->_moduleform) {
return $this->_moduleform;
}
$cm = reset($this->cms);
$modname = $cm->modname;
$course = $cm->get_course();
$modmoodleform = "$CFG->dirroot/mod/$modname/mod_form.php";
if (file_exists($modmoodleform)) {
require_once($modmoodleform);
} else {
print_error('noformdesc');
}
list($module, $context, $cw, $cmrec, $data) = prepare_new_moduleinfo_data($course, $modname, 0);
//list($cm, $context, $module, $data, $cw) = get_moduleinfo_data($cm->get_course_module_record(), $course);
$data->return = 0;
$data->sr = 0;
$data->add = $modname;
// Initialise the form but discard all JS requirements it adds, our form has already added them.
$mformclassname = 'mod_'.$modname.'_mod_form';
if (!defined('PHPUNIT_TEST') || !PHPUNIT_TEST) {
$PAGE->start_collecting_javascript_requirements();
}
$this->_moduleform = new $mformclassname($data, 0, $cmrec, $course);
if (!defined('PHPUNIT_TEST') || !PHPUNIT_TEST) {
$PAGE->end_collecting_javascript_requirements();
}
return $this->_moduleform;
}
/**
* If all selected modules are of the same module type, adds custom completion rules from this module type
*
* @return array
*/
protected function add_custom_completion_rules() {
$modnames = array_keys($this->get_module_names());
if (count($modnames) != 1 || !plugin_supports('mod', $modnames[0], FEATURE_COMPLETION_HAS_RULES, false)) {
return [];
}
try {
// Add completion rules from the module form to this form.
$moduleform = $this->get_module_form();
$moduleform->_form = $this->_form;
if ($customcompletionelements = $moduleform->add_completion_rules()) {
$this->hascustomrules = true;
}
return $customcompletionelements;
} catch (Exception $e) {
debugging('Could not add custom completion rule of module ' . $modnames[0] .
' to this form, this has to be fixed by the developer', DEBUG_DEVELOPER);
return [];
}
}
/**
* Checks if at least one of the custom completion rules is enabled
*
* @param array $data Input data (not yet validated)
* @return bool True if one or more rules is enabled, false if none are;
* default returns false
*/
protected function completion_rule_enabled($data) {
if ($this->hascustomrules) {
return $this->get_module_form()->completion_rule_enabled($data);
}
return false;
}
/**
* Returns list of modules that have automatic completion rules that are not shown on this form
* (because they are not present in at least one other selected module).
*
* @return array
*/
protected function get_modules_with_hidden_rules() {
$modnames = $this->get_module_names();
if (count($modnames) <= 1) {
// No rules definitions conflicts if there is only one module type.
return [];
}
$conflicts = [];
if (!$this->support_views()) {
// If we don't display views rule but at least one module supports it - we have conflicts.
foreach ($modnames as $modname => $modfullname) {
if (empty($conflicts[$modname]) && plugin_supports('mod', $modname, FEATURE_COMPLETION_TRACKS_VIEWS, false)) {
$conflicts[$modname] = $modfullname;
}
}
}
if (!$this->support_grades()) {
// If we don't display grade rule but at least one module supports it - we have conflicts.
foreach ($modnames as $modname => $modfullname) {
if (empty($conflicts[$modname]) && plugin_supports('mod', $modname, FEATURE_GRADE_HAS_GRADE, false)) {
$conflicts[$modname] = $modfullname;
}
}
}
foreach ($modnames as $modname => $modfullname) {
// We do not display any custom completion rules, find modules that define them and add to conflicts list.
if (empty($conflicts[$modname]) && plugin_supports('mod', $modname, FEATURE_COMPLETION_HAS_RULES, false)) {
$conflicts[$modname] = $modfullname;
}
}
return $conflicts;
}
/**
* Form definition
*/
public function definition() {
$this->cms = $this->_customdata['cms'];
$cm = reset($this->cms); // First selected course module.
$this->course = $cm->get_course();
$mform = $this->_form;
$mform->addElement('hidden', 'id', $cm->course);
$mform->setType('id', PARAM_INT);
foreach ($this->cms as $cm) {
$mform->addElement('hidden', 'cmid['.$cm->id.']', $cm->id);
$mform->setType('cmid['.$cm->id.']', PARAM_INT);
}
// Unlock completion automatically (this element can be used in validation).
$mform->addElement('hidden', 'completionunlocked', 1);
$mform->setType('completionunlocked', PARAM_INT);
$mform->addElement('select', 'completion', get_string('completion', 'completion'),
array(COMPLETION_TRACKING_NONE=>get_string('completion_none', 'completion'),
COMPLETION_TRACKING_MANUAL=>get_string('completion_manual', 'completion')));
$mform->addHelpButton('completion', 'completion', 'completion');
$mform->setDefault('completion', COMPLETION_TRACKING_NONE);
// Automatic completion once you view it
$autocompletionpossible = false;
if ($this->support_views()) {
$mform->addElement('advcheckbox', 'completionview', get_string('completionview', 'completion'),
get_string('completionview_desc', 'completion'));
$mform->disabledIf('completionview', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
$autocompletionpossible = true;
}
// Automatic completion once it's graded
if ($this->support_grades()) {
$mform->addElement('advcheckbox', 'completionusegrade', get_string('completionusegrade', 'completion'),
get_string('completionusegrade_desc', 'completion'));
$mform->disabledIf('completionusegrade', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
$mform->addHelpButton('completionusegrade', 'completionusegrade', 'completion');
$autocompletionpossible = true;
}
// Automatic completion according to module-specific rules
foreach ($this->add_custom_completion_rules() as $element) {
$mform->disabledIf($element, 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
$autocompletionpossible = true;
}
// Automatic option only appears if possible
if ($autocompletionpossible) {
$mform->getElement('completion')->addOption(
get_string('completion_automatic', 'completion'),
COMPLETION_TRACKING_AUTOMATIC);
}
// Completion expected at particular date? (For progress tracking)
$mform->addElement('date_selector', 'completionexpected', get_string('completionexpected', 'completion'), ['optional' => true]);
$mform->addHelpButton('completionexpected', 'completionexpected', 'completion');
$mform->disabledIf('completionexpected', 'completion', 'eq', COMPLETION_TRACKING_NONE);
if ($conflicts = $this->get_modules_with_hidden_rules()) {
$mform->addElement('static', 'qwerty', '', get_string('hiddenrules', 'completion', join(', ', $conflicts)));
}
$this->add_action_buttons();
parent::definition();
$modform = $this->get_module_form();
if ($modform) {
// Pre-fill the form with the current completion rules of the first selected module.
list($cmrec, $context, $module, $data, $cw) = get_moduleinfo_data($cm->get_course_module_record(), $cm->get_course());
list($cmrec, $context, $module, $data, $cw) = get_moduleinfo_data($cm->get_course_module_record(), $this->course);
$data = (array)$data;
$modform->data_preprocessing($data);
// Unset fields that will conflict with this form and set data to this form.
@@ -294,52 +82,4 @@ class core_completion_bulkedit_form extends moodleform {
$this->set_data($data);
}
}
/**
* Form validation
*
* @param array $data array of ("fieldname"=>value) of submitted data
* @param array $files array of uploaded files "element_name"=>tmp_file_path
* @return array of "element_name"=>"error_description" if there are errors,
* or an empty array if everything is OK (true allowed for backwards compatibility too).
*/
public function validation($data, $files) {
$errors = parent::validation($data, $files);
// Completion: Don't let them choose automatic completion without turning
// on some conditions.
if (array_key_exists('completion', $data) &&
$data['completion'] == COMPLETION_TRACKING_AUTOMATIC) {
if (empty($data['completionview']) && empty($data['completionusegrade']) &&
!$this->completion_rule_enabled($data)) {
$errors['completion'] = get_string('badautocompletion', 'completion');
}
}
return $errors;
}
/**
* Returns if this form has custom completion rules. This is only possible if all selected modules have the same
* module type and this module type supports custom completion rules
*
* @return bool
*/
public function has_custom_completion_rules() {
return $this->hascustomrules;
}
/**
* Return submitted data if properly submitted or returns NULL if validation fails or
* if there is no submitted data.
*
* @return object submitted data; NULL if not valid or not submitted or cancelled
*/
public function get_data() {
$data = parent::get_data();
if ($data && $this->hascustomrules) {
$this->get_module_form()->data_postprocessing($data);
}
return $data;
}
}
+87
View File
@@ -0,0 +1,87 @@
<?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/>.
/**
* Default activity completion form
*
* @package core_completion
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
/**
* Default activity completion form
*
* @package core_completion
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_completion_defaultedit_form extends core_completion_edit_base_form {
/** @var array */
protected $modules;
/** @var array */
protected $_modnames;
/**
* Returns list of types of selected modules
*
* @return array modname=>modfullname
*/
protected function get_module_names() {
if ($this->_modnames !== null) {
return $this->_modnames;
}
$this->_modnames = [];
foreach ($this->modules as $module) {
$this->_modnames[$module->name] = $module->formattedname;
}
return $this->_modnames;
}
/**
* Form definition,
*/
public function definition() {
$this->course = $this->_customdata['course'];
$this->modules = $this->_customdata['modules'];
$mform = $this->_form;
foreach ($this->modules as $modid => $module) {
$mform->addElement('hidden', 'modids['.$modid.']', $modid);
$mform->setType('modids['.$modid.']', PARAM_INT);
}
parent::definition();
$modform = $this->get_module_form();
if ($modform) {
$modnames = array_keys($this->get_module_names());
$modname = $modnames[0];
// Pre-fill the form with the current completion rules of the first selected module type.
list($module, $context, $cw, $cmrec, $data) = prepare_new_moduleinfo_data($this->course, $modname, 0);
$data = (array)$data;
$modform->data_preprocessing($data);
// Unset fields that will conflict with this form and set data to this form.
unset($data['cmid']);
unset($data['modids']);
unset($data['id']);
$this->set_data($data);
}
}
}
+316
View File
@@ -0,0 +1,316 @@
<?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/>.
/**
* Base form for changing completion rules
*
* @package core_completion
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
require_once($CFG->libdir.'/formslib.php');
require_once($CFG->libdir.'/completionlib.php');
require_once($CFG->dirroot.'/course/modlib.php');
/**
* Base form for changing completion rules. Used in bulk editing activity completion and editing default activity completion
*
* @package core_completion
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class core_completion_edit_base_form extends moodleform {
/** @var moodleform_mod Do not use directly, call $this->get_module_form() */
protected $_moduleform = null;
/** @var bool */
protected $hascustomrules = false;
/** @var stdClass */
protected $course;
/**
* Returns list of types of selected module types
*
* @return array modname=>modfullname
*/
abstract protected function get_module_names();
/**
* Returns true if all selected modules support tracking view.
*
* @return bool
*/
protected function support_views() {
foreach ($this->get_module_names() as $modname => $modfullname) {
if (!plugin_supports('mod', $modname, FEATURE_COMPLETION_TRACKS_VIEWS, false)) {
return false;
}
}
return true;
}
/**
* Returns true if all selected modules support grading.
*
* @return bool
*/
protected function support_grades() {
foreach ($this->get_module_names() as $modname => $modfullname) {
if (!plugin_supports('mod', $modname, FEATURE_GRADE_HAS_GRADE, false)) {
return false;
}
}
return true;
}
/**
* Returns an instance of component-specific module form for the first selected module
*
* @return moodleform_mod|null
*/
protected function get_module_form() {
global $CFG, $PAGE;
if ($this->_moduleform) {
return $this->_moduleform;
}
$modnames = array_keys($this->get_module_names());
$modname = $modnames[0];
$course = $this->course;
$modmoodleform = "$CFG->dirroot/mod/$modname/mod_form.php";
if (file_exists($modmoodleform)) {
require_once($modmoodleform);
} else {
print_error('noformdesc');
}
list($module, $context, $cw, $cmrec, $data) = prepare_new_moduleinfo_data($course, $modname, 0);
$data->return = 0;
$data->sr = 0;
$data->add = $modname;
// Initialise the form but discard all JS requirements it adds, our form has already added them.
$mformclassname = 'mod_'.$modname.'_mod_form';
if (!defined('PHPUNIT_TEST') || !PHPUNIT_TEST) {
$PAGE->start_collecting_javascript_requirements();
}
$this->_moduleform = new $mformclassname($data, 0, $cmrec, $course);
if (!defined('PHPUNIT_TEST') || !PHPUNIT_TEST) {
$PAGE->end_collecting_javascript_requirements();
}
return $this->_moduleform;
}
/**
* If all selected modules are of the same module type, adds custom completion rules from this module type
*
* @return array
*/
protected function add_custom_completion_rules() {
$modnames = array_keys($this->get_module_names());
if (count($modnames) != 1 || !plugin_supports('mod', $modnames[0], FEATURE_COMPLETION_HAS_RULES, false)) {
return [];
}
try {
// Add completion rules from the module form to this form.
$moduleform = $this->get_module_form();
$moduleform->_form = $this->_form;
if ($customcompletionelements = $moduleform->add_completion_rules()) {
$this->hascustomrules = true;
}
return $customcompletionelements;
} catch (Exception $e) {
debugging('Could not add custom completion rule of module ' . $modnames[0] .
' to this form, this has to be fixed by the developer', DEBUG_DEVELOPER);
return [];
}
}
/**
* Checks if at least one of the custom completion rules is enabled
*
* @param array $data Input data (not yet validated)
* @return bool True if one or more rules is enabled, false if none are;
* default returns false
*/
protected function completion_rule_enabled($data) {
if ($this->hascustomrules) {
return $this->get_module_form()->completion_rule_enabled($data);
}
return false;
}
/**
* Returns list of modules that have automatic completion rules that are not shown on this form
* (because they are not present in at least one other selected module).
*
* @return array
*/
protected function get_modules_with_hidden_rules() {
$modnames = $this->get_module_names();
if (count($modnames) <= 1) {
// No rules definitions conflicts if there is only one module type.
return [];
}
$conflicts = [];
if (!$this->support_views()) {
// If we don't display views rule but at least one module supports it - we have conflicts.
foreach ($modnames as $modname => $modfullname) {
if (empty($conflicts[$modname]) && plugin_supports('mod', $modname, FEATURE_COMPLETION_TRACKS_VIEWS, false)) {
$conflicts[$modname] = $modfullname;
}
}
}
if (!$this->support_grades()) {
// If we don't display grade rule but at least one module supports it - we have conflicts.
foreach ($modnames as $modname => $modfullname) {
if (empty($conflicts[$modname]) && plugin_supports('mod', $modname, FEATURE_GRADE_HAS_GRADE, false)) {
$conflicts[$modname] = $modfullname;
}
}
}
foreach ($modnames as $modname => $modfullname) {
// We do not display any custom completion rules, find modules that define them and add to conflicts list.
if (empty($conflicts[$modname]) && plugin_supports('mod', $modname, FEATURE_COMPLETION_HAS_RULES, false)) {
$conflicts[$modname] = $modfullname;
}
}
return $conflicts;
}
/**
* Form definition
*/
public function definition() {
$mform = $this->_form;
// Course id.
$mform->addElement('hidden', 'id', $this->course->id);
$mform->setType('id', PARAM_INT);
// Unlock completion automatically (this element can be used in validation).
$mform->addElement('hidden', 'completionunlocked', 1);
$mform->setType('completionunlocked', PARAM_INT);
$mform->addElement('select', 'completion', get_string('completion', 'completion'),
array(COMPLETION_TRACKING_NONE => get_string('completion_none', 'completion'),
COMPLETION_TRACKING_MANUAL => get_string('completion_manual', 'completion')));
$mform->addHelpButton('completion', 'completion', 'completion');
$mform->setDefault('completion', COMPLETION_TRACKING_NONE);
// Automatic completion once you view it.
$autocompletionpossible = false;
if ($this->support_views()) {
$mform->addElement('advcheckbox', 'completionview', get_string('completionview', 'completion'),
get_string('completionview_desc', 'completion'));
$mform->disabledIf('completionview', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
$autocompletionpossible = true;
}
// Automatic completion once it's graded.
if ($this->support_grades()) {
$mform->addElement('advcheckbox', 'completionusegrade', get_string('completionusegrade', 'completion'),
get_string('completionusegrade_desc', 'completion'));
$mform->disabledIf('completionusegrade', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
$mform->addHelpButton('completionusegrade', 'completionusegrade', 'completion');
$autocompletionpossible = true;
}
// Automatic completion according to module-specific rules.
foreach ($this->add_custom_completion_rules() as $element) {
$mform->disabledIf($element, 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
$autocompletionpossible = true;
}
// Automatic option only appears if possible.
if ($autocompletionpossible) {
$mform->getElement('completion')->addOption(
get_string('completion_automatic', 'completion'),
COMPLETION_TRACKING_AUTOMATIC);
}
// Completion expected at particular date? (For progress tracking).
$mform->addElement('date_selector', 'completionexpected',
get_string('completionexpected', 'completion'), ['optional' => true]);
$mform->addHelpButton('completionexpected', 'completionexpected', 'completion');
$mform->disabledIf('completionexpected', 'completion', 'eq', COMPLETION_TRACKING_NONE);
if ($conflicts = $this->get_modules_with_hidden_rules()) {
$mform->addElement('static', 'qwerty', '', get_string('hiddenrules', 'completion', join(', ', $conflicts)));
}
$this->add_action_buttons();
}
/**
* Form validation
*
* @param array $data array of ("fieldname"=>value) of submitted data
* @param array $files array of uploaded files "element_name"=>tmp_file_path
* @return array of "element_name"=>"error_description" if there are errors,
* or an empty array if everything is OK (true allowed for backwards compatibility too).
*/
public function validation($data, $files) {
$errors = parent::validation($data, $files);
// Completion: Don't let them choose automatic completion without turning
// on some conditions.
if (array_key_exists('completion', $data) &&
$data['completion'] == COMPLETION_TRACKING_AUTOMATIC) {
if (empty($data['completionview']) && empty($data['completionusegrade']) &&
!$this->completion_rule_enabled($data)) {
$errors['completion'] = get_string('badautocompletion', 'completion');
}
}
return $errors;
}
/**
* Returns if this form has custom completion rules. This is only possible if all selected modules have the same
* module type and this module type supports custom completion rules
*
* @return bool
*/
public function has_custom_completion_rules() {
return $this->hascustomrules;
}
/**
* Return submitted data if properly submitted or returns NULL if validation fails or
* if there is no submitted data.
*
* @return object submitted data; NULL if not valid or not submitted or cancelled
*/
public function get_data() {
$data = parent::get_data();
if ($data && $this->hascustomrules) {
$this->get_module_form()->data_postprocessing($data);
}
return $data;
}
}
+154 -13
View File
@@ -28,6 +28,7 @@ namespace core_completion;
use stdClass;
use context_course;
use cm_info;
/**
* Bulk activity completion manager class
@@ -65,8 +66,7 @@ class manager {
$sectionobject = new stdClass();
$sectionobject->sectionnumber = $sectionnumber;
$sectionobject->name = get_section_name($this->courseid, $sectioninfo);
$activitiesdata = $this->get_activities($section, true);
$sectionobject->activities = $activitiesdata->activities;
$sectionobject->activities = $this->get_activities($section, true);
$data->sections[] = $sectionobject;
}
return $data;
@@ -77,7 +77,7 @@ class manager {
*
* @param array $cmids list of course module ids
* @param bool $withcompletiondetails include completion details
* @return \stdClass
* @return array
*/
public function get_activities($cmids, $withcompletiondetails = false) {
$moduleinfo = get_fast_modinfo($this->courseid);
@@ -96,34 +96,43 @@ class manager {
// Get activity completion information.
if ($moduleobject->canmanage) {
$moduleobject->completionstatus = $this->get_completion_detail($mod); // This is a placeholder only. Must be replaced later.
$moduleobject->completionstatus = $this->get_completion_detail($mod);
} else {
$moduleobject->completionstatus = ['icon' => null, 'string' => null];
}
$activities[] = $moduleobject;
}
return (object)['activities' => $activities];
return $activities;
}
private function get_completion_detail(\cm_info $mod) {
/**
* Get completion information on the selected module or module type
*
* @param cm_info|stdClass $mod either instance of cm_info (with 'customcompletionrules' in customdata) or
* object with fields ->completion, ->completionview, ->completionexpected, ->completionusegrade
* and ->customdata['customcompletionrules']
* @return array
*/
private function get_completion_detail($mod) {
global $OUTPUT;
$strings = [];
switch ($mod->completion) {
case 0:
case COMPLETION_TRACKING_NONE:
$strings['string'] = get_string('none');
break;
case 1:
case COMPLETION_TRACKING_MANUAL:
$strings['string'] = get_string('manual');
$strings['icon'] = $OUTPUT->pix_url('i/completion-manual-enabled')->out();
break;
case 2:
case COMPLETION_TRACKING_AUTOMATIC:
$strings['string'] = get_string('withconditions');
// Get the descriptions for all the active completion rules for the module.
if ($ruledescriptions = $mod->get_completion_active_rule_descriptions()) {
if ($ruledescriptions = $this->get_completion_active_rule_descriptions($mod)) {
foreach ($ruledescriptions as $ruledescription) {
$strings['string'] .= \html_writer::empty_tag('br') . $ruledescription;
}
@@ -139,8 +148,44 @@ class manager {
return $strings;
}
/**
* Get the descriptions for all active conditional completion rules for the current module.
*
* @param cm_info|stdClass $moduledata either instance of cm_info (with 'customcompletionrules' in customdata) or
* object with fields ->completion, ->completionview, ->completionexpected, ->completionusegrade
* and ->customdata['customcompletionrules']
* @return array $activeruledescriptions an array of strings describing the active completion rules.
*/
protected function get_completion_active_rule_descriptions($moduledata) {
$activeruledescriptions = [];
// Generate the description strings for the core conditional completion rules (if set).
if (!empty($moduledata->completionview)) {
$activeruledescriptions[] = get_string('completionview_desc', 'core_completion');
}
if ($moduledata instanceof cm_info && !is_null($moduledata->completiongradeitemnumber) ||
($moduledata instanceof stdClass && !empty($moduledata->completionusegrade))) {
$activeruledescriptions[] = get_string('completionusegrade_desc', 'core_completion');
}
// Now, ask the module to provide descriptions for its custom conditional completion rules.
if ($customruledescriptions = component_callback($moduledata->modname,
'get_completion_active_rule_descriptions', [$moduledata])) {
$activeruledescriptions = array_merge($activeruledescriptions, $customruledescriptions);
}
if (!empty($moduledata->completionexpected)) {
$activeruledescriptions[] = get_string('completionexpecteddesc', 'core_completion',
userdate($moduledata->completionexpected));
}
return $activeruledescriptions;
}
public function get_activities_and_resources() {
global $DB, $OUTPUT;
global $DB, $OUTPUT, $CFG;
require_once($CFG->dirroot.'/course/lib.php');
// Get enabled activities and resources.
$modules = $DB->get_records('modules', ['visible' => 1], 'name ASC');
$data = new stdClass();
@@ -150,9 +195,16 @@ class manager {
// Add icon information.
$data->modules = array_values($modules);
$coursecontext = context_course::instance($this->courseid);
$canmanage = has_capability('moodle/course:manageactivities', $coursecontext);
$course = get_course($this->courseid);
foreach ($data->modules as $module) {
$module->icon = $OUTPUT->pix_url('icon', $module->name)->out();
$module->formatedname = format_string(get_string('pluginname', 'mod_' . $module->name), true, ['context' => $coursecontext]);
$module->formattedname = format_string(get_string('modulenameplural', 'mod_' . $module->name),
true, ['context' => $coursecontext]);
$module->canmanage = $canmanage && \course_allowed_module($course, $module->name);
$defaults = self::get_default_completion($course, $module, false);
$defaults->modname = $module->name;
$module->completionstatus = $this->get_completion_detail($defaults);
}
return $data;
@@ -186,7 +238,7 @@ class manager {
* Applies completion from the bulk edit form to all selected modules
*
* @param stdClass $data data received from the core_completion_bulkedit_form
* @param bool $updateinstance if we need to update the instance tables of the module (i.e. 'assign', 'forum', etc.) -
* @param bool $updateinstances if we need to update the instance tables of the module (i.e. 'assign', 'forum', etc.) -
* if no module-specific completion rules were added to the form, update of the module table is not needed.
*/
public function apply_completion($data, $updateinstances) {
@@ -267,4 +319,93 @@ class manager {
return true;
}
/**
* Saves default completion from edit form to all selected module types
*
* @param stdClass $data data received from the core_completion_bulkedit_form
* @param bool $updatecustomrules if we need to update the custom rules of the module -
* if no module-specific completion rules were added to the form, update of the module table is not needed.
*/
public function apply_default_completion($data, $updatecustomrules) {
global $DB;
$courseid = $data->id;
$coursecontext = context_course::instance($courseid);
if (!$modids = $data->modids) {
return;
}
$defaults = ['completion' => COMPLETION_DISABLED, 'completionview' => COMPLETION_VIEW_NOT_REQUIRED,
'completionexpected' => 0, 'completionusegrade' => 0];
$data = (array)$data;
if ($updatecustomrules) {
$customdata = array_diff_key($data, $defaults);
$data['customrules'] = $customdata ? json_encode($customdata) : null;
$defaults['customrules'] = null;
}
$data = array_intersect_key($data, $defaults);
// Get names of the affected modules.
list($modidssql, $params) = $DB->get_in_or_equal($modids);
$params[] = 1;
$modules = $DB->get_records_select_menu('modules', 'id ' . $modidssql . ' and visible = ?', $params, '', 'id, name');
foreach ($modids as $modid) {
if (!array_key_exists($modid, $modules)) {
continue;
}
if ($defaultsid = $DB->get_field('course_completion_defaults', 'id', ['course' => $courseid, 'module' => $modid])) {
$DB->update_record('course_completion_defaults', $data + ['id' => $defaultsid]);
} else {
$defaultsid = $DB->insert_record('course_completion_defaults', $data + ['course' => $courseid, 'module' => $modid]);
}
// Trigger event.
\core\event\completion_defaults_updated::create([
'objectid' => $defaultsid,
'context' => $coursecontext,
'other' => ['modulename' => $modules[$modid]],
])->trigger();
// Add notification.
\core\notification::add(get_string('defaultcompletionupdated', 'completion',
get_string("modulenameplural", $modules[$modid])), \core\notification::SUCCESS);
}
}
/**
* Returns default completion rules for given module type in the given course
*
* @param stdClass $course
* @param stdClass $module
* @param bool $flatten if true all module custom completion rules become properties of the same object,
* otherwise they can be found as array in ->customdata['customcompletionrules']
* @return stdClass
*/
public static function get_default_completion($course, $module, $flatten = true) {
global $DB, $CFG;
if ($data = $DB->get_record('course_completion_defaults', ['course' => $course->id, 'module' => $module->id],
'completion, completionview, completionexpected, completionusegrade, customrules')) {
if ($data->customrules && ($customrules = @json_decode($data->customrules, true))) {
if ($flatten) {
foreach ($customrules as $key => $value) {
$data->$key = $value;
}
} else {
$data->customdata['customcompletionrules'] = $customrules;
}
}
unset($data->customrules);
} else {
$data = new stdClass();
$data->completion = COMPLETION_TRACKING_NONE;
if ($CFG->completiondefault) {
$completion = new \completion_info(get_fast_modinfo($course->id)->get_course());
if ($completion->is_enabled() && plugin_supports('mod', $module->name, FEATURE_MODEDIT_DEFAULT_COMPLETION, true)) {
$data->completion = COMPLETION_TRACKING_MANUAL;
}
}
}
return $data;
}
}
+3 -2
View File
@@ -52,7 +52,8 @@ class core_completion_bulk_update_testcase extends advanced_testcase {
'choice-2' => ['choice', ['completion' => COMPLETION_TRACKING_MANUAL]],
'data-1' => ['data', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'data-2' => ['data', ['completion' => COMPLETION_TRACKING_MANUAL]],
'feedback-1' => ['feedback', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 0, 'completionsubmit' => 1]],
'feedback-1' => ['feedback', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 0,
'completionsubmit' => 1]],
'feedback-2' => ['feedback', ['completion' => COMPLETION_TRACKING_MANUAL]],
'folder-1' => ['folder', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'folder-2' => ['folder', ['completion' => COMPLETION_TRACKING_MANUAL]],
@@ -210,7 +211,7 @@ class core_completion_bulk_update_testcase extends advanced_testcase {
* Use bulk completion edit for updating multiple modules
*
* @dataProvider bulk_form_submit_multiple_provider
* @param array $data
* @param array $providerdata
*/
public function test_bulk_form_submit_multiple($providerdata) {
global $DB;
+1 -1
View File
@@ -70,7 +70,7 @@ $renderer = $PAGE->get_renderer('core_course', 'bulk_activity_completion');
// Print the form.
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('editcoursecompletionsettings', 'core_completion'));
echo $OUTPUT->heading(get_string('bulkactivitycompletion', 'completion'));
echo $renderer->navigation($id, 'bulkcompletion');
@@ -76,9 +76,45 @@ class core_course_bulk_activity_completion_renderer extends plugin_renderer_base
return parent::render_from_template('core_course/defaultactivitycompletion', $data);
}
public function activities_list($data) {
return html_writer::div(get_string('affectedactivities', 'completion', count($data->activities))).
parent::render_from_template('core_course/activityinstance', $data);
/**
* Renders the form for bulk editing activities completion
*
* @param moodleform $form
* @param array $activities
* @return string
*/
public function edit_bulk_completion($form, $activities) {
ob_start();
$form->display();
$formhtml = ob_get_contents();
ob_end_clean();
$data = (object)[
'form' => $formhtml,
'activities' => array_values($activities),
'activitiescount' => count($activities),
];
return parent::render_from_template('core_course/editbulkactivitycompletion', $data);
}
/**
* Renders the form for editing default completion
*
* @param moodleform $form
* @param array $modules
* @return string
*/
public function edit_default_completion($form, $modules) {
ob_start();
$form->display();
$formhtml = ob_get_contents();
ob_end_clean();
$data = (object)[
'form' => $formhtml,
'modules' => array_values($modules),
'modulescount' => count($modules),
];
return parent::render_from_template('core_course/editdefaultcompletion', $data);
}
}
+2 -4
View File
@@ -28,9 +28,7 @@ require_once(__DIR__.'/../config.php');
require_once($CFG->dirroot.'/course/lib.php');
require_once($CFG->libdir.'/completionlib.php');
$id = required_param('id', PARAM_INT); // course id
// @TODO: Change this to module IDs.
$cmids = optional_param_array('cmid', [], PARAM_INT);
$id = required_param('id', PARAM_INT); // Course id.
// Perform some basic access control checks.
if ($id) {
@@ -67,7 +65,7 @@ $renderer = $PAGE->get_renderer('core_course', 'bulk_activity_completion');
// Print the form.
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('editcoursecompletionsettings', 'core_completion'));
echo $OUTPUT->heading(get_string('defaultcompletion', 'completion'));
echo $renderer->navigation($id, 'defaultcompletion');
+5 -27
View File
@@ -559,34 +559,12 @@ class dndupload_ajax_processor {
*/
protected function create_course_module() {
global $CFG;
require_once($CFG->dirroot.'/course/modlib.php');
list($module, $context, $cw, $cm, $data) =
prepare_new_moduleinfo_data($this->course, $this->module->name, $this->section);
if (!course_allowed_module($this->course, $this->module->name)) {
throw new coding_exception("The module {$this->module->name} is not allowed to be added to this course");
}
$this->cm = new stdClass();
$this->cm->course = $this->course->id;
$this->cm->section = $this->section;
$this->cm->module = $this->module->id;
$this->cm->modulename = $this->module->name;
$this->cm->instance = 0; // This will be filled in after we create the instance.
$this->cm->visible = 1;
$this->cm->groupmode = $this->course->groupmode;
$this->cm->groupingid = $this->course->defaultgroupingid;
// Set the correct default for completion tracking.
$this->cm->completion = COMPLETION_TRACKING_NONE;
$completion = new completion_info($this->course);
if ($completion->is_enabled() && $CFG->completiondefault) {
if (plugin_supports('mod', $this->cm->modulename, FEATURE_MODEDIT_DEFAULT_COMPLETION, true)) {
$this->cm->completion = COMPLETION_TRACKING_MANUAL;
}
}
if (!$this->cm->id = add_course_module($this->cm)) {
throw new coding_exception("Unable to create the course module");
}
$this->cm->coursemodule = $this->cm->id;
$data->coursemodule = $data->id = add_course_module($data);
$this->cm = $data;
}
/**
+3 -5
View File
@@ -22,7 +22,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
include __DIR__ . "/../config.php";
require_once(__DIR__ . "/../config.php");
require_once($CFG->libdir . '/completionlib.php');
$courseid = required_param('id', PARAM_INT);
@@ -68,13 +68,11 @@ if ($form->is_cancelled()) {
$renderer = $PAGE->get_renderer('core_course', 'bulk_activity_completion');
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('editcoursecompletionsettings', 'core_completion'));
echo $OUTPUT->heading(get_string('bulkactivitycompletion', 'completion'));
echo $renderer->navigation($course->id, 'bulkcompletion');
$form->display();
echo $renderer->activities_list($manager->get_activities(array_keys($cms)));
echo $renderer->edit_bulk_completion($form, $manager->get_activities(array_keys($cms)));
echo $OUTPUT->footer();
+75
View File
@@ -0,0 +1,75 @@
<?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/>.
/**
* Bulk activity completion selection
*
* @package core_completion
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . "/../config.php");
require_once($CFG->libdir . '/completionlib.php');
$courseid = required_param('id', PARAM_INT);
$modids = optional_param_array('modids', [], PARAM_INT);
$course = get_course($courseid);
require_login($course);
navigation_node::override_active_url(new moodle_url('/course/completion.php', array('id' => $course->id)));
$PAGE->set_url(new moodle_url('/course/editdefaultcompletion.php', ['id' => $courseid]));
$PAGE->set_title($course->shortname);
$PAGE->set_heading($course->fullname);
$PAGE->set_pagelayout('admin');
require_capability('moodle/course:update', context_course::instance($course->id));
// Prepare list of selected modules.
$manager = new \core_completion\manager($course->id);
$allmodules = $manager->get_activities_and_resources();
$modules = [];
foreach ($allmodules->modules as $module) {
if ($module->canmanage && in_array($module->id, $modids)) {
$modules[$module->id] = $module;
}
}
$returnurl = new moodle_url('/course/defaultcompletion.php', ['id' => $course->id]);
if (empty($modules)) {
redirect($returnurl);
}
$form = new core_completion_defaultedit_form(null, ['course' => $course, 'modules' => $modules]);
if ($form->is_cancelled()) {
redirect($returnurl);
} else if ($data = $form->get_data()) {
$manager->apply_default_completion($data, $form->has_custom_completion_rules());
redirect($returnurl);
}
$renderer = $PAGE->get_renderer('core_course', 'bulk_activity_completion');
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('defaultcompletion', 'completion'));
echo $renderer->navigation($course->id, 'defaultcompletion');
echo $renderer->edit_default_completion($form, $modules);
echo $OUTPUT->footer();
+6
View File
@@ -760,6 +760,12 @@ function prepare_new_moduleinfo_data($course, $modulename, $section) {
$data->instance = '';
$data->coursemodule = '';
// Apply completion defaults.
$defaults = \core_completion\manager::get_default_completion($course, $module);
foreach ($defaults as $key => $value) {
$data->$key = $value;
}
if (plugin_supports('mod', $data->modulename, FEATURE_MOD_INTRO, true)) {
$draftid_editor = file_get_submitted_draft_itemid('introeditor');
file_prepare_draft_area($draftid_editor, null, null, null, null, array('subdirs'=>true));
+2 -2
View File
@@ -2,7 +2,7 @@
require_once ($CFG->libdir.'/formslib.php');
require_once($CFG->libdir.'/completionlib.php');
require_once($CFG->libdir.'/gradelib.php');
require_once ($CFG->libdir.'/plagiarismlib.php');
require_once($CFG->libdir.'/plagiarismlib.php');
/**
* This class adds extra methods to form wrapper specific to be used for module
@@ -931,7 +931,7 @@ abstract class moodleform_mod extends moodleform {
// If the 'show description' feature is enabled, this checkbox appears below the intro.
// We want to hide that when using the singleactivity course format because it is confusing.
if ($this->_features->showdescription && $this->courseformat->has_view_page()) {
$mform->addElement('checkbox', 'showdescription', get_string('showdescription'));
$mform->addElement('advcheckbox', 'showdescription', get_string('showdescription'));
$mform->addHelpButton('showdescription', 'showdescription');
}
}
+3 -13
View File
@@ -24,20 +24,10 @@
"activities": [{
"cmid": "4",
"modname": "Test activity",
"icon": {
"attributes": [
{"name": "src", "value": "https://raw.githubusercontent.com/moodle/moodle/master/pix/t/check.png"},
{"name": "alt", "value": "Activity icon"}
]
},
"icon": "https://raw.githubusercontent.com/moodle/moodle/master/mod/feedback/pix/icon.png",
"completionstatus": {
"string": "Manual",
"icon": {
"attributes": [
{"name": "src", "value": "https://raw.githubusercontent.com/moodle/moodle/master/pix/t/check.png"},
{"name": "alt", "value": "Completion icon"}
]
}
"icon": "https://raw.githubusercontent.com/moodle/moodle/master/pix/i/completion-manual-enabled.png"
}
}]
}
@@ -50,7 +40,7 @@
{{#canmanage}}
<input type="checkbox" class="m-r-1" name="cmid[]" data-section="{{sectionnumber}}" value="{{cmid}}" aria-label="{{#str}}checkactivity, completion, {{modname}}{{/str}}">
{{/canmanage}}
<a href={{url}}>
<a href="{{url}}">
<img src="{{icon}}" class="iconlarge activityicon" alt=" " role="presentation" />
<span class="instancename">{{modname}}</span>
</a>
@@ -29,20 +29,10 @@
"activities": [{
"cmid": "4",
"modname": "Test activity",
"icon": {
"attributes": [
{"name": "src", "value": "https://raw.githubusercontent.com/moodle/moodle/master/pix/t/check.png"},
{"name": "alt", "value": "module icon"}
]
},
"icon": "https://raw.githubusercontent.com/moodle/moodle/master/mod/feedback/pix/icon.png",
"completionstatus": {
"string": "Manual",
"icon": {
"attributes": [
{"name": "src", "value": "https://raw.githubusercontent.com/moodle/moodle/master/pix/t/check.png"},
{"name": "alt", "value": "completion icon"}
]
}
"icon": "https://raw.githubusercontent.com/moodle/moodle/master/pix/i/completion-manual-enabled.png"
}
}]
}]
@@ -25,12 +25,12 @@
"sesskey": "AAAAAA",
"modules": [{
"id": "10",
"formatedname": "Assignment",
"icon": {
"attributes": [
{"name": "src", "value": "https://raw.githubusercontent.com/moodle/moodle/master/pix/t/check.png"},
{"name": "alt", "value": "Assignment icon"}
]
"formattedname": "Assignment",
"canmanage": true,
"icon": "https://raw.githubusercontent.com/moodle/moodle/master/mod/assign/pix/icon.png",
"completionstatus": {
"string": "Manual",
"icon": "https://raw.githubusercontent.com/moodle/moodle/master/pix/i/completion-manual-enabled.png"
}
}]
}
@@ -39,7 +39,7 @@
<div class="row m-b-2">
<div class="col">{{#str}}bulkactivitydetail, moodle{{/str}}</div>
</div>
<form method="post" action="defaultcompletion.php" class="mform" id="theform">
<form method="post" action="editdefaultcompletion.php" class="mform" id="theform">
<div class="row m-b-2">
<div class="col">
<input type="submit" value="{{#str}}edit{{/str}}" class="btn btn-primary" name="submitbutton" aria-label="{{#str}}updateactivities, completion{{/str}}" />
@@ -58,15 +58,30 @@
</div>
<div class="modules">
{{#modules}}
{{#canmanage}}
<div class="module-section m-b-1">
<div class="row m-b-1">
<div class="col-sm-12">
<input type="checkbox" class="m-r-1" name="modids[]" value="{{id}}" aria-label="{{#str}}checkactivity, completion, {{formatedname}}{{/str}}">
<img src={{icon}} />
<span>{{formatedname}}</span>
<span>{{formattedname}}</span>
</div>
<div class="activity-completionstatus col-sm-6">
<div class="col-sm-1">
{{#completionstatus.icon}}
<img src="{{completionstatus.icon}}" class="m-r-2">
{{/completionstatus.icon}}
{{^completionstatus.icon}}
<span class="m-r-3"></span>
{{/completionstatus.icon}}
</div>
<div class="col-sm-11">
<span class="text-muted muted">{{{completionstatus.string}}}</span>
</div>
</div>
</div>
</div>
{{/canmanage}}
{{/modules}}
</div>
<input type="hidden" name="id" value="{{courseid}}" />
@@ -0,0 +1,40 @@
{{!
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/editbulkactivitycompletion
Edit default module completion screen
Example context (json):
{
"form": "<form><p>Placeholder for the form</p></form>",
"activitiescount": 1,
"activities": [{
"cmid": "4",
"modname": "Test activity",
"icon": "https://raw.githubusercontent.com/moodle/moodle/master/mod/feedback/pix/icon.png"
}]
}
}}
<p>{{#str}}modifybulkactions, completion{{/str}}</p>
{{{form}}}
<div class="container-fluid">
<p>{{#str}}affectedactivities, completion, {{activitiescount}}{{/str}}</p>
<div class="activities">
{{> core_course/activityinstance}}
</div>
</div>
@@ -0,0 +1,47 @@
{{!
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/editdefaultcompletion
Edit default module completion screen
Example context (json):
{
"form": "<form><p>Placeholder for the form</p></form>",
"modulescount": 1,
"modules": [{
"id": "10",
"formattedname": "Assignment",
"icon": "https://raw.githubusercontent.com/moodle/moodle/master/mod/assign/pix/icon.png"
}]
}
}}
<p>{{#str}}modifybulkactions, completion{{/str}}</p>
{{{form}}}
<p>{{#str}}affectedactivities, completion, {{modulescount}}{{/str}}</p>
<div class="container-fluid">
<div class="modules m-b-1">
<div class="row m-b-1">
{{#modules}}
<div class="col-sm-2">
<img src="{{icon}}" class="m-r-1 m-b-1" alt=" " />
<span>{{{formattedname}}}</span>
</div>
{{/modules}}
</div>
</div>
</div>
+1
View File
@@ -62,6 +62,7 @@ class core_course_modlib_testcase extends advanced_testcase {
$expecteddata->instance = '';
$expecteddata->coursemodule = '';
$expecteddata->advancedgradingmethod_submissions = ''; // Not grading methods enabled by default.
$expecteddata->completion = 0;
// Unset untestable.
unset($data->introeditor);
unset($data->_advancedgradingdata);
+3
View File
@@ -118,6 +118,7 @@ $string['datepassed'] = 'Date passed';
$string['days'] = 'Days';
$string['daysoftotal'] = '{$a->days} of {$a->total}';
$string['defaultcompletion'] = 'Default activity completion';
$string['defaultcompletionupdated'] = 'Updated default completion for future <b>{$a}</b> in this course';
$string['deletecompletiondata'] = 'Delete completion data';
$string['dependencies'] = 'Dependencies';
$string['dependenciescompleted'] = 'Completion of other courses';
@@ -137,6 +138,7 @@ $string['err_system'] = 'An internal error occurred in the completion system. (S
$string['eventcoursecompleted'] = 'Course completed';
$string['eventcoursecompletionupdated'] = 'Course completion updated';
$string['eventcoursemodulecompletionupdated'] = 'Course module completion updated';
$string['eventdefaultcompletionupdated'] = 'Default for course module completion updated';
$string['excelcsvdownload'] = 'Download in Excel-compatible format (.csv)';
$string['fraction'] = 'Fraction';
$string['graderequired'] = 'Required course grade';
@@ -149,6 +151,7 @@ $string['manualselfcompletionnote'] = 'Note: The self completion block should be
$string['markcomplete'] = 'Mark complete';
$string['markedcompleteby'] = 'Marked complete by {$a}';
$string['markingyourselfcomplete'] = 'Marking yourself complete';
$string['modifybulkactions'] = 'Modify the actions you wish to bulk edit';
$string['moredetails'] = 'More details';
$string['nocriteriaset'] = 'No completion criteria set for this course';
$string['notcompleted'] = 'Not completed';
@@ -0,0 +1,101 @@
<?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/>.
/**
* Default completion for activity in a course updated event
*
* @package core
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\event;
defined('MOODLE_INTERNAL') || die();
/**
* Default completion for activity in a course updated event
*
* @package core
* @since Moodle 3.3
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class completion_defaults_updated extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = 'course_completion_defaults';
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventdefaultcompletionupdated', 'completion');
}
/**
* Returns relevant URL.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/course/defaultcompletion.php', array('id' => $this->courseid));
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' updated the default completion for module " .
"'{$this->other['modulename']}' in course with id '$this->courseid'.";
}
/**
* Custom validation.
*
* @throws \coding_exception
*/
protected function validate_data() {
parent::validate_data();
if ($this->contextlevel != CONTEXT_COURSE) {
throw new \coding_exception('Context passed must be course context.');
}
if (!isset($this->other['modulename'])) {
throw new \coding_exception('The \'modulename\' value must be set in other.');
}
}
/**
* This is used when restoring course logs where it is required that we
* map the objectid to it's new value in the new course.
*
* @return array
*/
public static function get_objectid_mapping() {
parent::get_objectid_mapping();
return array('db' => 'course_completion_defaults', 'restore' => 'course_completion_defaults');
}
}
+21 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="lib/db" VERSION="20170310" COMMENT="XMLDB file for core Moodle tables"
<XMLDB PATH="lib/db" VERSION="20170316" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
@@ -3539,5 +3539,25 @@
<KEY NAME="uniqinternal" TYPE="unique" FIELDS="issuerid, internalfield"/>
</KEYS>
</TABLE>
<TABLE NAME="course_completion_defaults" COMMENT="Default settings for activities completion">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="module" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="completion" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="completionview" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="completionusegrade" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="completionexpected" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="customrules" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="module" TYPE="foreign" FIELDS="module" REFTABLE="modules" REFFIELDS="id"/>
<KEY NAME="course" TYPE="foreign" FIELDS="course" REFTABLE="course" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="coursemodule" UNIQUE="true" FIELDS="course, module"/>
</INDEXES>
</TABLE>
</TABLES>
</XMLDB>
+31
View File
@@ -2803,5 +2803,36 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2017040700.04);
}
if ($oldversion < 2017041801.00) {
// Define table course_completion_defaults to be created.
$table = new xmldb_table('course_completion_defaults');
// Adding fields to table course_completion_defaults.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('module', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('completion', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
$table->add_field('completionview', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
$table->add_field('completionusegrade', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
$table->add_field('completionexpected', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('customrules', XMLDB_TYPE_TEXT, null, null, null, null, null);
// Adding keys to table course_completion_defaults.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('module', XMLDB_KEY_FOREIGN, array('module'), 'modules', array('id'));
$table->add_key('course', XMLDB_KEY_FOREIGN, array('course'), 'course', array('id'));
// Adding indexes to table course_completion_defaults.
$table->add_index('coursemodule', XMLDB_INDEX_UNIQUE, array('course', 'module'));
// Conditionally launch create table for course_completion_defaults.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
upgrade_main_savepoint(true, 2017041801.00);
}
return true;
}
-28
View File
@@ -2049,34 +2049,6 @@ class cm_info implements IteratorAggregate {
$this->call_mod_function('cm_info_view');
$this->state = self::STATE_VIEW;
}
/**
* Get the descriptions for all active conditional completion rules for the current module.
*
* @return array $activeruledescriptions an array of strings describing the active completion rules.
*/
public function get_completion_active_rule_descriptions() {
$activeruledescriptions = [];
// Generate the description strings for the core conditional completion rules (if set).
if (!empty($this->completionview)) {
$activeruledescriptions[] = get_string('completionview_desc', 'core_completion');
}
if (!empty($this->completionexpected)) {
$activeruledescriptions[] = get_string('completionexpecteddesc', 'core_completion',
userdate($this->completionexpected));
}
if (!is_null($this->completiongradeitemnumber)) {
$activeruledescriptions[] = get_string('completionusegrade_desc', 'core_completion');
}
// Now, ask the module to provide descriptions for its custom conditional completion rules.
if ($customruledescriptions = component_callback($this->modname, 'get_completion_active_rule_descriptions', [$this])) {
$activeruledescriptions = array_merge($activeruledescriptions, $customruledescriptions);
}
return $activeruledescriptions;
}
}
+3
View File
@@ -4986,6 +4986,9 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
// We have tried to delete everything the nice way - now let's force-delete any remaining module data.
// Delete completion defaults.
$DB->delete_records("course_completion_defaults", array("course" => $courseid));
// Remove all data from availability and completion tables that is associated
// with course-modules belonging to this course. Note this is done even if the
// features are not enabled now, in case they were enabled previously.
+2 -2
View File
@@ -481,12 +481,12 @@ function assign_get_coursemodule_info($coursemodule) {
/**
* Callback which returns human-readable strings describing the active completion custom rules for the module instance.
*
* @param object $cm the cm_info object.
* @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
* @return array $descriptions the array of descriptions for the custom rules.
*/
function mod_assign_get_completion_active_rule_descriptions($cm) {
// Values will be present in cm_info, and we assume these are up to date.
if (!$cm instanceof cm_info || !isset($cm->customdata['customcompletionrules'])
if (empty($cm->customdata['customcompletionrules'])
|| $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
return [];
}
+2 -2
View File
@@ -1260,12 +1260,12 @@ function choice_get_coursemodule_info($coursemodule) {
/**
* Callback which returns human-readable strings describing the active completion custom rules for the module instance.
*
* @param object $cm the cm_info object.
* @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
* @return array $descriptions the array of descriptions for the custom rules.
*/
function mod_choice_get_completion_active_rule_descriptions($cm) {
// Values will be present in cm_info, and we assume these are up to date.
if (!$cm instanceof cm_info || !isset($cm->customdata['customcompletionrules'])
if (empty($cm->customdata['customcompletionrules'])
|| $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
return [];
}
+1 -1
View File
@@ -132,7 +132,7 @@ class mod_choice_mod_form extends moodleform_mod {
*
* @param stdClass $data passed by reference
*/
function data_postprocessing(&$data) {
public function data_postprocessing(&$data) {
parent::data_postprocessing($data);
// Set up completion section even if checkbox is not ticked
if (!empty($data->completionunlocked)) {
+2 -2
View File
@@ -3487,12 +3487,12 @@ function feedback_get_coursemodule_info($coursemodule) {
/**
* Callback which returns human-readable strings describing the active completion custom rules for the module instance.
*
* @param object $cm the cm_info object.
* @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
* @return array $descriptions the array of descriptions for the custom rules.
*/
function mod_feedback_get_completion_active_rule_descriptions($cm) {
// Values will be present in cm_info, and we assume these are up to date.
if (!$cm instanceof cm_info || !isset($cm->customdata['customcompletionrules'])
if (empty($cm->customdata['customcompletionrules'])
|| $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
return [];
}
+2 -2
View File
@@ -8265,12 +8265,12 @@ function forum_get_coursemodule_info($coursemodule) {
/**
* Callback which returns human-readable strings describing the active completion custom rules for the module instance.
*
* @param object $cm the cm_info object.
* @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
* @return array $descriptions the array of descriptions for the custom rules.
*/
function mod_forum_get_completion_active_rule_descriptions($cm) {
// Values will be present in cm_info, and we assume these are up to date.
if (!$cm instanceof cm_info || !isset($cm->customdata['customcompletionrules'])
if (empty($cm->customdata['customcompletionrules'])
|| $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
return [];
}
+1 -1
View File
@@ -300,7 +300,7 @@ class mod_forum_mod_form extends moodleform_mod {
*
* @param stdClass $data passed by reference
*/
function data_postprocessing(&$data) {
public function data_postprocessing(&$data) {
parent::data_postprocessing($data);
// Turn off completion settings if the checkboxes aren't ticked
if (!empty($data->completionunlocked)) {
+2 -2
View File
@@ -4235,12 +4235,12 @@ function glossary_get_coursemodule_info($coursemodule) {
/**
* Callback which returns human-readable strings describing the active completion custom rules for the module instance.
*
* @param object $cm the cm_info object.
* @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
* @return array $descriptions the array of descriptions for the custom rules.
*/
function mod_glossary_get_completion_active_rule_descriptions($cm) {
// Values will be present in cm_info, and we assume these are up to date.
if (!$cm instanceof cm_info || !isset($cm->customdata['customcompletionrules'])
if (empty($cm->customdata['customcompletionrules'])
|| $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
return [];
}
+1 -1
View File
@@ -210,7 +210,7 @@ class mod_glossary_mod_form extends moodleform_mod {
*
* @param stdClass $data passed by reference
*/
function data_postprocessing(&$data) {
public function data_postprocessing(&$data) {
parent::data_postprocessing($data);
if (!empty($data->completionunlocked)) {
// Turn off completion settings if the checkboxes aren't ticked
+2 -2
View File
@@ -1679,12 +1679,12 @@ function lesson_get_coursemodule_info($coursemodule) {
/**
* Callback which returns human-readable strings describing the active completion custom rules for the module instance.
*
* @param object $cm the cm_info object.
* @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
* @return array $descriptions the array of descriptions for the custom rules.
*/
function mod_lesson_get_completion_active_rule_descriptions($cm) {
// Values will be present in cm_info, and we assume these are up to date.
if (!$cm instanceof cm_info || !isset($cm->customdata['customcompletionrules'])
if (empty($cm->customdata['customcompletionrules'])
|| $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
return [];
}
+2 -2
View File
@@ -2197,12 +2197,12 @@ function quiz_get_coursemodule_info($coursemodule) {
/**
* Callback which returns human-readable strings describing the active completion custom rules for the module instance.
*
* @param object $cm the cm_info object.
* @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
* @return array $descriptions the array of descriptions for the custom rules.
*/
function mod_quiz_get_completion_active_rule_descriptions($cm) {
// Values will be present in cm_info, and we assume these are up to date.
if (!$cm instanceof cm_info || !isset($cm->customdata['customcompletionrules'])
if (empty($cm->customdata['customcompletionrules'])
|| $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
return [];
}
+2 -2
View File
@@ -1681,12 +1681,12 @@ function scorm_get_coursemodule_info($coursemodule) {
/**
* Callback which returns human-readable strings describing the active completion custom rules for the module instance.
*
* @param object $cm the cm_info object.
* @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
* @return array $descriptions the array of descriptions for the custom rules.
*/
function mod_scorm_get_completion_active_rule_descriptions($cm) {
// Values will be present in cm_info, and we assume these are up to date.
if (!$cm instanceof cm_info || !isset($cm->customdata['customcompletionrules'])
if (empty($cm->customdata['customcompletionrules'])
|| $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
return [];
}
+2 -2
View File
@@ -1179,12 +1179,12 @@ function survey_get_coursemodule_info($coursemodule) {
/**
* Callback which returns human-readable strings describing the active completion custom rules for the module instance.
*
* @param object $cm the cm_info object.
* @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
* @return array $descriptions the array of descriptions for the custom rules.
*/
function mod_survey_get_completion_active_rule_descriptions($cm) {
// Values will be present in cm_info, and we assume these are up to date.
if (!$cm instanceof cm_info || !isset($cm->customdata['customcompletionrules'])
if (empty($cm->customdata['customcompletionrules'])
|| $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
return [];
}
+1 -1
View File
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2017041800.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2017041801.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.