diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index f28460d..8e6a7b5 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -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 diff --git a/docs/GRADEBOOK.md b/docs/GRADEBOOK.md index 19ab279..963c43a 100644 --- a/docs/GRADEBOOK.md +++ b/docs/GRADEBOOK.md @@ -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 @@ -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. diff --git a/lib.php b/lib.php index 78feca8..31053e9 100644 --- a/lib.php +++ b/lib.php @@ -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: @@ -72,9 +76,11 @@ 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; } } @@ -82,8 +88,7 @@ function exelearning_supports($feature) { * 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. */ diff --git a/research/decisiones/adr/DEC-159-01-proposito-interactivecontent.md b/research/decisiones/adr/DEC-159-01-proposito-interactivecontent.md new file mode 100644 index 0000000..94f7b4c --- /dev/null +++ b/research/decisiones/adr/DEC-159-01-proposito-interactivecontent.md @@ -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. diff --git a/research/decisiones/adr/DEC-37-01-clasificacion-funcional-archetype-purpose.md b/research/decisiones/adr/DEC-37-01-clasificacion-funcional-archetype-purpose.md index 0ee3b4c..baa28b0 100644 --- a/research/decisiones/adr/DEC-37-01-clasificacion-funcional-archetype-purpose.md +++ b/research/decisiones/adr/DEC-37-01-clasificacion-funcional-archetype-purpose.md @@ -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 diff --git a/research/docs/indices/adrs.yaml b/research/docs/indices/adrs.yaml index ef13a38..73a4b34 100644 --- a/research/docs/indices/adrs.yaml +++ b/research/docs/indices/adrs.yaml @@ -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' diff --git a/tests/supports_test.php b/tests/supports_test.php index 590f9cc..c94c49a 100644 --- a/tests/supports_test.php +++ b/tests/supports_test.php @@ -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)); @@ -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. diff --git a/version.php b/version.php index a90fa12..baed7e6 100644 --- a/version.php +++ b/version.php @@ -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.