diff --git a/backup/moodle2/backup_local_recompletion_plugin.class.php b/backup/moodle2/backup_local_recompletion_plugin.class.php index 137f5b8..8e2c118 100644 --- a/backup/moodle2/backup_local_recompletion_plugin.class.php +++ b/backup/moodle2/backup_local_recompletion_plugin.class.php @@ -144,6 +144,46 @@ protected function define_course_plugin_structure() { } $choiceanswer->annotate_ids('user', 'userid'); + // Now deal with hvp archive tables. + $hvpattempts = new backup_nested_element('hvpattempts'); + $hvpattempt = new backup_nested_element('hvpattempt', array('id'), array( + 'user_id', 'hvp_id', 'sub_content_id', 'data_id', 'data', 'preloaded', 'delete_on_content_change', 'course')); + + $recompletion->add_child($hvpattempts); + $hvpattempts->add_child($hvpattempt); + + if ($usercompletion) { + $hvpattempt->set_source_table('local_recompletion_hvp', array('course' => backup::VAR_COURSEID)); + } + $hvpattempt->annotate_ids('user', 'user_id'); + + // Now deal with h5p table. + $h5ps = new backup_nested_element('h5ps'); + $h5p = new backup_nested_element('h5p', array('id'), array( + 'originalattemptid', 'h5pactivityid', 'userid', 'timecreated', 'timemodified', + 'rawscore', 'maxscore', 'scaled', 'duration', 'completion', 'success', 'course')); + + // Now deal with h5p results table. + $h5presults = new backup_nested_element('h5presults'); + $h5presult = new backup_nested_element('h5presult', array('id'), array( + 'attemptid', 'subcontent', 'timecreated', 'interactiontype', 'description', + 'correctpattern', 'response', 'additionals', 'rawscore', 'maxscore', 'duration', 'completion', 'success', 'course')); + + $recompletion->add_child($h5ps); + $h5ps->add_child($h5p); + $h5p->add_child($h5presults); + $h5presults->add_child($h5presult); + + if ($usercompletion) { + $h5p->set_source_table('local_recompletion_h5p', array('course' => backup::VAR_COURSEID)); + $h5presult->set_source_table( + 'local_recompletion_h5pr', + array('course' => backup::VAR_COURSEID, 'attemptid' => backup::VAR_PARENTID) + ); + } + + $h5p->annotate_ids('user', 'userid'); + return $plugin; } diff --git a/backup/moodle2/restore_local_recompletion_plugin.class.php b/backup/moodle2/restore_local_recompletion_plugin.class.php index 15fc9fd..09c3b3b 100644 --- a/backup/moodle2/restore_local_recompletion_plugin.class.php +++ b/backup/moodle2/restore_local_recompletion_plugin.class.php @@ -48,6 +48,9 @@ protected function define_course_plugin_structure() { $paths[] = new restore_path_element('recompletion_qg', $elepath.'/quizgrades/grade'); $paths[] = new restore_path_element('recompletion_sst', $elepath.'/scormtracks/sco_track'); $paths[] = new restore_path_element('recompletion_cha', $elepath.'/choiceanswers/choiceanswer'); + $paths[] = new restore_path_element('recompletion_hvp', $elepath.'/hvpattempts/hvpattempt'); + $paths[] = new restore_path_element('recompletion_h5p', $elepath.'/h5ps/h5p'); + $paths[] = new restore_path_element('recompletion_h5pr', $elepath.'/h5ps/h5p/h5presults/h5presult'); return $paths; } @@ -164,6 +167,49 @@ public function process_recompletion_cha($data) { $DB->insert_record('local_recompletion_cha', $data); } + /** + * Process local_recompletion_hvp table. + * @param stdClass $data + */ + public function process_recompletion_hvp($data) { + global $DB; + + $data = (object) $data; + $data->course = $this->task->get_courseid(); + $data->user_id = $this->get_mappingid('user', $data->user_id); + + $DB->insert_record('local_recompletion_hvp', $data); + } + + /** + * Process local_recompletion_h5p table. + * @param stdClass $data + */ + public function process_recompletion_h5p($data) { + global $DB; + + $data = (object) $data; + $oldid = $data->id; + $data->course = $this->task->get_courseid(); + $data->userid = $this->get_mappingid('user', $data->userid); + + $newitemid = $DB->insert_record('local_recompletion_h5p', $data); + $this->set_mapping('recompletion_h5p', $oldid, $newitemid); + } + + /** + * Process local_recompletion_h5pr table. + * @param stdClass $data + */ + public function process_recompletion_h5pr($data) { + global $DB; + + $data = (object) $data; + $data->course = $this->task->get_courseid(); + $data->attemptid = $this->get_new_parentid('recompletion_h5p'); + $DB->insert_record('local_recompletion_h5pr', $data); + } + /** * We call the after restore_course to update the coursemodule ids we didn't know when creating. */ @@ -210,5 +256,22 @@ protected function after_restore_course() { } $rcm->close(); + // Fix hvp attempts. + $rcm = $DB->get_recordset('local_recompletion_hvp', array('course' => $this->task->get_courseid())); + foreach ($rcm as $rc) { + $rc->hvp_id = $this->get_mappingid('hvp', $rc->hvp_id); + $DB->update_record('local_recompletion_hvp', $rc); + } + $rcm->close(); + + // Fix h5p attempts. + $rcm = $DB->get_recordset('local_recompletion_h5p', array('course' => $this->task->get_courseid())); + foreach ($rcm as $rc) { + $rc->h5pactivityid = $this->get_mappingid('h5pactivity', $rc->h5pactivityid); + $rc->originalattemptid = 0; // Don't restore orginal attempt id. + + $DB->update_record('local_recompletion_h5p', $rc); + } + $rcm->close(); } } diff --git a/classes/plugins/mod_h5pactivity.php b/classes/plugins/mod_h5pactivity.php new file mode 100644 index 0000000..d2498fb --- /dev/null +++ b/classes/plugins/mod_h5pactivity.php @@ -0,0 +1,144 @@ +. + +namespace local_recompletion\plugins; + +use stdClass; +use lang_string; +use admin_setting_configselect; +use admin_setting_configcheckbox; +use admin_settingpage; +use MoodleQuickForm; + +/** + * H5P handler event. + * + * @package local_recompletion + * @author 2023 Dmitrii Metelkin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_h5pactivity { + + /** + * Add params to form. + * + * @param MoodleQuickForm $mform + */ + public static function editingform(MoodleQuickForm $mform): void { + $config = get_config('local_recompletion'); + + $cba = []; + $cba[] = $mform->createElement('radio', 'h5pactivity', '', + get_string('donothing', 'local_recompletion'), LOCAL_RECOMPLETION_NOTHING); + $cba[] = $mform->createElement('radio', 'h5pactivity', '', + get_string('delete', 'local_recompletion'), LOCAL_RECOMPLETION_DELETE); + + $mform->addGroup($cba, 'h5pactivity', get_string('h5pattempts', 'local_recompletion'), [' '], false); + $mform->addHelpButton('h5pactivity', 'h5pattempts', 'local_recompletion'); + $mform->setDefault('h5pactivity', $config->h5pattempts); + + $mform->addElement('checkbox', 'archiveh5pactivity', get_string('archive', 'local_recompletion')); + $mform->setDefault('archiveh5pactivity', $config->archiveh5p); + + $mform->disabledIf('archiveh5pactivity', 'enable'); + $mform->hideIf('archiveh5pactivity', 'h5pactivity'); + $mform->disabledIf('h5pactivity', 'enable'); + } + + /** + * Add site level settings for this plugin. + * + * @param admin_settingpage $settings + */ + public static function settings(admin_settingpage $settings): void { + $choices = [ + LOCAL_RECOMPLETION_NOTHING => get_string('donothing', 'local_recompletion'), + LOCAL_RECOMPLETION_DELETE => get_string('delete', 'local_recompletion') + ]; + + $settings->add(new admin_setting_configselect('local_recompletion/h5pattempts', + new lang_string('h5pattempts', 'local_recompletion'), + new lang_string('h5pattempts_help', 'local_recompletion'), LOCAL_RECOMPLETION_NOTHING, $choices)); + + $settings->add(new admin_setting_configcheckbox('local_recompletion/archiveh5p', + new lang_string('archiveh5p', 'local_recompletion'), '', 1)); + } + + /** + * Reset pulse notification records. + * + * @param int $userid - user id + * @param stdClass $course - course record. + * @param stdClass $config - recompletion config. + */ + public static function reset(int $userid, stdClass $course, stdClass $config): void { + global $DB; + + if (empty($config->h5pactivity)) { + return; + } + + if ($config->h5pactivity == LOCAL_RECOMPLETION_DELETE) { + $params = [ + 'userid' => $userid, + 'course' => $course->id + ]; + + $attemptsselectsql = 'userid = :userid AND h5pactivityid IN (SELECT id FROM {h5pactivity} WHERE course = :course)'; + $resultsselectsql = 'attemptid IN (SELECT id FROM {h5pactivity_attempts} WHERE ' . $attemptsselectsql . ')'; + + if ($config->archiveh5pactivity) { + + // Archive attempts. + $attempts = $DB->get_records_select('h5pactivity_attempts', $attemptsselectsql, $params); + $attemptids = array_keys($attempts); + + foreach ($attempts as $attempt) { + $attempt->course = $course->id; + $attempt->originalattemptid = $attempt->id; + } + + $DB->insert_records('local_recompletion_h5p', $attempts); + + // Archive results. + $results = $DB->get_records_select('h5pactivity_attempts_results', $resultsselectsql, $params); + foreach ($results as $result) { + $result->course = $course->id; + } + $DB->insert_records('local_recompletion_h5pr', $results); + + // Update attemptid for just inserted attempt results with IDs of previously inserted attempts. + // We use temp originalattemptid here as it should be unique. + $sql = 'UPDATE {local_recompletion_h5pr} + SET attemptid = (SELECT id + FROM {local_recompletion_h5p} + WHERE originalattemptid = attemptid)'; + $DB->execute($sql); + + // Now reset originalattemptid as we don't need it anymore. + // Also to avoid issues with backup and restore when potentially originalattemptid can clash + // if restoring a course from another Moodle instance. + list($insql, $inparams) = $DB->get_in_or_equal($attemptids, SQL_PARAMS_NAMED); + $sql = "UPDATE {local_recompletion_h5p} SET originalattemptid = 0 WHERE originalattemptid $insql"; + $DB->execute($sql, $inparams); + } + + // Finally can delete records. + $DB->delete_records_select('h5pactivity_attempts_results', $resultsselectsql, $params); + $DB->delete_records_select('h5pactivity_attempts', $attemptsselectsql, $params); + } + } +} diff --git a/classes/plugins/mod_hvp.php b/classes/plugins/mod_hvp.php new file mode 100644 index 0000000..a538924 --- /dev/null +++ b/classes/plugins/mod_hvp.php @@ -0,0 +1,138 @@ +. + +namespace local_recompletion\plugins; + +use stdClass; +use lang_string; +use admin_setting_configselect; +use admin_setting_configcheckbox; +use admin_settingpage; +use MoodleQuickForm; + +/** + * H5P handler event. + * + * @package local_recompletion + * @author 2023 Dmitrii Metelkin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_hvp { + + /** + * Add params to form. + * + * @param MoodleQuickForm $mform + */ + public static function editingform(MoodleQuickForm $mform): void { + if (!self::installed()) { + return; + } + + $config = get_config('local_recompletion'); + + $cba = []; + $cba[] = $mform->createElement('radio', 'hvp', '', + get_string('donothing', 'local_recompletion'), LOCAL_RECOMPLETION_NOTHING); + $cba[] = $mform->createElement('radio', 'hvp', '', + get_string('delete', 'local_recompletion'), LOCAL_RECOMPLETION_DELETE); + + $mform->addGroup($cba, 'hvp', get_string('hvpattempts', 'local_recompletion'), [' '], false); + $mform->addHelpButton('hvp', 'hvpattempts', 'local_recompletion'); + $mform->setDefault('hvp', $config->hvpattempts); + + $mform->addElement('checkbox', 'archivehvp', get_string('archive', 'local_recompletion')); + $mform->setDefault('archivehvp', $config->archivehvp); + + $mform->disabledIf('archivehvp', 'enable'); + $mform->hideIf('archivehvp', 'hvp'); + $mform->disabledIf('hvp', 'enable'); + } + + /** + * Add site level settings for this plugin. + * + * @param admin_settingpage $settings + */ + public static function settings(admin_settingpage $settings): void { + if (!self::installed()) { + return; + } + + $choices = [ + LOCAL_RECOMPLETION_NOTHING => get_string('donothing', 'local_recompletion'), + LOCAL_RECOMPLETION_DELETE => get_string('delete', 'local_recompletion') + ]; + + $settings->add(new admin_setting_configselect('local_recompletion/hvpattempts', + new lang_string('hvpattempts', 'local_recompletion'), + new lang_string('hvpattempts_help', 'local_recompletion'), LOCAL_RECOMPLETION_NOTHING, $choices)); + + $settings->add(new admin_setting_configcheckbox('local_recompletion/archivehvp', + new lang_string('archivehvp', 'local_recompletion'), '', 1)); + } + + /** + * Reset pulse notification records. + * + * @param int $userid - user id + * @param stdClass $course - course record. + * @param stdClass $config - recompletion config. + */ + public static function reset(int $userid, stdClass $course, stdClass $config): void { + global $DB; + + if (!self::installed()) { + return; + } + + if (empty($config->hvp)) { + return; + } + + if ($config->hvp == LOCAL_RECOMPLETION_DELETE) { + $params = [ + 'userid' => $userid, + 'course' => $course->id + ]; + + $selectsql = 'user_id = :userid AND hvp_id IN (SELECT id FROM {hvp} WHERE course = :course)'; + + if ($config->archivehvp) { + $records = $DB->get_records_select('hvp_content_user_data', $selectsql, $params); + foreach ($records as $record) { + $record->course = $course->id; + } + $DB->insert_records('local_recompletion_hvp', $records); + } + + $DB->delete_records_select('hvp_content_user_data', $selectsql, $params); + $DB->delete_records_select('hvp_xapi_results', $selectsql, $params); + } + } + + /** + * Helper function to check if the plugin is installed. + * @return bool + */ + public static function installed(): bool { + global $CFG; + if (!file_exists($CFG->dirroot . '/mod/hvp/version.php')) { + return false; + } + return true; + } +} diff --git a/db/install.xml b/db/install.xml index a4ddb3e..6a15d65 100644 --- a/db/install.xml +++ b/db/install.xml @@ -1,5 +1,5 @@ - @@ -325,5 +325,64 @@ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
diff --git a/db/upgrade.php b/db/upgrade.php index 0f8791b..1ddda85 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -694,5 +694,93 @@ function xmldb_local_recompletion_upgrade($oldversion) { } + if ($oldversion < 2023101800) { + // Define table local_recompletion_hvp to be created. + $table = new xmldb_table('local_recompletion_hvp'); + + // Adding fields to table local_recompletion_hvp. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('user_id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('hvp_id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('sub_content_id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('data_id', XMLDB_TYPE_CHAR, '127', null, null, null, null); + $table->add_field('data', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('preloaded', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null); + $table->add_field('delete_on_content_change', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + + // Adding keys to table local_recompletion_hvp. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Conditionally launch create table for local_recompletion_hvp. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Recompletion savepoint reached. + upgrade_plugin_savepoint(true, 2023101800, 'local', 'recompletion'); + } + + if ($oldversion < 2023101900) { + + // Define table local_recompletion_h5p to be created. + $table = new xmldb_table('local_recompletion_h5p'); + + // Adding fields to table local_recompletion_h5p. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('originalattemptid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('h5pactivityid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '20', null, XMLDB_NOTNULL, null, null); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('attempt', XMLDB_TYPE_INTEGER, '6', null, XMLDB_NOTNULL, null, '1'); + $table->add_field('rawscore', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + $table->add_field('maxscore', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + $table->add_field('scaled', XMLDB_TYPE_NUMBER, '10, 5', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('duration', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + $table->add_field('completion', XMLDB_TYPE_INTEGER, '1', null, null, null, null); + $table->add_field('success', XMLDB_TYPE_INTEGER, '1', null, null, null, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + + // Adding keys to table local_recompletion_h5p. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Conditionally launch create table for local_recompletion_h5p. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Define table local_recompletion_h5pr to be created. + $table = new xmldb_table('local_recompletion_h5pr'); + + // Adding fields to table local_recompletion_h5pr. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('attemptid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('subcontent', XMLDB_TYPE_CHAR, '128', null, null, null, null); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('interactiontype', XMLDB_TYPE_CHAR, '128', null, null, null, null); + $table->add_field('description', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('correctpattern', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('response', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null); + $table->add_field('additionals', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('rawscore', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('maxscore', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('duration', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + $table->add_field('completion', XMLDB_TYPE_INTEGER, '1', null, null, null, null); + $table->add_field('success', XMLDB_TYPE_INTEGER, '1', null, null, null, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + + // Adding keys to table local_recompletion_h5pr. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Conditionally launch create table for local_recompletion_h5pr. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Recompletion savepoint reached. + upgrade_plugin_savepoint(true, 2023101900, 'local', 'recompletion'); + } + return true; } diff --git a/lang/en/local_recompletion.php b/lang/en/local_recompletion.php index efd34f7..e2bc12e 100644 --- a/lang/en/local_recompletion.php +++ b/lang/en/local_recompletion.php @@ -180,3 +180,9 @@ $string['archivecustomcertcertificates_help'] = 'Should issued custom certificates be archived?'; $string['recompletionunenrolenable'] = 'Reset completion on un-enrolment'; $string['recompletionunenrolenable_help'] = 'Enable to trigger completion reset on user un-enrolment'; +$string['hvpattempts'] = 'H5P attempts (mod_hvp)'; +$string['hvpattempts_help'] = 'How to handle H5P attempts within the course. If archive is selected, the old H5P attempts will be archived in the local_recompletion_hvp table.'; +$string['archivehvp'] = 'Archive old H5P attempts (mod_hvp)'; +$string['h5pattempts'] = 'H5P attempts (mod_h5pactivity)'; +$string['h5pattempts_help'] = 'How to handle H5P attempts within the course. If archive is selected, the old H5P attempts will be archived in the local_recompletion_h5p and local_recompletion_h5pr tables.'; +$string['archiveh5p'] = 'Archive old H5P attempts (mod_h5pactivity)'; diff --git a/locallib.php b/locallib.php index c30aaa7..8e69e52 100644 --- a/locallib.php +++ b/locallib.php @@ -38,7 +38,7 @@ function local_recompletion_get_supported_plugins() { $plugins = []; $files = scandir($CFG->dirroot. '/local/recompletion/classes/plugins'); foreach ($files as $file) { - $component = clean_param(str_replace('.php', '', $file), PARAM_ALPHAEXT); + $component = clean_param(str_replace('.php', '', $file), PARAM_ALPHANUMEXT); list($plugin, $type) = core_component::normalize_component($component); if (!core_component::is_valid_plugin_name($type, $plugin)) { diff --git a/version.php b/version.php index 3fd8e7a..36c79cf 100644 --- a/version.php +++ b/version.php @@ -24,8 +24,8 @@ defined('MOODLE_INTERNAL') || die; -$plugin->version = 2023101702; -$plugin->release = 2023101702; +$plugin->version = 2023101900; +$plugin->release = 2023101900; $plugin->maturity = MATURITY_STABLE; $plugin->requires = 2022112806; // Requires 4.1 $plugin->component = 'local_recompletion';