Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue #127: add support for mod_certificate #129

Merged
merged 1 commit into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion backup/moodle2/backup_local_recompletion_plugin.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ protected function define_course_plugin_structure() {
}
$lessonoverride->annotate_ids('user', 'userid');

// Now deal with hvp archive tables.
// Now deal with hotpot archive tables.
$hotpotattempts = new backup_nested_element('hotpotattempts');
$hotpotattempt = new backup_nested_element('hotpotattempt', array('id'), array(
'hotpotid', 'userid', 'starttime', 'endtime', 'score', 'penalties', 'attempt', 'timestart',
Expand All @@ -260,6 +260,19 @@ protected function define_course_plugin_structure() {
}
$hotpotattempt->annotate_ids('user', 'userid');

// Now deal mod_certificate archive table.
$certificates = new backup_nested_element('certificates');
$certificate = new backup_nested_element('certificate', array('id'), array(
'userid', 'certificateid', 'code', 'timecreated', 'printdate', 'course'));

$recompletion->add_child($certificates);
$certificates->add_child($certificate);

if ($usercompletion) {
$certificate->set_source_table('local_recompletion_cert', array('course' => backup::VAR_COURSEID));
}
$certificate->annotate_ids('user', 'userid');

return $plugin;
}

Expand Down
23 changes: 23 additions & 0 deletions backup/moodle2/restore_local_recompletion_plugin.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ protected function define_course_plugin_structure() {
$paths[] = new restore_path_element('recompletion_lessonbrach', $elepath.'/lessonbraches/lessonbrach');
$paths[] = new restore_path_element('recompletion_lessonoverride', $elepath.'/lessonoverrides/lessonoverride');
$paths[] = new restore_path_element('recompletion_hpa', $elepath.'/hotpotattempts/hotpotattempt');
$paths[] = new restore_path_element('recompletion_cert', $elepath.'/certificates/certificate');

return $paths;
}
Expand Down Expand Up @@ -310,6 +311,20 @@ public function process_recompletion_hpa($data) {
$DB->insert_record('local_recompletion_hpa', $data);
}

/**
* Process local_recompletion_cert table.
* @param stdClass $data
*/
public function process_recompletion_cert($data) {
global $DB;

$data = (object) $data;
$data->course = $this->task->get_courseid();
$data->userid = $this->get_mappingid('user', $data->userid);

$DB->insert_record('local_recompletion_cert', $data);
}

/**
* We call the after restore_course to update the coursemodule ids we didn't know when creating.
*/
Expand Down Expand Up @@ -394,5 +409,13 @@ protected function after_restore_course() {
$DB->update_record('local_recompletion_hpa', $rc);
}
$rcm->close();

// Fix certificates.
$rcm = $DB->get_recordset('local_recompletion_cert', array('course' => $this->task->get_courseid()));
foreach ($rcm as $rc) {
$rc->certificateid = $this->get_mappingid('certificate', $rc->certificateid);
$DB->update_record('local_recompletion_cert', $rc);
}
$rcm->close();
}
}
213 changes: 213 additions & 0 deletions classes/plugins/mod_certificate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
<?php
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.

namespace local_recompletion\plugins;

use stdClass;
use lang_string;
use admin_setting_configselect;
use admin_setting_configcheckbox;
use admin_settingpage;
use core\output\notification;
use MoodleQuickForm;

defined('MOODLE_INTERNAL') || die;

require_once($CFG->dirroot.'/local/recompletion/locallib.php');

/**
* Certificate handler event.
*
* @package local_recompletion
* @author 2023 Dmitrii Metelkin
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_certificate {

/**
* Add params to form.
*
* @param MoodleQuickForm $mform
*/
public static function editingform(MoodleQuickForm $mform): void {
global $OUTPUT;

if (!self::installed()) {
return;
}
$config = get_config('local_recompletion');

$cba = [];
$cba[] = $mform->createElement('radio', 'certificate', '',
get_string('donothing', 'local_recompletion'), LOCAL_RECOMPLETION_NOTHING);
$cba[] = $mform->createElement('radio', 'certificate', '',
get_string('deletecertificate', 'local_recompletion'), LOCAL_RECOMPLETION_DELETE);

$mform->addGroup($cba, 'certificate', get_string('certificate', 'local_recompletion'), [' '], false);
$mform->addHelpButton('certificate', 'certificate', 'local_recompletion');
$mform->setDefault('certificate', $config->certificate);

$mform->addElement('checkbox', 'archivecertificate',
get_string('archivecertificate', 'local_recompletion'));
$mform->setDefault('archivecertificate', $config->archivecertificate);

$verifywarngroup = [];
$verifywarn = new notification(
get_string('certificateverifywarn', 'local_recompletion'),
notification::NOTIFY_WARNING);
$verifywarn->set_show_closebutton(false);
$verifywarngroup[] =
$mform->createElement('static', 'certificateverifywarn', '', $OUTPUT->render($verifywarn));
$mform->addGroup($verifywarngroup, 'certificateverifywarngroup', '', ' ', false);

$mform->disabledIf('certificate', 'enable');
$mform->disabledIf('archivecertificate', 'enable');
$mform->hideIf('archivecertificate', 'certificate', 'noteq', LOCAL_RECOMPLETION_DELETE);
$mform->hideIf('certificateverifywarn', 'certificate', 'noteq', LOCAL_RECOMPLETION_DELETE);
}

/**
* Add site level settings for this plugin.
*
* @param admin_settingpage $settings
*/
public static function settings(admin_settingpage $settings) {

if (!self::installed()) {
return;
}

$choices = [
LOCAL_RECOMPLETION_NOTHING => get_string('donothing', 'local_recompletion'),
LOCAL_RECOMPLETION_DELETE => get_string('customcertresetcertificates', 'local_recompletion')
];

$settings->add(new admin_setting_configselect('local_recompletion/certificate',
new lang_string('certificate', 'local_recompletion'),
new lang_string('certificate_help', 'local_recompletion'), LOCAL_RECOMPLETION_NOTHING, $choices));

$settings->add(new admin_setting_configcheckbox('local_recompletion/archivecertificate',
new lang_string('archivecertificate', 'local_recompletion'),
new lang_string('archivecertificate_help', 'local_recompletion'), 1));
}

/**
* Reset 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->certificate)) {
return;
}

if ($config->certificate == LOCAL_RECOMPLETION_DELETE) {
$params = [
'userid' => $userid,
'courseid' => $course->id,
];

if ($config->archivecertificate) {

// Archive the issued certificates.
$sql = "SELECT ci.*, c.printdate
FROM {certificate_issues} ci
JOIN {certificate} c ON c.id = ci.certificateid
WHERE ci.userid = :userid AND ci.certificateid IN (SELECT id FROM {certificate} WHERE course = :courseid)";
$issuedcerts = $DB->get_records_sql($sql, $params);

foreach (array_keys($issuedcerts) as $ic) {
$issuedcerts[$ic]->course = $course->id;
// Depending on activity settings actual date printed on a certificate can be different
// to a date of issue. Let's try to build printed date and archive it as well for future verification.
$issuedcerts[$ic]->printdate = self::certificate_get_date(
$issuedcerts[$ic]->timecreated,
$issuedcerts[$ic]->printdate,
(object) ['id' => $course->id],
$userid
);
}

// Archive records.
$DB->insert_records('local_recompletion_cert', $issuedcerts);
}

// Finally delete records.
$selectsql = 'userid = :userid AND certificateid IN (SELECT id FROM {certificate} WHERE course = :courseid)';
$DB->delete_records_select('certificate_issues', $selectsql, $params);
}
}

/**
* Helper function to check if it's installed.
* @return bool
*/
public static function installed(): bool {
global $CFG;

if (!file_exists($CFG->dirroot . '/mod/certificate/version.php')) {
return false;
}

return true;
}

/**
* Returns the date to display for the certificate.
*
* This is pretty much replication of certificate_get_date from locallib.php of mod_certificate.
*
* @param string $issueddate Issue date.
* @param string $printdate Print date setting.
* @param stdClass $course Course object.
* @param int $userid User ID.
*
* @return string the date
*/
protected static function certificate_get_date(string $issueddate, string $printdate, stdClass $course, int $userid): string {
global $DB, $CFG;

require_once($CFG->dirroot . '/mod/certificate/locallib.php');

$date = $issueddate;

if ($printdate == '2') {
$sql = "SELECT MAX(c.timecompleted) as timecompleted
FROM {course_completions} c
WHERE c.userid = :userid
AND c.course = :courseid";
if ($timecompleted = $DB->get_record_sql($sql, ['userid' => $userid, 'courseid' => $course->id])) {
if (!empty($timecompleted->timecompleted)) {
$date = $timecompleted->timecompleted;
}
}
} else if ($printdate > 2) {
if ($modinfo = certificate_get_mod_grade($course, $printdate, $userid)) {
$date = $modinfo->dategraded;
}
}

return $date;
}
}
40 changes: 38 additions & 2 deletions classes/privacy/provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ public static function get_metadata(collection $collection) : collection {
'score' => 'privacy:metadata:score',
], 'privacy:metadata:local_recompletion_hpa');

$collection->add_database_table('local_recompletion_cert', [
'userid' => 'privacy:metadata:userid',
'timecreated' => 'privacy:metadata:local_recompletion_cert:timecreated',
'course' => 'privacy:metadata:course',
], 'privacy:metadata:local_recompletion_cert');

return $collection;
}

Expand Down Expand Up @@ -367,6 +373,14 @@ public static function export_user_data(approved_contextlist $contextlist) {
[get_string('recompletion', 'local_recompletion'), 'hotpot_attempts'],
(object)[array_map([self::class, 'transform_db_row_to_session_data'], $records)]);
}

$records = $DB->get_records('local_recompletion_cert', $params);
foreach ($records as $record) {
$context = \context_course::instance($record->course);
writer::with_context($context)->export_data(
[get_string('recompletion', 'local_recompletion'), 'certificate_issues'],
(object)[array_map([self::class, 'transform_db_row_to_session_data'], $records)]);
}
}
}

Expand All @@ -381,7 +395,7 @@ public static function export_user_data(approved_contextlist $contextlist) {
*/
private static function transform_db_row_to_session_data(stdClass $dbrow) : stdClass {
$times = array('timeenrolled', 'timestarted', 'timecompleted', 'timemodified', 'timemodifiedoffline',
'timestart', 'timefinish', 'timeseen', 'starttime', 'endtime');
'timestart', 'timefinish', 'timeseen', 'starttime', 'endtime', 'timecreated');
foreach ($times as $time) {
if (isset($dbrow->$time) && (!empty($dbrow->$time))) {
$dbrow->$time = transform::datetime($dbrow->$time);
Expand Down Expand Up @@ -421,6 +435,7 @@ public static function delete_data_for_all_users_in_context(\context $context) {
$DB->delete_records('local_recompletion_lb', $params);
$DB->delete_records('local_recompletion_lo', $params);
$DB->delete_records('local_recompletion_hpa', $params);
$DB->delete_records('local_recompletion_cert', $params);

self::delete_hp5_activity_records($courseid);
}
Expand Down Expand Up @@ -457,6 +472,7 @@ public static function delete_data_for_user(approved_contextlist $contextlist) {
$DB->delete_records('local_recompletion_lb', $params);
$DB->delete_records('local_recompletion_lo', $params);
$DB->delete_records('local_recompletion_hpa', $params);
$DB->delete_records('local_recompletion_cert', $params);
}
}

Expand Down Expand Up @@ -557,6 +573,12 @@ public static function get_contexts_for_userid(int $userid) : contextlist {
JOIN {local_recompletion_hpa} rc ON rc.course = c.id and rc.userid = :userid";
$contextlist->add_from_sql($sql, $params);

$sql = "SELECT ctx.id
FROM {course} c
JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
JOIN {local_recompletion_cert} rc ON rc.course = c.id and rc.userid = :userid";
$contextlist->add_from_sql($sql, $params);

return $contextlist;
}
/**
Expand Down Expand Up @@ -682,6 +704,13 @@ public static function get_users_in_context(userlist $userlist) {
JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
WHERE ctx.id = :contextid";
$userlist->add_from_sql('userid', $sql, $params);

$sql = "SELECT rc.userid
FROM {local_recompletion_cert} rc
JOIN {course} c ON rc.course = c.id
JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
WHERE ctx.id = :contextid";
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Delete multiple users within a single context.
Expand Down Expand Up @@ -821,7 +850,14 @@ public static function delete_data_for_users(approved_userlist $userlist) {
JOIN {course} c ON rc.course = c.id
JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
WHERE ctx.id = :contextid AND rc.userid $insql";
$DB->delete_records_select('local_recompletion_lo', "id $sql", $params);
$DB->delete_records_select('local_recompletion_hpa', "id $sql", $params);

$sql = "SELECT rc.id
FROM {local_recompletion_cert} rc
JOIN {course} c ON rc.course = c.id
JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
WHERE ctx.id = :contextid AND rc.userid $insql";
$DB->delete_records_select('local_recompletion_cert', "id $sql", $params);
}

/**
Expand Down
Loading
Loading