Skip to content
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

Todas las modificaciones importantes del proyecto se documentarán en este archivo.

## [2.0.4] — 2026-07-28

### Cambiado
- El bloque para personal autorizado muestra un lanzador compacto hacia el panel administrativo, sin métricas ni estadísticas expuestas en la página del curso.
- Se estandarizó el acceso a resultados individuales, la exportación y los textos de permisos en español e inglés.

### Seguridad
- Se añadieron las capacidades explícitas `viewstudentdata` y `deletestudentdata`; la consulta, exportación y eliminación de respuestas quedaron separadas.
- Los roles docentes ya no reciben acceso automático a datos sensibles.
- Se retiró la capacidad heredada e inactiva `viewreports`.

## [2.0.3] — 2026-01-18
- Se eliminaron las comprobaciones redundantes de administrador (`is_siteadmin()`) en varias vistas clave, mejorando la detección correcta de roles locales (profesores vs estudiantes) y el sistema de permisos basado en capacidades.
- Se agrega negrilla en el titulo del bloque para mejorar la visibilidad.
Expand Down
14 changes: 10 additions & 4 deletions admin_view.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
}

// Friendly redirect for unauthorized users
if (!has_capability('block/learning_style:viewreports', $context)) {
if (!has_capability('block/learning_style:viewstudentdata', $context)) {
redirect(new moodle_url('/course/view.php', array('id' => $courseid)));
}

Expand All @@ -48,13 +48,17 @@
$PAGE->set_url($admin_url);

// Handle Delete Action
if ($action === 'delete' && $userid && !has_capability('block/learning_style:deletestudentdata', $context)) {

Check failure on line 51 in admin_view.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "block/learning_style:deletestudentdata" 3 times.

See more on https://sonarcloud.io/project/issues?id=ISCOUTB_AS-dashboard_savio&issues=AZ-nh25JpI4t_4jmh7Oy&open=AZ-nh25JpI4t_4jmh7Oy&pullRequest=69
redirect($admin_url);
}

if ($action === 'delete' && $userid && confirm_sesskey()) {
$confirm = optional_param('confirm', 0, PARAM_INT);
if ($confirm) {
// Privacy check
$targetuser = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
if (!is_enrolled($context, $targetuser, 'block/learning_style:take_test', true)
|| has_capability('block/learning_style:viewreports', $context, $userid)) {
|| has_capability('block/learning_style:viewstudentdata', $context, $userid)) {
redirect(new moodle_url('/course/view.php', array('id' => $courseid)));
}

Expand All @@ -64,7 +68,7 @@
}

$title = get_string('admin_title', 'block_learning_style');
$PAGE->set_pagelayout('standard');
$PAGE->set_pagelayout('incourse');
$PAGE->set_title($title . " : " . $course->fullname);
$PAGE->set_heading($title . " : " . $course->fullname);
$PAGE->requires->css('/blocks/learning_style/styles.css');
Expand All @@ -78,7 +82,8 @@
'admin_url' => $admin_url->out(false),
'export_url' => (new moodle_url('/blocks/learning_style/download_results.php', ['courseid' => $courseid, 'sesskey' => sesskey()]))->out(false),
'course_url' => (new moodle_url('/course/view.php', ['id' => $courseid]))->out(false),
'search_term' => $search
'search_term' => $search,
'can_delete' => has_capability('block/learning_style:deletestudentdata', $context),
];

$user = $DB->get_record('user', array('id' => $userid), 'firstname, lastname');
Expand Down Expand Up @@ -259,6 +264,7 @@
'is_completed' => ($p->is_completed == 1),
'completion_date' => userdate($p->updated_at, get_string('strftimedatetimeshort')),
'view_url' => (new moodle_url('/blocks/learning_style/view_individual.php', ['courseid' => $courseid, 'userid' => $p->user]))->out(false),
'can_delete' => has_capability('block/learning_style:deletestudentdata', $context),
'delete_url' => (new moodle_url('/blocks/learning_style/admin_view.php', ['courseid' => $courseid, 'action' => 'delete', 'userid' => $p->user, 'sesskey' => sesskey()]))->out(false)
];

Expand Down
73 changes: 9 additions & 64 deletions block_learning_style.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public function get_content() {
* Check if user can view dashboard
*/
private function can_view_dashboard($context, $userid = null) {
return has_capability('block/learning_style:viewreports', $context, $userid);
return has_capability('block/learning_style:viewstudentdata', $context, $userid);
}

/**
Expand Down Expand Up @@ -251,70 +251,15 @@ private function render_results($entry) {
* Generate content for teachers/admins
*/
private function get_teacher_content($context) {
global $CFG, $COURSE, $DB, $OUTPUT;

// Header with learning style icon for teacher/admin view.
$icon = $this->get_learning_style_icon('4em', '', true);

// Assets for embedded dashboard.
$this->page->requires->css('/blocks/learning_style/dashboard/css/style.css');
$this->page->requires->js('/blocks/learning_style/dashboard/js/main.js');

// Render dashboard HTML fragment.
$embedded_file = $CFG->dirroot . '/blocks/learning_style/dashboard/embedded.php';
$dashboard_html = '';

if (file_exists($embedded_file)) {
ob_start();
$courseid = $COURSE->id;
include_once($embedded_file);
$dashboard_html = ob_get_clean();
} else {
$dashboard_html = '<p>' . get_string('dashboard_not_found', 'block_learning_style') . '</p>';
}

$template_data = [
'icon' => $icon,
'dashboard_html' => $dashboard_html,
'show_buttons' => false
];

// Agregar botones al final (después del dashboard) para profesores/administradores
$is_teacher = $this->can_view_dashboard($context);
if ($is_teacher) {
$template_data['show_buttons'] = true;
$template_data['admin_url'] = (new moodle_url('/blocks/learning_style/admin_view.php', array('courseid' => $COURSE->id)))->out();

// Determinar si hay tests completados en este curso (solo estudiantes)
$student_users = get_enrolled_users($context, 'block/learning_style:take_test', 0, 'u.id');
$student_ids = array_keys($student_users);

// Defensive: exclude any teacher/manager-type user even if misconfigured.
$filtered_student_ids = array();
foreach ($student_ids as $candidateid) {
$candidateid = (int)$candidateid;

if ($this->can_view_dashboard($context, $candidateid)) {
continue;
}
$filtered_student_ids[] = $candidateid;
}
$student_ids = $filtered_student_ids;

$has_completed = false;
if (!empty($student_ids)) {
list($insql, $params) = $DB->get_in_or_equal($student_ids, SQL_PARAMS_NAMED, 'user');
$completed_count = $DB->count_records_sql("SELECT COUNT(*) FROM {learning_style} WHERE user $insql AND is_completed = 1", $params);
$has_completed = ($completed_count > 0);
}

$template_data['show_download'] = $has_completed;
if ($has_completed) {
$template_data['download_url'] = (new moodle_url('/blocks/learning_style/download_results.php', array('courseid' => $COURSE->id, 'sesskey' => sesskey())))->out(false);
}
}
global $COURSE, $OUTPUT;

return $OUTPUT->render_from_template('block_learning_style/teacher_dashboard', $template_data);
return $OUTPUT->render_from_template('block_learning_style/admin_launcher', [
'icon_html' => $this->get_learning_style_icon('4em', '', true),
'security_label' => get_string('sensitive_data', 'block_learning_style'),
'title' => get_string('management_title', 'block_learning_style'),
'admin_url' => (new moodle_url('/blocks/learning_style/admin_view.php', ['courseid' => $COURSE->id]))->out(false),
'button_label' => get_string('open_admin_panel', 'block_learning_style'),
]);
}

/**
Expand Down
4 changes: 1 addition & 3 deletions dashboard/metrics.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@

$context = context_course::instance($courseid);

$canview = has_capability('block/learning_style:viewreports', $context)
|| has_capability('moodle/course:viewhiddensections', $context)
|| is_siteadmin();
$canview = has_capability('block/learning_style:viewstudentdata', $context);

if (!$canview) {
http_response_code(403);
Expand Down
4 changes: 1 addition & 3 deletions dashboard/view.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
$context = context_course::instance($courseid);

// Check permissions
$canview = has_capability('block/learning_style:viewreports', $context)
|| has_capability('moodle/course:viewhiddensections', $context)
|| is_siteadmin();
$canview = has_capability('block/learning_style:viewstudentdata', $context);

if (!$canview) {
$redirecturl = new moodle_url('/course/view.php', array('id' => $courseid));
Expand Down
23 changes: 13 additions & 10 deletions db/access.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,24 @@
'clonepermissionsfrom' => 'moodle/site:manageblocks'
),

'block/learning_style:viewreports' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),

'block/learning_style:take_test' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'student' => CAP_ALLOW
)
),

// Datos personales: no se asignan automáticamente a roles docentes.
'block/learning_style:viewstudentdata' => array(
'riskbitmask' => RISK_PERSONAL,
'captype' => 'read',
'contextlevel' => CONTEXT_COURSE
),

'block/learning_style:deletestudentdata' => array(
'riskbitmask' => RISK_DATALOSS | RISK_PERSONAL,
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE
),
);
4 changes: 2 additions & 2 deletions download_results.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function block_learning_style_csv_safe($value): string {

// Requerir inicio de sesión
require_login($course, false);
require_capability('block/learning_style:viewreports', $context);
require_capability('block/learning_style:viewstudentdata', $context);

// Obtener solo estudiantes (usuarios que pueden tomar el test)
$enrolled_users = get_enrolled_users($context, 'block/learning_style:take_test', 0, 'u.id');
Expand All @@ -45,7 +45,7 @@ function block_learning_style_csv_safe($value): string {
foreach ($enrolled_ids as $candidateid) {
$candidateid = (int)$candidateid;

if (has_capability('block/learning_style:viewreports', $context, $candidateid)) {
if (has_capability('block/learning_style:viewstudentdata', $context, $candidateid)) {
continue;
}
$student_ids[] = $candidateid;
Expand Down
5 changes: 5 additions & 0 deletions lang/en/block_learning_style.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
<?php
$string['learning_style:viewstudentdata'] = 'View sensitive student data';
$string['learning_style:deletestudentdata'] = 'Delete sensitive student data';
$string['learning_style:take_test'] = 'Take the learning styles exploration';
$string['sensitive_data'] = 'Sensitive data';
$string['open_admin_panel'] = 'Open administration panel';
$string['pluginname'] = 'Learning Styles Exploration';
$string['management_title'] = 'Learning Styles Exploration Management';
$string['course_overview'] = 'Course Overview';
Expand Down
5 changes: 5 additions & 0 deletions lang/es/block_learning_style.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
<?php
$string['learning_style:viewstudentdata'] = 'Ver datos sensibles de estudiantes';
$string['learning_style:deletestudentdata'] = 'Eliminar datos sensibles de estudiantes';
$string['learning_style:take_test'] = 'Realizar la exploración de estilos de aprendizaje';
$string['sensitive_data'] = 'Datos sensibles';
$string['open_admin_panel'] = 'Abrir panel de administración';
$string['pluginname'] = 'Exploración de Estilos de Aprendizaje';
$string['management_title'] = 'Gestión - Exploración de Estilos de Aprendizaje';
$string['course_overview'] = 'Resumen del Curso';
Expand Down
6 changes: 6 additions & 0 deletions templates/admin_launcher.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div class="savio-admin-launcher text-center" style="padding: 1.25rem 1rem; border: 1px solid #dbe8ff; border-radius: 14px; background: linear-gradient(145deg, #fbfdff 0%, #edf5ff 100%); box-shadow: 0 8px 20px rgba(21, 103, 249, .10);">
<div style="margin: 0 auto .85rem; line-height: 0;">{{{icon_html}}}</div>
<div class="small font-weight-bold text-uppercase" style="letter-spacing: .07em; color: #1567f9;"><i class="fa fa-lock mr-1"></i>{{security_label}}</div>
<h6 class="mt-2 mb-2 font-weight-bold">{{title}}</h6>
<a href="{{admin_url}}" class="btn btn-primary btn-sm btn-block" style="background: linear-gradient(135deg, #1567f9 0%, #0054ce 100%); border: 0; color: #fff !important; font-weight: 700; border-radius: 8px;"><i class="fa fa-arrow-right mr-1"></i>{{button_label}}</a>
</div>
8 changes: 5 additions & 3 deletions templates/admin_view.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,11 @@
<a href="{{view_url}}" class="btn btn-sm btn-info mr-2" title="{{#str}}view_details, block_learning_style{{/str}}">
<i class="fa fa-eye"></i> {{#str}}view_details, block_learning_style{{/str}}
</a>
<a href="{{delete_url}}" class="btn btn-sm btn-danger" title="{{#str}}delete{{/str}}">
<i class="fa fa-trash"></i> {{#str}}delete{{/str}}
</a>
{{#can_delete}}
<a href="{{delete_url}}" class="btn btn-sm btn-danger" title="{{#str}}delete{{/str}}">
<i class="fa fa-trash"></i> {{#str}}delete{{/str}}
</a>
{{/can_delete}}
</td>
</tr>
{{/list}}
Expand Down
4 changes: 2 additions & 2 deletions version.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

defined('MOODLE_INTERNAL') || die();

$plugin->version = 2026011801; // YYYYMMDDXX (year, month, day, 2-digit version number).
$plugin->version = 2026072800; // YYYYMMDDXX (year, month, day, 2-digit version number).
$plugin->requires = 2022041900; // Moodle 4.0+
$plugin->component = 'block_learning_style';
$plugin->maturity = MATURITY_STABLE;
$plugin->release = '2.0.3';
$plugin->release = '2.0.4';
2 changes: 1 addition & 1 deletion view.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
}

// Redirect teachers/admins to admin page
if (has_capability('block/learning_style:viewreports', $context)) {
if (has_capability('block/learning_style:viewstudentdata', $context)) {
$manage_url = new moodle_url('/blocks/learning_style/admin_view.php', array('cid' => $courseid));
redirect($manage_url, get_string('teachers_redirect_message', 'block_learning_style'));
}
Expand Down
2 changes: 1 addition & 1 deletion view_individual.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

// Security: Check permissions and enrollment
$is_own_results = ($USER->id == $userid);
$can_view_reports = has_capability('block/learning_style:viewreports', $context);
$can_view_reports = has_capability('block/learning_style:viewstudentdata', $context);

// Basic access check: If not owner, not teacher, and not admin -> Kick out
if (!$is_own_results && !$can_view_reports) {
Expand Down
Loading