From 7f53e8aa22d555ba5f94530b7fc04a4e95f35c98 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Fri, 17 Mar 2017 15:24:59 +0800 Subject: [PATCH] MDL-58267 completion: allow to set default activity completion Part of MDL-58138 epic --- backup/moodle2/backup_course_task.class.php | 3 + backup/moodle2/backup_stepslib.php | 46 +++ backup/moodle2/restore_course_task.class.php | 3 + backup/moodle2/restore_stepslib.php | 77 +++++ completion/classes/bulkedit_form.php | 272 +-------------- completion/classes/defaultedit_form.php | 87 +++++ completion/classes/edit_base_form.php | 316 ++++++++++++++++++ completion/classes/manager.php | 167 ++++++++- completion/tests/bulk_update_test.php | 5 +- course/bulkcompletion.php | 2 +- .../bulk_activity_completion_renderer.php | 42 ++- course/defaultcompletion.php | 6 +- course/dnduploadlib.php | 32 +- course/editbulkcompletion.php | 8 +- course/editdefaultcompletion.php | 75 +++++ course/modlib.php | 6 + course/moodleform_mod.php | 4 +- course/templates/activityinstance.mustache | 16 +- .../templates/bulkactivitycompletion.mustache | 14 +- .../defaultactivitycompletion.mustache | 31 +- .../editbulkactivitycompletion.mustache | 40 +++ .../templates/editdefaultcompletion.mustache | 47 +++ course/tests/modlib_test.php | 1 + lang/en/completion.php | 3 + .../event/completion_defaults_updated.php | 101 ++++++ lib/db/install.xml | 22 +- lib/db/upgrade.php | 31 ++ lib/modinfolib.php | 28 -- lib/moodlelib.php | 3 + mod/assign/lib.php | 4 +- mod/choice/lib.php | 4 +- mod/choice/mod_form.php | 2 +- mod/feedback/lib.php | 4 +- mod/forum/lib.php | 4 +- mod/forum/mod_form.php | 2 +- mod/glossary/lib.php | 4 +- mod/glossary/mod_form.php | 2 +- mod/lesson/lib.php | 4 +- mod/quiz/lib.php | 4 +- mod/scorm/lib.php | 4 +- mod/survey/lib.php | 4 +- version.php | 2 +- 42 files changed, 1125 insertions(+), 407 deletions(-) create mode 100644 completion/classes/defaultedit_form.php create mode 100644 completion/classes/edit_base_form.php create mode 100644 course/editdefaultcompletion.php create mode 100644 course/templates/editbulkactivitycompletion.mustache create mode 100644 course/templates/editdefaultcompletion.mustache create mode 100644 lib/classes/event/completion_defaults_updated.php diff --git a/backup/moodle2/backup_course_task.class.php b/backup/moodle2/backup_course_task.class.php index 7cb258bf7c5..f9ae5dca8b6 100644 --- a/backup/moodle2/backup_course_task.class.php +++ b/backup/moodle2/backup_course_task.class.php @@ -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')); diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php index 83300f1c9a0..8855d58cedb 100644 --- a/backup/moodle2/backup_stepslib.php +++ b/backup/moodle2/backup_stepslib.php @@ -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; + + } +} diff --git a/backup/moodle2/restore_course_task.class.php b/backup/moodle2/restore_course_task.class.php index 1c07e9ccd8a..74bfa6c17c3 100644 --- a/backup/moodle2/restore_course_task.class.php +++ b/backup/moodle2/restore_course_task.class.php @@ -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; } diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index 5f4cf05685c..5a744e6d8d0 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -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); + } +} \ No newline at end of file diff --git a/completion/classes/bulkedit_form.php b/completion/classes/bulkedit_form.php index c83dc8a781c..7e92918d6fc 100644 --- a/completion/classes/bulkedit_form.php +++ b/completion/classes/bulkedit_form.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * 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; - } } \ No newline at end of file diff --git a/completion/classes/defaultedit_form.php b/completion/classes/defaultedit_form.php new file mode 100644 index 00000000000..7a7b5547f33 --- /dev/null +++ b/completion/classes/defaultedit_form.php @@ -0,0 +1,87 @@ +. + +/** + * 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); + } + } +} \ No newline at end of file diff --git a/completion/classes/edit_base_form.php b/completion/classes/edit_base_form.php new file mode 100644 index 00000000000..34e29ddcd6b --- /dev/null +++ b/completion/classes/edit_base_form.php @@ -0,0 +1,316 @@ +. + +/** + * 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; + } +} \ No newline at end of file diff --git a/completion/classes/manager.php b/completion/classes/manager.php index 5e30a660d5e..20c43d48fd0 100644 --- a/completion/classes/manager.php +++ b/completion/classes/manager.php @@ -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; + } } \ No newline at end of file diff --git a/completion/tests/bulk_update_test.php b/completion/tests/bulk_update_test.php index e6dff2793f7..2e660813bdf 100644 --- a/completion/tests/bulk_update_test.php +++ b/completion/tests/bulk_update_test.php @@ -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; diff --git a/course/bulkcompletion.php b/course/bulkcompletion.php index 2096b6f1c29..7f1c5602c1b 100644 --- a/course/bulkcompletion.php +++ b/course/bulkcompletion.php @@ -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'); diff --git a/course/classes/output/bulk_activity_completion_renderer.php b/course/classes/output/bulk_activity_completion_renderer.php index b6ab22cb587..e35eb88308d 100644 --- a/course/classes/output/bulk_activity_completion_renderer.php +++ b/course/classes/output/bulk_activity_completion_renderer.php @@ -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); + } } diff --git a/course/defaultcompletion.php b/course/defaultcompletion.php index 9f595e0bd2b..f02f1b8fadd 100644 --- a/course/defaultcompletion.php +++ b/course/defaultcompletion.php @@ -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'); diff --git a/course/dnduploadlib.php b/course/dnduploadlib.php index 843ea9a9fe0..c6e2c598c0a 100644 --- a/course/dnduploadlib.php +++ b/course/dnduploadlib.php @@ -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; } /** diff --git a/course/editbulkcompletion.php b/course/editbulkcompletion.php index 259a1979134..728565cf32a 100644 --- a/course/editbulkcompletion.php +++ b/course/editbulkcompletion.php @@ -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(); diff --git a/course/editdefaultcompletion.php b/course/editdefaultcompletion.php new file mode 100644 index 00000000000..7f29bbddb29 --- /dev/null +++ b/course/editdefaultcompletion.php @@ -0,0 +1,75 @@ +. + +/** + * 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(); + diff --git a/course/modlib.php b/course/modlib.php index 68ed661ad6a..d4cc82b3f9e 100644 --- a/course/modlib.php +++ b/course/modlib.php @@ -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)); diff --git a/course/moodleform_mod.php b/course/moodleform_mod.php index e37a231c3d6..c8ea479cf64 100644 --- a/course/moodleform_mod.php +++ b/course/moodleform_mod.php @@ -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'); } } diff --git a/course/templates/activityinstance.mustache b/course/templates/activityinstance.mustache index 326e9245c10..878dbfd7c66 100644 --- a/course/templates/activityinstance.mustache +++ b/course/templates/activityinstance.mustache @@ -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}} {{/canmanage}} - +  {{modname}} diff --git a/course/templates/bulkactivitycompletion.mustache b/course/templates/bulkactivitycompletion.mustache index 410827f49e4..33937a021d4 100644 --- a/course/templates/bulkactivitycompletion.mustache +++ b/course/templates/bulkactivitycompletion.mustache @@ -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" } }] }] diff --git a/course/templates/defaultactivitycompletion.mustache b/course/templates/defaultactivitycompletion.mustache index 0e12248bcdd..d4985ba9ce6 100644 --- a/course/templates/defaultactivitycompletion.mustache +++ b/course/templates/defaultactivitycompletion.mustache @@ -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 @@
{{#str}}bulkactivitydetail, moodle{{/str}}
-
+
@@ -58,15 +58,30 @@
{{#modules}} + {{#canmanage}}
- {{formatedname}} + {{formattedname}} +
+
+
+ {{#completionstatus.icon}} + + {{/completionstatus.icon}} + {{^completionstatus.icon}} + + {{/completionstatus.icon}} +
+
+ {{{completionstatus.string}}} +
+ {{/canmanage}} {{/modules}}
diff --git a/course/templates/editbulkactivitycompletion.mustache b/course/templates/editbulkactivitycompletion.mustache new file mode 100644 index 00000000000..01498c42cb3 --- /dev/null +++ b/course/templates/editbulkactivitycompletion.mustache @@ -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 . +}} +{{! + @template core_course/editbulkactivitycompletion + + Edit default module completion screen + + Example context (json): + { + "form": "

Placeholder for the form

", + "activitiescount": 1, + "activities": [{ + "cmid": "4", + "modname": "Test activity", + "icon": "https://raw.githubusercontent.com/moodle/moodle/master/mod/feedback/pix/icon.png" + }] + } +}} +

{{#str}}modifybulkactions, completion{{/str}}

+{{{form}}} +
+

{{#str}}affectedactivities, completion, {{activitiescount}}{{/str}}

+
+ {{> core_course/activityinstance}} +
+
diff --git a/course/templates/editdefaultcompletion.mustache b/course/templates/editdefaultcompletion.mustache new file mode 100644 index 00000000000..e5e2d252775 --- /dev/null +++ b/course/templates/editdefaultcompletion.mustache @@ -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 . +}} +{{! + @template core_course/editdefaultcompletion + + Edit default module completion screen + + Example context (json): + { + "form": "

Placeholder for the form

", + "modulescount": 1, + "modules": [{ + "id": "10", + "formattedname": "Assignment", + "icon": "https://raw.githubusercontent.com/moodle/moodle/master/mod/assign/pix/icon.png" + }] + } +}} +

{{#str}}modifybulkactions, completion{{/str}}

+{{{form}}} +

{{#str}}affectedactivities, completion, {{modulescount}}{{/str}}

+
+
+
+ {{#modules}} +
+  + {{{formattedname}}} +
+ {{/modules}} +
+
+
diff --git a/course/tests/modlib_test.php b/course/tests/modlib_test.php index f4b607c0488..2196333723c 100644 --- a/course/tests/modlib_test.php +++ b/course/tests/modlib_test.php @@ -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); diff --git a/lang/en/completion.php b/lang/en/completion.php index 130829fdfdc..d6fc8384bb9 100644 --- a/lang/en/completion.php +++ b/lang/en/completion.php @@ -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 {$a} 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'; diff --git a/lib/classes/event/completion_defaults_updated.php b/lib/classes/event/completion_defaults_updated.php new file mode 100644 index 00000000000..daa0bd36a27 --- /dev/null +++ b/lib/classes/event/completion_defaults_updated.php @@ -0,0 +1,101 @@ +. + +/** + * 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'); + } +} diff --git a/lib/db/install.xml b/lib/db/install.xml index 3b9525ddec3..39ab0c62304 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -3539,5 +3539,25 @@ + + + + + + + + + + + + + + + + + + + +
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index dd8444875fc..0bca99461bc 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -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; } diff --git a/lib/modinfolib.php b/lib/modinfolib.php index fca2b84b7f1..f7179b4f3a1 100644 --- a/lib/modinfolib.php +++ b/lib/modinfolib.php @@ -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; - } } diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 23314747c85..aefd1905a6f 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -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. diff --git a/mod/assign/lib.php b/mod/assign/lib.php index 96d21dca968..d63e2d23b46 100644 --- a/mod/assign/lib.php +++ b/mod/assign/lib.php @@ -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 []; } diff --git a/mod/choice/lib.php b/mod/choice/lib.php index 662b2a3476e..6211721c947 100644 --- a/mod/choice/lib.php +++ b/mod/choice/lib.php @@ -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 []; } diff --git a/mod/choice/mod_form.php b/mod/choice/mod_form.php index 5a2ce49c10d..fd872325aff 100644 --- a/mod/choice/mod_form.php +++ b/mod/choice/mod_form.php @@ -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)) { diff --git a/mod/feedback/lib.php b/mod/feedback/lib.php index c1f25ce06be..f800811edb3 100644 --- a/mod/feedback/lib.php +++ b/mod/feedback/lib.php @@ -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 []; } diff --git a/mod/forum/lib.php b/mod/forum/lib.php index a3fc2b58547..96e3985bb8e 100644 --- a/mod/forum/lib.php +++ b/mod/forum/lib.php @@ -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 []; } diff --git a/mod/forum/mod_form.php b/mod/forum/mod_form.php index cc3f51e2ab8..5fe40fd85f1 100644 --- a/mod/forum/mod_form.php +++ b/mod/forum/mod_form.php @@ -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)) { diff --git a/mod/glossary/lib.php b/mod/glossary/lib.php index 04ad29ef4ea..a977f5c7ba7 100644 --- a/mod/glossary/lib.php +++ b/mod/glossary/lib.php @@ -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 []; } diff --git a/mod/glossary/mod_form.php b/mod/glossary/mod_form.php index 50780132f4d..4f70264122f 100644 --- a/mod/glossary/mod_form.php +++ b/mod/glossary/mod_form.php @@ -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 diff --git a/mod/lesson/lib.php b/mod/lesson/lib.php index 817f34d966d..0c4ad107062 100644 --- a/mod/lesson/lib.php +++ b/mod/lesson/lib.php @@ -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 []; } diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index 26ddc29fa52..5041155460b 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -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 []; } diff --git a/mod/scorm/lib.php b/mod/scorm/lib.php index bf2db5f0413..a9a0419712c 100644 --- a/mod/scorm/lib.php +++ b/mod/scorm/lib.php @@ -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 []; } diff --git a/mod/survey/lib.php b/mod/survey/lib.php index 24307330a87..e0b01c3ccf4 100644 --- a/mod/survey/lib.php +++ b/mod/survey/lib.php @@ -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 []; } diff --git a/version.php b/version.php index b0c90743df0..9a2a73a2554 100644 --- a/version.php +++ b/version.php @@ -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. -- 2.43.0