Skip to content
Open
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: 10 additions & 5 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,16 @@ is **not** debt.

## Functional classification

`exelearning_supports()` declares `MOD_ARCHETYPE_ASSIGNMENT` + `MOD_PURPOSE_ASSESSMENT`
(`lib.php:52-79`). These are resolved per **module type**, not per instance, so they do
not vary with the per-activity `gradeenabled` switch ([[DEC-13-07]]); `gradeenabled = 0`
is a resource-like mode within an assessment-archetype module. Decision recorded in
[[DEC-37-01]]; see `docs/AUDIT_FOLLOWUP.md`.
`exelearning_supports()` keeps the default archetype (`MOD_ARCHETYPE_OTHER`, which the
4.5 and 5.0 activity chooser needs to list the module under "Activities") and declares
`MOD_PURPOSE_INTERACTIVECONTENT` as primary purpose and, on Moodle 5.1+,
`MOD_PURPOSE_ASSESSMENT` as secondary purpose (`FEATURE_MOD_OTHERPURPOSE`, guarded
with `defined()` because 4.5 and 5.0 lack it). This matches core's own modules for
interactive content (`mod_h5pactivity`, `mod_lesson`). The purpose only places the
activity in the chooser: the icon is branded (`exelearning_is_branded()`), so it is
never tinted. These are resolved per **module type**, not per instance, so they do
not vary with the per-activity `gradeenabled` switch ([[DEC-13-07]]). Decision
recorded in [[DEC-159-01]], which supersedes [[DEC-37-01]].

## Global search

Expand Down
4 changes: 2 additions & 2 deletions docs/GRADEBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ History recorded while the activity **was** graded is untouched by all of this:
**Caveat**: `FEATURE_GRADE_HAS_GRADE` is **static** — `exelearning_supports()` returns `true` unconditionally
(`lib.php:66-67`), regardless of `gradeenabled`. So Moodle still classifies the activity type as gradable even when a
given instance is not. This functional classification mismatch is tracked in the audit follow-up — see the new ADR
**DEC-37-01** (functional classification) and `docs/AUDIT_FOLLOWUP.md`.
**DEC-37-01**, superseded by **DEC-159-01** (functional classification), and `docs/AUDIT_FOLLOWUP.md`.

## Worked example

Expand Down Expand Up @@ -197,4 +197,4 @@ The Grading and Attempts sections of the activity form (`mod_form.php:78-227`),
for the single-channel SCORM 1.2 pipeline).
- `docs/PRIVACY_BACKUP_FILES.md` — backup/restore of `exelearning_grade_item` and attempt data
(`backup/moodle2/backup_exelearning_stepslib.php`).
- `research/decisiones/adr/` — DEC-0-08, DEC-0-10, DEC-5-01, DEC-12-01, DEC-13-07, DEC-25-01, DEC-37-01, DEC-69-01.
- `research/decisiones/adr/` — DEC-0-08, DEC-0-10, DEC-5-01, DEC-12-01, DEC-13-07, DEC-25-01, DEC-37-01, DEC-69-01, DEC-159-01.
17 changes: 11 additions & 6 deletions lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,13 @@
* @return mixed
*/
function exelearning_supports($feature) {
// Moodle 5.1+ only: a secondary purpose also lists the activity under
// assessment (DEC-159-01). Resolved here, not as a switch case: evaluating the
// undefined constant as a case would throw on 4.5 and 5.0.
$otherpurpose = defined('FEATURE_MOD_OTHERPURPOSE') ? constant('FEATURE_MOD_OTHERPURPOSE') : null;
// No FEATURE_MOD_ARCHETYPE: the default (MOD_ARCHETYPE_OTHER) is what lists the
// module under the chooser's "Activities" tab on 4.5 and 5.0 (DEC-159-01).
switch ($feature) {
case FEATURE_MOD_ARCHETYPE:
return MOD_ARCHETYPE_ASSIGNMENT;
case FEATURE_GROUPS:
return true;
case FEATURE_GROUPINGS:
Expand All @@ -72,18 +76,19 @@ function exelearning_supports($feature) {
case FEATURE_SHOW_DESCRIPTION:
return true;
case FEATURE_MOD_PURPOSE:
return MOD_PURPOSE_ASSESSMENT;
// Interactive content that may carry graded iDevices, like SCORM, H5P
// and Lesson (DEC-159-01, supersedes DEC-37-01).
return MOD_PURPOSE_INTERACTIVECONTENT;
default:
return null;
return ($otherpurpose !== null && $feature === $otherpurpose) ? MOD_PURPOSE_ASSESSMENT : null;
}
}

/**
* Whether the activity icon is branded.
*
* Branded icons keep their own colours: Moodle skips the purpose colour filter
* that would otherwise tint the monologo pink for MOD_PURPOSE_ASSESSMENT
* (exelearning/exelearning issue 2453). The purpose itself stays unchanged.
* that would otherwise tint the monologo (exelearning/exelearning issue 2453).
*
* @return bool Always true, so pix/monologo.svg renders in the official colour.
*/
Expand Down
80 changes: 80 additions & 0 deletions research/decisiones/adr/DEC-159-01-proposito-interactivecontent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
id: DEC-159-01
title: "Propósito del módulo: MOD_PURPOSE_INTERACTIVECONTENT como primario y ASSESSMENT como secundario"
status: Accepted
date: 2026-09-28
tracking_issue: 159
supersedes: [DEC-37-01]
deciders:
- erseco
- claude-code
sources:
- REPO-004
related:
adrs: [DEC-13-07, DEC-0-15]
ai_assistance:
tool: claude-code
model: claude-opus-5-5
---

# DEC-159-01: Propósito del módulo: MOD_PURPOSE_INTERACTIVECONTENT como primario y ASSESSMENT como secundario

## Contexto

`DEC-37-01` mantuvo `MOD_PURPOSE_ASSESSMENT` comparándolo solo con `RESOURCE`/`CONTENT`.
No valoró `MOD_PURPOSE_INTERACTIVECONTENT`, que es el propósito que core usa para los
módulos más parecidos. Como efecto secundario, el propósito teñía el icono de rosa
(exelearning/exelearning issue 2453); `exelearning_is_branded()` ya lo evita, así que el
propósito solo decide dónde aparece la actividad en el selector y en su filtro por
propósito. La propuesta es el issue exelearning/exelearning#2454 (los issues del plugin
se centralizan allí); como no hay issue en este repositorio, el ADR usa el número del
PR 159.

## Evidencia

Consultado en `moodle/moodle` el 2026-09-28 (`gh api repos/moodle/moodle/contents/...`):

- `FEATURE_MOD_OTHERPURPOSE` se define en `public/lib/moodlelib.php:488` de
`MOODLE_501_STABLE` y `lib/moodlelib.php:488` de `MOODLE_502_STABLE`; no existe en
`MOODLE_405_STABLE` ni en `MOODLE_500_STABLE`.
- `MOD_PURPOSE_INTERACTIVECONTENT` existe en todas las ramas soportadas (4.5–5.2).
- `public/mod/h5pactivity/lib.php:58-59` (`MOODLE_501_STABLE`) declara
`FEATURE_MOD_PURPOSE => MOD_PURPOSE_INTERACTIVECONTENT` y
`FEATURE_MOD_OTHERPURPOSE => MOD_PURPOSE_ASSESSMENT`, sin archetype propio.

| Módulo | Propósito primario | Secundario (5.1+) |
|---|---|---|
| `mod_scorm` | `INTERACTIVECONTENT` | `CONTENT` |
| `mod_h5pactivity` | `INTERACTIVECONTENT` | `ASSESSMENT` |
| `mod_lesson` | `INTERACTIVECONTENT` | `ASSESSMENT` |

## Decisión

- Propósito primario: `MOD_PURPOSE_INTERACTIVECONTENT`.
- Propósito secundario: `MOD_PURPOSE_ASSESSMENT`, como H5P y Lesson. `CONTENT` (el de
SCORM) encaja peor: el plugin califica por iDevice y sincroniza con el gradebook.
- `FEATURE_MOD_OTHERPURPOSE` se comprueba con `defined()` antes del `switch` de
`exelearning_supports()`. Evaluar la constante como `case` lanzaría un `Error` en 4.5
y 5.0 para cualquier feature no reconocida.
- Se retira `MOD_ARCHETYPE_ASSIGNMENT` y el módulo usa el archetype por defecto
(`MOD_ARCHETYPE_OTHER`), como SCORM, H5P, Lesson y la propia Tarea. En 4.5 y 5.0 el
selector reparte las pestañas por archetype: `course/amd/src/activitychooser.js:261-262`
(imagen `erseco/alpine-moodle:v5.0.7`) filtra "Actividades" con `archetype === 0` y
"Recursos" con `archetype === 1`. Con el valor 2 el módulo solo salía en "Todos".
Core no usa `MOD_ARCHETYPE_ASSIGNMENT` en ningún otro sitio (solo lo define en
`lib/moodlelib.php:481`), así que los valores por defecto de grupos y finalización
que citaba `DEC-37-01` vienen de sus propias features, no del archetype.

## Consecuencias

- **Positivas:** la actividad aparece junto a SCORM y H5P, que es donde la buscan los
docentes; en 4.5 y 5.0 vuelve a salir en la pestaña "Actividades"; sigue la taxonomía
de core; en 5.1+ también aparece bajo evaluación.
- **Negativas:** en 4.5 y 5.0 solo aparece como contenido interactivo. En sitios
existentes la actividad cambia de categoría en el selector.
- **Sin riesgo visual:** el icono es de marca y no se tiñe.

## Validación

`tests/supports_test.php` fija el archetype por defecto, el propósito primario y, cuando la constante existe, el
secundario (en 4.5 y 5.0 ese caso se omite). La matriz de CI cubre 4.5–5.2.
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
id: DEC-37-01
title: "Clasificación funcional del módulo: mantener MOD_ARCHETYPE_ASSIGNMENT + MOD_PURPOSE_ASSESSMENT"
status: Accepted
status: Superseded
superseded_by: DEC-159-01
date: 2026-06-11
tracking_issue: 37
legacy_id: DEC-0047
Expand Down
1 change: 1 addition & 0 deletions research/docs/indices/adrs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ items:
- id: 'DEC-124-03', titulo: 'DEC-124-03-lo-hecho-sin-calificacion-nunca-se-convierte-en-nota', estado: None, fecha: None, ruta: 'decisiones/adr/DEC-124-03-lo-hecho-sin-calificacion-nunca-se-convierte-en-nota.md'
- id: 'DEC-125-01', titulo: 'DEC-125-01-linea-base-de-cambio-por-objectid', estado: None, fecha: None, ruta: 'decisiones/adr/DEC-125-01-linea-base-de-cambio-por-objectid.md'
- id: 'DEC-126-01', titulo: 'DEC-126-01-con-la-calificacion-apagada-no-se-registra-nada', estado: None, fecha: None, ruta: 'decisiones/adr/DEC-126-01-con-la-calificacion-apagada-no-se-registra-nada.md'
- id: 'DEC-159-01', titulo: 'DEC-159-01-proposito-interactivecontent', estado: None, fecha: None, ruta: 'decisiones/adr/DEC-159-01-proposito-interactivecontent.md'
24 changes: 19 additions & 5 deletions tests/supports_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@
*/
final class supports_test extends advanced_testcase {
/**
* DEC-37-01: the module is an assessment-archetype activity. The classification
* is fixed per module type and must not silently change.
* DEC-159-01: the module is interactive content (like SCORM, H5P and Lesson)
* that may carry graded iDevices. The classification is fixed per module type
* and must not silently change.
*/
public function test_supports_reports_assessment_classification(): void {
$this->assertSame(MOD_ARCHETYPE_ASSIGNMENT, exelearning_supports(FEATURE_MOD_ARCHETYPE));
$this->assertSame(MOD_PURPOSE_ASSESSMENT, exelearning_supports(FEATURE_MOD_PURPOSE));
public function test_supports_reports_interactive_content_classification(): void {
// Default archetype: 4.5 and 5.0 list only MOD_ARCHETYPE_OTHER under "Activities".
$this->assertNull(exelearning_supports(FEATURE_MOD_ARCHETYPE));
$this->assertSame(MOD_ARCHETYPE_OTHER, plugin_supports('mod', 'exelearning', FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER));
$this->assertSame(MOD_PURPOSE_INTERACTIVECONTENT, exelearning_supports(FEATURE_MOD_PURPOSE));
$this->assertTrue(exelearning_supports(FEATURE_GRADE_HAS_GRADE));
$this->assertTrue(exelearning_supports(FEATURE_BACKUP_MOODLE2));
$this->assertTrue(exelearning_supports(FEATURE_MOD_INTRO));
Expand All @@ -53,6 +56,17 @@ public function test_supports_reports_assessment_classification(): void {
$this->assertNull(exelearning_supports('a_feature_that_does_not_exist'));
}

/**
* DEC-159-01: on Moodle 5.1+ the module is also listed under assessment, as a
* secondary purpose. Older branches do not define the feature at all.
*/
public function test_supports_reports_assessment_as_secondary_purpose(): void {
if (!defined('FEATURE_MOD_OTHERPURPOSE')) {
$this->markTestSkipped('FEATURE_MOD_OTHERPURPOSE exists from Moodle 5.1.');
}
$this->assertSame(MOD_PURPOSE_ASSESSMENT, exelearning_supports(FEATURE_MOD_OTHERPURPOSE));
}

/**
* The icon is branded so Moodle does not tint it with the assessment purpose
* colour, and the monologo carries the official colour.
Expand Down
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
// in $plugin->release ('dev'); a release-preparation PR commits the final
// version + semver release BEFORE the tag is created (see DEVELOPMENT.md,
// "Versioning and releases").
$plugin->version = 2026092610;
$plugin->version = 2026092613;
$plugin->release = 'dev';
$plugin->requires = 2024100700; // Moodle 4.5 LTS+.
$plugin->supported = [405, 502]; // Moodle 4.5 LTS through Moodle 5.2.
Expand Down
Loading