-
Notifications
You must be signed in to change notification settings - Fork 181
Updated view_user_cert to support all certificates. #681
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
base: MOODLE_404_STABLE
Are you sure you want to change the base?
Changes from all commits
fcd6271
d17d5f2
d4ed4eb
9fa0eac
288d0f5
2728a48
129747f
b60c152
9b6ab3a
8fd2bf3
820882b
20e732e
6cdab98
cce2c41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,6 @@ | |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class certificate { | ||
|
||
/** | ||
* Send the file inline to the browser. | ||
*/ | ||
|
@@ -293,7 +292,7 @@ public static function download_all_issues_for_instance(\mod_customcert\template | |
public static function download_all_for_site(): void { | ||
global $DB; | ||
|
||
list($namefields, $nameparams) = \core_user\fields::get_sql_fullname(); | ||
[$namefields, $nameparams] = \core_user\fields::get_sql_fullname(); | ||
$sql = "SELECT ci.*, $namefields as fullname, ct.id as templateid, ct.name as templatename, ct.contextid | ||
FROM {customcert_issues} ci | ||
JOIN {user} u | ||
|
@@ -350,7 +349,7 @@ public static function get_issues($customcertid, $groupmode, $cm, $limitfrom, $l | |
global $DB; | ||
|
||
// Get the conditional SQL. | ||
list($conditionssql, $conditionsparams) = self::get_conditional_issues_sql($cm, $groupmode); | ||
[$conditionssql, $conditionsparams] = self::get_conditional_issues_sql($cm, $groupmode); | ||
|
||
// If it is empty then return an empty array. | ||
if (empty($conditionsparams)) { | ||
|
@@ -389,7 +388,7 @@ public static function get_number_of_issues($customcertid, $cm, $groupmode) { | |
global $DB; | ||
|
||
// Get the conditional SQL. | ||
list($conditionssql, $conditionsparams) = self::get_conditional_issues_sql($cm, $groupmode); | ||
[$conditionssql, $conditionsparams] = self::get_conditional_issues_sql($cm, $groupmode); | ||
|
||
// If it is empty then return 0. | ||
if (empty($conditionsparams)) { | ||
|
@@ -428,7 +427,7 @@ public static function get_conditional_issues_sql($cm, $groupmode) { | |
// Get all users that can manage this certificate to exclude them from the report. | ||
$certmanagers = array_keys(get_users_by_capability($context, 'mod/customcert:manage', 'u.id')); | ||
$certmanagers = array_merge($certmanagers, array_keys(get_admins())); | ||
list($sql, $params) = $DB->get_in_or_equal($certmanagers, SQL_PARAMS_NAMED, 'cert'); | ||
[$sql, $params] = $DB->get_in_or_equal($certmanagers, SQL_PARAMS_NAMED, 'cert'); | ||
$conditionssql .= "AND NOT u.id $sql \n"; | ||
$conditionsparams += $params; | ||
|
||
|
@@ -464,7 +463,7 @@ public static function get_conditional_issues_sql($cm, $groupmode) { | |
return ['', []]; | ||
} | ||
|
||
list($sql, $params) = $DB->get_in_or_equal($groupusers, SQL_PARAMS_NAMED, 'grp'); | ||
[$sql, $params] = $DB->get_in_or_equal($groupusers, SQL_PARAMS_NAMED, 'grp'); | ||
$conditionssql .= "AND u.id $sql "; | ||
$conditionsparams += $params; | ||
} | ||
|
@@ -563,14 +562,13 @@ public static function generate_code(): string { | |
// Get the user's selected method from settings. | ||
$method = get_config('customcert', 'codegenerationmethod'); | ||
|
||
do { | ||
$code = match ($method) { | ||
'0' => self::generate_code_upper_lower_digits(), | ||
'1' => self::generate_code_digits_with_hyphens(), | ||
default => self::generate_code_upper_lower_digits(), | ||
}; | ||
} while ($DB->record_exists('customcert_issues', ['code' => $code])); | ||
return $code; | ||
// If the upper/lower/digits is selected (0), use the upper/lower/digits code generation method. | ||
if ($method == 0) { | ||
return self::generate_code_upper_lower_digits(); | ||
} | ||
|
||
// Otherwise, use the digits with hyphens method (1). | ||
return self::generate_code_digits_with_hyphens(); | ||
} | ||
|
||
/** | ||
|
@@ -580,7 +578,19 @@ public static function generate_code(): string { | |
* @return string | ||
*/ | ||
private static function generate_code_upper_lower_digits(): string { | ||
return random_string(10); | ||
global $DB; | ||
|
||
$uniquecodefound = false; | ||
$code = random_string(10); | ||
while (!$uniquecodefound) { | ||
if (!$DB->record_exists('customcert_issues', ['code' => $code])) { | ||
$uniquecodefound = true; | ||
} else { | ||
$code = random_string(10); | ||
} | ||
} | ||
|
||
return $code; | ||
} | ||
|
||
/** | ||
|
@@ -590,11 +600,27 @@ private static function generate_code_upper_lower_digits(): string { | |
* @return string | ||
*/ | ||
private static function generate_code_digits_with_hyphens(): string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you tell me why we are editing this function? |
||
return sprintf( | ||
'%04d-%04d-%04d', | ||
random_int(0, 9999), | ||
random_int(0, 9999), | ||
random_int(0, 9999) | ||
); | ||
global $DB; | ||
|
||
// Define the character set (digits only). | ||
$characters = '0123456789'; | ||
$charcount = strlen($characters); // Cache the length to optimize loop performance. | ||
$length = 12; // Total length excluding hyphens. | ||
|
||
do { | ||
// Generate a raw code. | ||
$rawcode = ''; | ||
for ($i = 0; $i < $length; $i++) { | ||
$rawcode .= $characters[random_int(0, $charcount - 1)]; // Secure random number selection. | ||
} | ||
|
||
// Format the code as XXXX-XXXX-XXXX. | ||
$code = substr($rawcode, 0, 4) . '-' . substr($rawcode, 4, 4) . '-' . substr($rawcode, 8, 4); | ||
|
||
// Check if the generated code already exists in the database. | ||
$exists = $DB->record_exists('customcert_issues', ['code' => $code]); | ||
} while ($exists); // Repeat until a unique code is found. | ||
|
||
return $code; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -316,24 +316,5 @@ function xmldb_customcert_upgrade($oldversion) { | |
upgrade_plugin_savepoint(true, 2024042210, 'mod', 'customcert'); | ||
} | ||
|
||
if ($oldversion < 2024042213) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need an upgrade clause to remove these settings from the Moodle DB if it exists, probably use something like |
||
$table = new xmldb_table('customcert'); | ||
|
||
// Add 'usecustomfilename' field. | ||
$field = new xmldb_field('usecustomfilename', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'deliveryoption'); | ||
if (!$dbman->field_exists($table, $field)) { | ||
$dbman->add_field($table, $field); | ||
} | ||
|
||
// Add 'customfilenamepattern' field. | ||
$field = new xmldb_field('customfilenamepattern', XMLDB_TYPE_TEXT, null, null, null, null, null, 'usecustomfilename'); | ||
if (!$dbman->field_exists($table, $field)) { | ||
$dbman->add_field($table, $field); | ||
} | ||
|
||
// Savepoint reached. | ||
upgrade_mod_savepoint(true, 2024042213, 'customcert'); | ||
} | ||
|
||
return true; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -350,18 +350,26 @@ function customcert_extend_settings_navigation(settings_navigation $settings, na | |
if (has_capability('mod/customcert:manage', $settings->get_page()->cm->context)) { | ||
// Get the template id. | ||
$templateid = $DB->get_field('customcert', 'templateid', ['id' => $settings->get_page()->cm->instance]); | ||
$node = navigation_node::create(get_string('editcustomcert', 'customcert'), | ||
new moodle_url('/mod/customcert/edit.php', ['tid' => $templateid]), | ||
navigation_node::TYPE_SETTING, null, 'mod_customcert_edit', | ||
new pix_icon('t/edit', '')); | ||
$node = navigation_node::create( | ||
get_string('editcustomcert', 'customcert'), | ||
new moodle_url('/mod/customcert/edit.php', ['tid' => $templateid]), | ||
navigation_node::TYPE_SETTING, | ||
null, | ||
'mod_customcert_edit', | ||
new pix_icon('t/edit', '') | ||
); | ||
$customcertnode->add_node($node, $beforekey); | ||
} | ||
|
||
if (has_capability('mod/customcert:verifycertificate', $settings->get_page()->cm->context)) { | ||
$node = navigation_node::create(get_string('verifycertificate', 'customcert'), | ||
$node = navigation_node::create( | ||
get_string('verifycertificate', 'customcert'), | ||
new moodle_url('/mod/customcert/verify_certificate.php', ['contextid' => $settings->get_page()->cm->context->id]), | ||
navigation_node::TYPE_SETTING, null, 'mod_customcert_verify_certificate', | ||
new pix_icon('t/check', '')); | ||
navigation_node::TYPE_SETTING, | ||
null, | ||
'mod_customcert_verify_certificate', | ||
new pix_icon('t/check', '') | ||
); | ||
$customcertnode->add_node($node, $beforekey); | ||
} | ||
|
||
|
@@ -380,8 +388,10 @@ function customcert_extend_settings_navigation(settings_navigation $settings, na | |
function mod_customcert_myprofile_navigation(core_user\output\myprofile\tree $tree, $user, $iscurrentuser, $course) { | ||
global $USER; | ||
|
||
if (($user->id != $USER->id) | ||
&& !has_capability('mod/customcert:viewallcertificates', context_system::instance())) { | ||
if ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks worse than before. Was this change necessary? |
||
($user->id != $USER->id) | ||
&& !has_capability('mod/customcert:viewallcertificates', context_system::instance()) | ||
) { | ||
return; | ||
} | ||
|
||
|
@@ -392,8 +402,13 @@ function mod_customcert_myprofile_navigation(core_user\output\myprofile\tree $tr | |
$params['course'] = $course->id; | ||
} | ||
$url = new moodle_url('/mod/customcert/my_certificates.php', $params); | ||
$node = new core_user\output\myprofile\node('miscellaneous', 'mycustomcerts', | ||
get_string('mycertificates', 'customcert'), null, $url); | ||
$node = new core_user\output\myprofile\node( | ||
'miscellaneous', | ||
'mycustomcerts', | ||
get_string('mycertificates', 'customcert'), | ||
null, | ||
$url | ||
); | ||
$tree->add_node($node); | ||
} | ||
|
||
|
@@ -431,11 +446,71 @@ function mod_customcert_inplace_editable($itemtype, $itemid, $newvalue) { | |
$updateelement->name = clean_param($newvalue, PARAM_TEXT); | ||
$DB->update_record('customcert_elements', $updateelement); | ||
|
||
return new \core\output\inplace_editable('mod_customcert', 'elementname', $element->id, true, | ||
$updateelement->name, $updateelement->name); | ||
return new \core\output\inplace_editable( | ||
'mod_customcert', | ||
'elementname', | ||
$element->id, | ||
true, | ||
$updateelement->name, | ||
$updateelement->name | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Generates a public URL for viewing a user's certificate (eCard). | ||
* | ||
* This function constructs a URL that allows public access to a certificate | ||
* without requiring authentication. It does so by generating a secure token | ||
* based on the certificate code. | ||
* | ||
* @param string $certcode The unique code of the certificate. | ||
* @return string The generated public URL for the certificate. | ||
*/ | ||
function generate_public_url_for_certificate(string $certcode): string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem to be used anywhere. |
||
global $CFG; | ||
|
||
// Generate a security token for the certificate using a private function. | ||
$token = calculate_signature($certcode); | ||
|
||
// Construct and return the public URL to view the certificate. | ||
return $CFG->wwwroot . '/mod/customcert/view_user_cert.php?cert_code=' . urlencode($certcode) . '&token=' . urlencode($token); | ||
} | ||
|
||
/** | ||
* Generates a secure HMAC signature for a certificate. | ||
* | ||
* This function creates a unique signature for a certificate based on its code. | ||
* The signature is used as a security token to verify access to the certificate. | ||
* It prevents unauthorized access by ensuring that only valid certificates can | ||
* be accessed through a generated URL. | ||
* | ||
* The signature is generated using the HMAC (Hash-based Message Authentication Code) | ||
* method with SHA-256, ensuring strong security. It uses Moodle's `siteidentifier` | ||
* as the secret key, making it unique to each Moodle installation. | ||
* | ||
* @param string $certcode The unique certificate code. | ||
* @return string The generated HMAC signature. | ||
*/ | ||
function calculate_signature(string $certcode): string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
global $CFG; | ||
|
||
// Define a namespaced message prefix to avoid signature collisions. | ||
$messageprefix = 'mod_customcert:view_user_cert'; | ||
|
||
// Construct the message that will be signed. | ||
// This includes the prefix and the certificate code to create a unique hash. | ||
$message = $messageprefix . '|' . $certcode; | ||
|
||
// Use Moodle's unique site identifier as the secret key for HMAC. | ||
// This ensures that signatures are installation-specific. | ||
$secret = $CFG->siteidentifier; | ||
|
||
// Generate the HMAC hash using SHA-256. | ||
// This provides a cryptographic signature that is difficult to forge. | ||
return hash_hmac('sha256', $message, $secret); | ||
} | ||
|
||
/** | ||
* Get icon mapping for font-awesome. | ||
*/ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not a fan of using a hard-coded variable like 0. It doesn't mean much to the developer using it.