Skip to content

Commit b94ca6e

Browse files
author
Felix Manrique
committed
update: 2.0.0 - the post revision (needs further verification and changes)
1 parent f905909 commit b94ca6e

16 files changed

Lines changed: 283 additions & 55 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
/vendor/
2+
/issues-resolver/
3+

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# Changelog
22

3+
## [2.0.0] - 2026-03-03
4+
### The Post Revision Update
5+
6+
### Fixed
7+
- **Hard-coded Language Strings:** Replaced all hard-coded user-facing text with proper `get_string()` calls for full internationalization support. Added 27 new language strings for CSV report headers, deprecated function descriptions, and unsafe function warnings across all supported languages (en, es, fr, it, pt).
8+
- **Non-English Comments:** Translated all Spanish comments in `db/upgrade.php` to English for international collaboration compliance.
9+
- **Database Performance (N+1 Queries):** Optimized `dashboard.php` role heatmap generation by preloading all roles in a single bulk query using `get_in_or_equal()`, eliminating N+1 query problem in role risk display.
10+
- **Third-Party Library Documentation:** Created `thirdpartylibs.xml` to properly document Chart.js v4.5.1 (MIT License) as required by Moodle plugin guidelines.
11+
12+
### Changed
13+
- **Plugin Name:** Rebranded from "Moodle Risk & Compliance Analyzer" to "Risk & Compliance Analyzer for Moodle" across all language files and CLI scripts for better naming consistency.
14+
- **CSV Report Generation:** All CSV headers and labels now use language strings from `get_string()` instead of hard-coded English text, enabling proper translation.
15+
- **Deprecated Functions Detection:** Refactored `structural_scanner.php` to store language string keys instead of hard-coded messages, with runtime translation via `get_string()`.
16+
17+
### Added
18+
- **Language Strings:** Added 27 new translatable strings for deprecated Moodle functions (`dep_func_*`) and unsafe PHP functions (`unsafe_func_*`) across all 5 supported languages (en, es, fr, it, pt).
19+
20+
321
## [1.5.0] - 2026-02-26
422

523
### Patch & Chill Update

amd/src/dashboard.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ define(
158158
// ===== PII FIELD TOGGLE =====
159159
$(document).on('click', '.mrca-toggle-pii', function (e) {
160160
e.preventDefault();
161-
var target = $($(this).data('target'));
161+
var target = $($(this).data('bs-target') || $(this).data('target'));
162162
target.toggle();
163163
// Toggle icon.
164164
var icon = $(this).find('i');

classes/reporting/csv_generator.php

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,24 +54,31 @@ public function generate_report(int $scanid): void {
5454
$output = fopen('php://output', 'w');
5555

5656
// Info header.
57-
fputcsv($output, ['MRCA Scan Report']);
58-
fputcsv($output, ['Scan Date', userdate($scan->timecreated)]);
59-
fputcsv($output, ['Total Score', $scan->total_score]);
60-
fputcsv($output, ['Site Risk Index', round($scan->site_risk_index, 1) . '/100']);
61-
fputcsv($output, ['Plugins Scanned', $scan->plugins_scanned]);
62-
fputcsv($output, ['Roles Scanned', $scan->roles_scanned]);
57+
fputcsv($output, [get_string('csv_header_report_title', 'local_mrca')]);
58+
fputcsv($output, [get_string('csv_label_scan_date', 'local_mrca'), userdate($scan->timecreated)]);
59+
fputcsv($output, [get_string('csv_label_total_score', 'local_mrca'), $scan->total_score]);
60+
fputcsv($output, [get_string('csv_label_site_risk_index', 'local_mrca'), round($scan->site_risk_index, 1) . '/100']);
61+
fputcsv($output, [get_string('csv_label_plugins_scanned', 'local_mrca'), $scan->plugins_scanned]);
62+
fputcsv($output, [get_string('csv_label_roles_scanned', 'local_mrca'), $scan->roles_scanned]);
6363
fputcsv($output, []);
6464

6565
// Results.
66-
fputcsv($output, ['Plugin', 'Risk Score', 'Risk Level', 'Privacy API']);
66+
fputcsv($output, [
67+
get_string('csv_header_plugin', 'local_mrca'),
68+
get_string('csv_header_risk_score', 'local_mrca'),
69+
get_string('csv_header_risk_level', 'local_mrca'),
70+
get_string('csv_header_privacy_api', 'local_mrca'),
71+
]);
6772

6873
foreach ($results as $result) {
6974
$level = $engine->get_risk_level($result->risk_score);
7075
fputcsv($output, [
7176
$result->plugin,
7277
$result->risk_score,
7378
ucfirst($level),
74-
$result->has_privacy_provider ? 'Yes' : 'No',
79+
$result->has_privacy_provider ?
80+
get_string('csv_label_yes', 'local_mrca') :
81+
get_string('csv_label_no', 'local_mrca'),
7582
]);
7683
}
7784

classes/reporting/dashboard.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,11 +421,19 @@ private function add_dependency_audit(array $data, $db, \stdClass $scan): array
421421
private function add_role_heatmap(array $data, $db, \stdClass $scan): array {
422422
$rolerisks = $db->get_records('local_mrca_role_risks', ['scanid' => $scan->id], 'risk_score DESC');
423423

424+
// Preload all roles to avoid N+1 query problem.
425+
$roleids = array_column($rolerisks, 'roleid');
426+
$roles = [];
427+
if (!empty($roleids)) {
428+
list($insql, $inparams) = $db->get_in_or_equal($roleids, SQL_PARAMS_NAMED);
429+
$roles = $db->get_records_select('role', "id $insql", $inparams, '', 'id,shortname');
430+
}
431+
424432
foreach ($rolerisks as $rr) {
425-
$role = $db->get_record('role', ['id' => $rr->roleid]);
426-
if (!$role) {
433+
if (!isset($roles[$rr->roleid])) {
427434
continue;
428435
}
436+
$role = $roles[$rr->roleid];
429437

430438
$heatmapclass = 'success';
431439
$emoji = '🟢';

classes/scanners/structural_scanner.php

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -62,33 +62,33 @@ class structural_scanner {
6262

6363
/** @var array Known deprecated Moodle functions to detect. */
6464
private const DEPRECATED_FUNCTIONS = [
65-
'print_object' => 'Use debugging() or var_dump for debugging.',
66-
'print_header' => 'Replaced by $OUTPUT->header().',
67-
'print_footer' => 'Replaced by $OUTPUT->footer().',
68-
'print_heading' => 'Replaced by $OUTPUT->heading().',
69-
'print_table' => 'Replaced by html_writer::table().',
70-
'print_simple_box' => 'Deprecated UI function.',
71-
'choose_from_menu' => 'Replaced by html_writer::select().',
72-
'helpbutton' => 'Replaced by $OUTPUT->help_icon().',
73-
'print_recent_activity_note' => 'Deprecated activity function.',
74-
'get_context_instance' => 'Use context_*::instance() instead.',
75-
'add_to_log' => 'Replaced by Events API (\\core\\event).',
76-
'events_trigger' => 'Replaced by Events 2 API.',
77-
'print_error' => 'Use throw new moodle_exception() instead.',
65+
'print_object' => 'dep_func_print_object',
66+
'print_header' => 'dep_func_print_header',
67+
'print_footer' => 'dep_func_print_footer',
68+
'print_heading' => 'dep_func_print_heading',
69+
'print_table' => 'dep_func_print_table',
70+
'print_simple_box' => 'dep_func_print_simple_box',
71+
'choose_from_menu' => 'dep_func_choose_from_menu',
72+
'helpbutton' => 'dep_func_helpbutton',
73+
'print_recent_activity_note' => 'dep_func_print_recent_activity_note',
74+
'get_context_instance' => 'dep_func_get_context_instance',
75+
'add_to_log' => 'dep_func_add_to_log',
76+
'events_trigger' => 'dep_func_events_trigger',
77+
'print_error' => 'dep_func_print_error',
7878
];
7979

8080
/** @var array PHP functions that should not be used in Moodle plugins. */
8181
private const UNSAFE_PHP_FUNCTIONS = [
82-
'eval' => 'Arbitrary code execution risk.',
83-
'exec' => 'System command execution.',
84-
'shell_exec' => 'System command execution.',
85-
'passthru' => 'System command execution.',
86-
'popen' => 'Process execution.',
87-
'proc_open' => 'Process execution.',
88-
'dl' => 'Dynamic extension loading.',
89-
'mysql_query' => 'Deprecated MySQL extension. Use $DB API.',
90-
'mysql_connect' => 'Deprecated MySQL extension. Use $DB API.',
91-
'mysqli_query' => 'Direct DB access. Use Moodle $DB API.',
82+
'eval' => 'unsafe_func_eval',
83+
'exec' => 'unsafe_func_exec',
84+
'shell_exec' => 'unsafe_func_shell_exec',
85+
'passthru' => 'unsafe_func_passthru',
86+
'popen' => 'unsafe_func_popen',
87+
'proc_open' => 'unsafe_func_proc_open',
88+
'dl' => 'unsafe_func_dl',
89+
'mysql_query' => 'unsafe_func_mysql_query',
90+
'mysql_connect' => 'unsafe_func_mysql_connect',
91+
'mysqli_query' => 'unsafe_func_mysqli_query',
9292
];
9393

9494
/**
@@ -228,15 +228,23 @@ private function scan_php_sources(string $dir, array &$findings, int &$score): v
228228
*/
229229
private function scan_content(string $content, string $relative, array &$findings): int {
230230
$score = 0;
231-
foreach (self::DEPRECATED_FUNCTIONS as $func => $reason) {
231+
foreach (self::DEPRECATED_FUNCTIONS as $func => $stringkey) {
232232
if ($this->contains_function_call($content, $func)) {
233-
$findings['deprecated_calls'][] = ['file' => $relative, 'function' => $func, 'reason' => $reason];
233+
$findings['deprecated_calls'][] = [
234+
'file' => $relative,
235+
'function' => $func,
236+
'reason' => get_string($stringkey, 'local_mrca'),
237+
];
234238
$score += self::SCORE_DEPRECATED_CALL;
235239
}
236240
}
237-
foreach (self::UNSAFE_PHP_FUNCTIONS as $func => $reason) {
241+
foreach (self::UNSAFE_PHP_FUNCTIONS as $func => $stringkey) {
238242
if ($this->contains_function_call($content, $func)) {
239-
$findings['unsafe_calls'][] = ['file' => $relative, 'function' => $func, 'reason' => $reason];
243+
$findings['unsafe_calls'][] = [
244+
'file' => $relative,
245+
'function' => $func,
246+
'reason' => get_string($stringkey, 'local_mrca'),
247+
];
240248
$score += self::SCORE_DEPRECATED_CALL;
241249
}
242250
}

cli/run_scan_cli.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
[$options, $unrecognised] = cli_get_params(['help' => false], ['h' => 'help']);
3232

3333
if ($options['help']) {
34-
echo "MRCA — Moodle Risk & Compliance Analyzer CLI Scan
34+
echo "MRCA — Risk & Compliance Analyzer for Moodle CLI Scan
3535
3636
Usage:
3737
php run_scan_cli.php [--help]
@@ -48,7 +48,7 @@
4848
}
4949

5050
mtrace("===================================================");
51-
mtrace(" MRCA — Moodle Risk & Compliance Analyzer");
51+
mtrace(" MRCA — Risk & Compliance Analyzer for Moodle");
5252
mtrace(" Starting CLI Scan...");
5353
mtrace("===================================================");
5454

db/upgrade.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@
3030
* @return bool Always returns true.
3131
*/
3232
function xmldb_local_mrca_upgrade($oldversion) {
33-
global $DB; // Moodle pide que esté aquí aunque no se use en este bloque.
33+
global $DB; // Required by Moodle, even if not used in this block.
3434

3535
if ($oldversion < 2026022400) {
3636
// MRCA Golden Release 1.1.5 upgrade step.
37-
// Aquí podrías poner lógica de base de datos, pero el savepoint es OBLIGATORIO.
37+
// Database schema logic can be placed here, but the savepoint is MANDATORY.
3838
upgrade_plugin_savepoint(true, 2026022400, 'local', 'mrca');
3939
}
4040

lang/en/local_mrca.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,37 @@
4040
$string['capability_score'] = 'Capability Score';
4141
$string['confirm_scan'] = 'This will run a complete risk & compliance scan. Continue?';
4242
$string['correlation_engine'] = 'Correlation Engine';
43+
$string['csv_header_plugin'] = 'Plugin';
44+
$string['csv_header_privacy_api'] = 'Privacy API';
45+
$string['csv_header_report_title'] = 'MRCA Scan Report';
46+
$string['csv_header_risk_level'] = 'Risk Level';
47+
$string['csv_header_risk_score'] = 'Risk Score';
48+
$string['csv_label_no'] = 'No';
49+
$string['csv_label_plugins_scanned'] = 'Plugins Scanned';
50+
$string['csv_label_roles_scanned'] = 'Roles Scanned';
51+
$string['csv_label_scan_date'] = 'Scan Date';
52+
$string['csv_label_site_risk_index'] = 'Site Risk Index';
53+
$string['csv_label_total_score'] = 'Total Score';
54+
$string['csv_label_yes'] = 'Yes';
4355
$string['correlation_engine_desc'] = 'Systemic risk detection by correlating findings across all layers.';
4456
$string['critical_caps'] = 'Critical Capabilities';
4557
$string['dashboard_desc'] = 'Comprehensive risk analysis for your Moodle installation.';
4658
$string['dashboard_title'] = 'MRCA Dashboard';
4759
$string['dep_core_mismatch'] = 'Core version mismatch — plugin requires a newer Moodle version.';
4860
$string['dep_deprecated_apis'] = '{$a} deprecated API call(s) detected.';
61+
$string['dep_func_add_to_log'] = 'Replaced by Events API (\\core\\event).';
62+
$string['dep_func_choose_from_menu'] = 'Replaced by html_writer::select().';
63+
$string['dep_func_events_trigger'] = 'Replaced by Events 2 API.';
64+
$string['dep_func_get_context_instance'] = 'Use context_*::instance() instead.';
65+
$string['dep_func_helpbutton'] = 'Replaced by $OUTPUT->help_icon().';
66+
$string['dep_func_print_error'] = 'Use throw new moodle_exception() instead.';
67+
$string['dep_func_print_footer'] = 'Replaced by $OUTPUT->footer().';
68+
$string['dep_func_print_header'] = 'Replaced by $OUTPUT->header().';
69+
$string['dep_func_print_heading'] = 'Replaced by $OUTPUT->heading().';
70+
$string['dep_func_print_object'] = 'Use debugging() or var_dump for debugging.';
71+
$string['dep_func_print_recent_activity_note'] = 'Deprecated activity function.';
72+
$string['dep_func_print_simple_box'] = 'Deprecated UI function.';
73+
$string['dep_func_print_table'] = 'Replaced by html_writer::table().';
4974
$string['dep_issues'] = 'Issues';
5075
$string['dep_missing'] = 'Missing dependency: {$a}';
5176
$string['dep_outdated'] = 'Plugin version is outdated (not updated in 2+ years).';
@@ -80,7 +105,7 @@
80105
$string['no_scans_yet'] = 'No scans have been run yet. Click "Scan Now" to start your first analysis.';
81106
$string['plugin'] = 'Plugin';
82107
$string['plugin_risk_details'] = 'Plugin Risk Details';
83-
$string['pluginname'] = 'Moodle Risk & Compliance Analyzer';
108+
$string['pluginname'] = 'Risk & Compliance Analyzer for Moodle';
84109
$string['plugins_scanned'] = 'Plugins Scanned';
85110
$string['privacy:metadata:whitelist'] = 'Records of fields whitelisted by administrators during risk scans.';
86111
$string['privacy:metadata:whitelist:component'] = 'The plugin component the whitelisted field belongs to.';
@@ -149,6 +174,16 @@
149174
$string['top_risky_plugins'] = 'Top 5 Risky Plugins';
150175
$string['top_risky_roles'] = 'Top 5 Risky Roles';
151176
$string['total_score'] = 'Total Score';
177+
$string['unsafe_func_dl'] = 'Dynamic extension loading.';
178+
$string['unsafe_func_eval'] = 'Arbitrary code execution risk.';
179+
$string['unsafe_func_exec'] = 'System command execution.';
180+
$string['unsafe_func_mysqli_query'] = 'Direct DB access. Use Moodle $DB API.';
181+
$string['unsafe_func_mysql_connect'] = 'Deprecated MySQL extension. Use $DB API.';
182+
$string['unsafe_func_mysql_query'] = 'Deprecated MySQL extension. Use $DB API.';
183+
$string['unsafe_func_passthru'] = 'System command execution.';
184+
$string['unsafe_func_popen'] = 'Process execution.';
185+
$string['unsafe_func_proc_open'] = 'Process execution.';
186+
$string['unsafe_func_shell_exec'] = 'System command execution.';
152187
$string['verified_encrypted'] = 'Content verified as encrypted';
153188
$string['verified_plaintext'] = 'Content detected as plaintext';
154189
$string['webhook_token'] = 'Webhook Token';

lang/es/local_mrca.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@
4040
$string['capability_score'] = 'Puntuación de Capacidades';
4141
$string['confirm_scan'] = 'Esto ejecutará un escaneo completo de riesgos y cumplimiento. ¿Continuar?';
4242
$string['correlation_engine'] = 'Motor de Correlación';
43+
$string['csv_header_plugin'] = 'Plugin';
44+
$string['csv_header_privacy_api'] = 'API de Privacidad';
45+
$string['csv_header_report_title'] = 'Informe de Riesgos y Cumplimiento de MRCA';
46+
$string['csv_header_risk_level'] = 'Nivel de Riesgo';
47+
$string['csv_header_risk_score'] = 'Puntuación de Riesgo';
48+
$string['csv_label_no'] = 'No';
49+
$string['csv_label_plugins_scanned'] = 'Plugins Escaneados';
50+
$string['csv_label_roles_scanned'] = 'Roles Escaneados';
51+
$string['csv_label_scan_date'] = 'Fecha del Escaneo';
52+
$string['csv_label_site_risk_index'] = 'Índice de Riesgo del Sitio';
53+
$string['csv_label_total_score'] = 'Puntuación Total';
54+
$string['csv_label_yes'] = '';
4355
$string['correlation_engine_desc'] = 'Detección de riesgos sistémicos correlacionando hallazgos de todas las capas.';
4456
$string['critical_caps'] = 'Capacidades Críticas';
4557
$string['dashboard_desc'] = 'Análisis integral de riesgos para tu instalación Moodle.';
@@ -49,6 +61,19 @@
4961
$string['dep_issues'] = 'Problemas';
5062
$string['dep_missing'] = 'Dependencia faltante: {$a}';
5163
$string['dep_outdated'] = 'La versión del plugin es antigua (sin actualización en más de 2 años).';
64+
$string['dep_func_add_to_log'] = 'Reemplazado por Events API (\\core\\event).';
65+
$string['dep_func_choose_from_menu'] = 'Reemplazado por html_writer::select().';
66+
$string['dep_func_events_trigger'] = 'Reemplazado por Events 2 API.';
67+
$string['dep_func_get_context_instance'] = 'Usar context_*::instance() en su lugar.';
68+
$string['dep_func_helpbutton'] = 'Reemplazado por $OUTPUT->help_icon().';
69+
$string['dep_func_print_error'] = 'Usar throw new moodle_exception() en su lugar.';
70+
$string['dep_func_print_footer'] = 'Reemplazado por $OUTPUT->footer().';
71+
$string['dep_func_print_header'] = 'Reemplazado por $OUTPUT->header().';
72+
$string['dep_func_print_heading'] = 'Reemplazado por $OUTPUT->heading().';
73+
$string['dep_func_print_object'] = 'Usar debugging() o var_dump para depuración.';
74+
$string['dep_func_print_recent_activity_note'] = 'Función de actividad obsoleta.';
75+
$string['dep_func_print_simple_box'] = 'Función de UI obsoleta.';
76+
$string['dep_func_print_table'] = 'Reemplazado por html_writer::table().';
5277
$string['dependency_audit'] = 'Auditoría de Dependencias';
5378
$string['dependency_scanner'] = 'Capa de Dependencias y Compatibilidad';
5479
$string['dependency_scanner_desc'] = 'Requisitos de plugins, compatibilidad con el core y detección de APIs obsoletas.';
@@ -149,6 +174,16 @@
149174
$string['top_risky_plugins'] = 'Top 5 Plugins Más Riesgosos';
150175
$string['top_risky_roles'] = 'Top 5 Roles Más Riesgosos';
151176
$string['total_score'] = 'Puntuación Total';
177+
$string['unsafe_func_dl'] = 'Carga dinámica de extensiones.';
178+
$string['unsafe_func_eval'] = 'Riesgo de ejecución arbitraria de código.';
179+
$string['unsafe_func_exec'] = 'Ejecución de comandos del sistema.';
180+
$string['unsafe_func_mysqli_query'] = 'Acceso directo a BD. Usar la API $DB de Moodle.';
181+
$string['unsafe_func_mysql_connect'] = 'Extensión MySQL obsoleta. Usar la API $DB.';
182+
$string['unsafe_func_mysql_query'] = 'Extensión MySQL obsoleta. Usar la API $DB.';
183+
$string['unsafe_func_passthru'] = 'Ejecución de comandos del sistema.';
184+
$string['unsafe_func_popen'] = 'Ejecución de procesos.';
185+
$string['unsafe_func_proc_open'] = 'Ejecución de procesos.';
186+
$string['unsafe_func_shell_exec'] = 'Ejecución de comandos del sistema.';
152187
$string['verified_encrypted'] = 'Contenido verificado como cifrado';
153188
$string['verified_plaintext'] = 'Contenido detectado como texto plano';
154189
$string['webhook_token'] = 'Token del Webhook';

0 commit comments

Comments
 (0)