diff --git a/AGENTS.md b/AGENTS.md index e1a07f5..3ff7ad2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -182,7 +182,7 @@ Cerradas: **TAREA-012 / RIE-001** investigación (DEC-0019); **TAREA-009 / RIE-0 | DEC-0048 | **Aceptada** (2026-06-12) | Estrategia de cobertura de tests: mockear la red con `\curl::mock_response()` + mock parcial de `download_to_temp()` en vez de excluir; no excluir del scope código testeable (`excludelistfiles` vacío); xdebug/Codecov es la medida autoritativa (pcov local subacredita llamadas anidadas — artefacto, no límite); gate `codecov project: target: auto` (trinquete). Cobertura honesta 85.71%→87.2% (PR #65) | | DEC-0049 | **Aceptada** (2026-06-12) | Auditoría estándar de repositorio (2026-06-11, tras DEC-0016/DEC-0044): 9 mejoras P1–P3 implementadas (PRs #46–#54: hardening XML de estilos, thirdpartylibs en el ZIP, fidelidad backup/restore, lock de intentos, participación vs grademethod, recálculo de notas en lote, `zip_utils`, descarga del informe, Behat) + registro de **hallazgos descartados** y opciones de dirección para no re-auditar | | DEC-0050 | **Aceptada** (2026-06-12) | La herramienta de migración exeweb/exescorm vive en `mod_exelearning` (destino, dueño de los internals); orígenes como fuentes legacy de solo lectura tras `source_interface`. Endurecimiento de la rama issue #13: fix `mod_exeweb` itemid=revision (antes leía 0 → todo `nosource`); clasificación `mod_exescorm` (`.elpx` directo / 1 embebido / 0=nosource / >1=ambiguous / external+aiccurl+localsync=unsupported, `localsync` excluido por sincronización aunque tenga snapshot local); limpieza compensatoria con `course_delete_module` ante fallo parcial (sin transacción, caveat recycle bin); preservación de metadatos del cm (idnumber **nunca** se copia); validación post-extracción anti shell-vacío (`migrateextractfailed`); eventos (started/migrated/skipped/failed, patrón DEC-0041); columnas `userid`/`timemodified` (upgrade 2026061201); preflight + `\core\progress\display`. Refactor a `classes/local/migration/` (elimina `import_service`). CLI diferido | -| DEC-0051..0063 | (varias, 2026-06-12 → 2026-06-17) | **Ver índice completo en `research/docs/indices/adrs.yaml`.** Resumen: DEC-0051 eventos selectivos · DEC-0052 completion por estado · DEC-0053 búsqueda global · DEC-0054 refactor `lib.php` (extracción a clases) · DEC-0055 auditoría post-refactor · DEC-0056 tests JS (Vitest) · DEC-0057 extracción no-destructiva (BETA→STABLE) · DEC-0058 fijar tag del editor en release · DEC-0063 validación canónica del endpoint xAPI + política de versión (1.0.3 tolerante a 2.0) · DEC-0064 implementación ingesta xAPI · DEC-0065 editor solo empaquetado en release (sin instalador runtime). *(DEC-0059..0062 = iframe seguro en rama `feature/secure-iframe-scorm-bridge`, aún no en `main`.)* | +| DEC-0051..0063 | (varias, 2026-06-12 → 2026-06-17) | **Ver índice completo en `research/docs/indices/adrs.yaml`.** Resumen: DEC-0051 eventos selectivos · DEC-0052 completion por estado · DEC-0053 búsqueda global · DEC-0054 refactor `lib.php` (extracción a clases) · DEC-0055 auditoría post-refactor · DEC-0056 tests JS (Vitest) · DEC-0057 extracción no-destructiva (BETA→STABLE) · DEC-0058 fijar tag del editor en release · DEC-0063 validación canónica del endpoint xAPI + política de versión (1.0.3 tolerante a 2.0) · DEC-0064 implementación ingesta xAPI · DEC-0065 editor solo empaquetado en release (sin instalador runtime) · DEC-0066 interruptor global del editor (modo reproductor puro). *(DEC-0059..0062 = iframe seguro en rama `feature/secure-iframe-scorm-bridge`, aún no en `main`.)* | ## Restricciones inmutables diff --git a/README.md b/README.md index cc1e357..300ebe9 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,10 @@ All settings live on a single admin page (see rationale of dropping the eXeLearning Online integration — only the embedded editor remains): +* **Embedded editor**: a single site-wide switch. Disabling it turns the plugin + into a pure `.elpx` player — uploads and playback keep working, but the + "Edit with eXeLearning" button is hidden and the editor endpoints refuse + requests. * **Styles**: upload eXeLearning style packages (`.zip`), enable/disable the editor's built-in styles, and optionally block users from importing styles bundled inside an `.elpx`. A dedicated _Styles_ admin page lists and manages diff --git a/docs/EMBEDDED_EDITOR.md b/docs/EMBEDDED_EDITOR.md index fbde066..5cd05cd 100644 --- a/docs/EMBEDDED_EDITOR.md +++ b/docs/EMBEDDED_EDITOR.md @@ -49,6 +49,16 @@ removed installer era is obsolete and ignored; upgrading cleans the installer's config keys (`db/upgrade.php`, stage 2026072400) but deliberately leaves the directory for the administrator to delete. +**Site-wide toggle (DEC-0066).** Embedded editing can be switched off with the +`exelearning/editordisabled` admin setting (a deliberately negative checkbox, +unticked by default, so the unset config and the unticked box both mean +"editing on"). `exelearning_embedded_editor_enabled()` combines the toggle with bundle +validation, so the edit button, `editor/static.php` and the create-from-scratch +CTA all react to it; `editor/index.php` and `editor/save.php` additionally +refuse direct requests via `exelearning_require_embedded_editor_enabled()`. +Uploading and serving `.elpx` packages is unaffected — the plugin degrades to a +pure player. + ## 2. Embedding the editor and the postMessage bridge The editor bootstrap page is `editor/index.php`. Access requires diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 72d93f4..5f7b13d 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -240,6 +240,14 @@ from an official release ZIP. > `moodledata/mod_exelearning/embedded_editor/` installed by the removed > management page. It is no longer used and can be deleted at any time. +### Disabling in-place editing + +If your site only plays packages authored elsewhere, tick **Disable the +embedded editor** on the settings page. Teachers keep uploading and serving +`.elpx` packages exactly as before, but the **Edit with eXeLearning** button +disappears and the editor pages refuse requests. Untick it at any time to +restore in-place editing — no data is affected either way. + ### Managing styles On the settings page you can manage the eXeLearning styles available to the diff --git a/editor/index.php b/editor/index.php index b8c48b9..3d5b1cb 100644 --- a/editor/index.php +++ b/editor/index.php @@ -97,6 +97,12 @@ function exelearning_editor_error_page(string $message): void { // Direct access to dist/static/ can fail on servers that block .zip, .md, etc. $editorbaseurl = $CFG->wwwroot . '/mod/exelearning/editor/static.php/' . $cm->id; +// Embedded editing can be switched off site-wide (DEC-0066); hiding the button +// is not enough, a direct request to this bootstrap must be refused too. +if (!exelearning_embedded_editor_enabled()) { + exelearning_editor_error_page(get_string('editordisabledbyadmin', 'mod_exelearning')); +} + // Read the editor template from the bundled installation (DEC-0065). $editorindexsource = exelearning_get_embedded_editor_index_source(); if ($editorindexsource === null) { diff --git a/editor/save.php b/editor/save.php index 69baeee..460f165 100644 --- a/editor/save.php +++ b/editor/save.php @@ -44,6 +44,9 @@ require_sesskey(); $context = context_module::instance($cm->id); require_capability('moodle/course:manageactivities', $context); +// Embedded editing can be switched off site-wide (DEC-0066): refuse saves too, +// not only the editor bootstrap — this is the state-changing half of the flow. +exelearning_require_embedded_editor_enabled(); header('Content-Type: application/json; charset=utf-8'); diff --git a/lang/ca/exelearning.php b/lang/ca/exelearning.php index d574e16..44cf772 100644 --- a/lang/ca/exelearning.php +++ b/lang/ca/exelearning.php @@ -51,7 +51,11 @@ $string['deleteattempt_confirm'] = '~S\'eliminarà definitivament l\'intent {$a} d\'aquest estudiant i la seva qualificació es recalcularà a partir dels intents restants. Aquesta acció no es pot desfer.'; $string['detecteditems'] = '~iDevices qualificables detectats:'; $string['downloadreport'] = '~Baixa les dades de l\'informe com a'; +$string['editordisabled'] = '~Desactiva l\'editor integrat'; +$string['editordisabled_desc'] = '~Si es marca, el connector funciona com un reproductor pur: pujar i mostrar paquets .elpx continua funcionant, però el botó «Edita amb eXeLearning» s\'amaga i els punts finals de l\'editor rebutgen les peticions. Deixeu-la sense marcar (el valor per defecte) perquè els qui poden gestionar una activitat editin els paquets des de Moodle.'; +$string['editordisabledbyadmin'] = '~L\'edició integrada està desactivada en aquest lloc. L\'activitat continua reproduint els paquets eXeLearning pujats; contacteu amb l\'administrador si necessiteu editar des de Moodle.'; $string['editorreaderror'] = 'No s\'han pogut llegir els fitxers de l\'editor integrat eXeLearning. Comproveu els permisos dels fitxers i contacteu amb l\'administrador.'; +$string['editorsettings'] = '~Editor integrat'; $string['editwitheditor'] = '~Edita amb eXeLearning'; $string['embeddednotinstalledadmin'] = '~L\'editor integrat no està inclòs en aquesta instal·lació. Instal·leu el connector des d\'un ZIP de versió oficial, que inclou l\'editor.'; $string['embeddednotinstalledcontactadmin'] = '~L\'editor integrat no està disponible en aquest lloc. Contacteu amb l\'administrador del lloc.'; diff --git a/lang/en/exelearning.php b/lang/en/exelearning.php index 1ddfc46..3a204ff 100644 --- a/lang/en/exelearning.php +++ b/lang/en/exelearning.php @@ -47,7 +47,11 @@ $string['deleteattempt_confirm'] = 'This will permanently delete attempt {$a} for this student and recalculate their grade from the remaining attempts. This action cannot be undone.'; $string['detecteditems'] = 'Gradable iDevices detected:'; $string['downloadreport'] = 'Download report data as'; +$string['editordisabled'] = 'Disable the embedded editor'; +$string['editordisabled_desc'] = 'When ticked, the plugin works as a pure player: uploading and serving .elpx packages keeps working, but the "Edit with eXeLearning" button is hidden and the editor endpoints refuse requests. Leave unticked (the default) to let users who can manage an activity author packages in place.'; +$string['editordisabledbyadmin'] = 'Embedded editing is disabled on this site. The activity still plays uploaded eXeLearning packages; contact your administrator if you need in-place editing.'; $string['editorreaderror'] = 'Could not read the eXeLearning embedded editor files. Please check file permissions and contact your administrator.'; +$string['editorsettings'] = 'Embedded editor'; $string['editwitheditor'] = 'Edit with eXeLearning'; $string['embeddednotinstalledadmin'] = 'The embedded editor is not included in this installation. Install the plugin from an official release ZIP, which bundles the editor.'; $string['embeddednotinstalledcontactadmin'] = 'The embedded editor is not available on this site. Please contact your site administrator.'; diff --git a/lang/es/exelearning.php b/lang/es/exelearning.php index 554a6d2..62789e7 100644 --- a/lang/es/exelearning.php +++ b/lang/es/exelearning.php @@ -51,7 +51,11 @@ $string['deleteattempt_confirm'] = '~Se eliminará definitivamente el intento {$a} de este estudiante y su calificación se recalculará a partir de los intentos restantes. Esta acción no se puede deshacer.'; $string['detecteditems'] = '~iDevices calificables detectados:'; $string['downloadreport'] = '~Descargar los datos del informe como'; +$string['editordisabled'] = '~Desactivar el editor integrado'; +$string['editordisabled_desc'] = '~Si se marca, el plugin funciona como un reproductor puro: subir y mostrar paquetes .elpx sigue funcionando, pero el botón «Editar con eXeLearning» se oculta y los endpoints del editor rechazan las peticiones. Déjelo sin marcar (el valor por defecto) para que quienes pueden gestionar una actividad editen los paquetes desde Moodle.'; +$string['editordisabledbyadmin'] = '~La edición integrada está desactivada en este sitio. La actividad sigue reproduciendo los paquetes eXeLearning subidos; contacte con el administrador si necesita editar desde Moodle.'; $string['editorreaderror'] = 'No se pudieron leer los archivos del editor integrado eXeLearning. Compruebe los permisos de los archivos y contacte con el administrador.'; +$string['editorsettings'] = '~Editor integrado'; $string['editwitheditor'] = 'Editar con eXeLearning'; $string['embeddednotinstalledadmin'] = '~El editor integrado no está incluido en esta instalación. Instale el plugin desde un ZIP de release oficial, que incluye el editor.'; $string['embeddednotinstalledcontactadmin'] = '~El editor integrado no está disponible en este sitio. Contacte con el administrador del sitio.'; diff --git a/lang/eu/exelearning.php b/lang/eu/exelearning.php index 9c80e2c..2869cfd 100644 --- a/lang/eu/exelearning.php +++ b/lang/eu/exelearning.php @@ -51,7 +51,11 @@ $string['deleteattempt_confirm'] = '~Ikasle honen {$a}. saialdia behin betiko ezabatuko da, eta kalifikazioa gainerako saialdietatik birkalkulatuko da. Ekintza hau ezin da desegin.'; $string['detecteditems'] = '~Hautemandako iDevice kalifikagarriak:'; $string['downloadreport'] = '~Deskargatu txostenaren datuak honela:'; +$string['editordisabled'] = '~Desaktibatu editore txertatua'; +$string['editordisabled_desc'] = '~Markatzen bada, pluginak erreproduzitzaile huts gisa funtzionatzen du: .elpx paketeak igotzea eta erakustea funtzionatzen jarraitzen du, baina «Editatu eXeLearning-ekin» botoia ezkutatu egiten da eta editorearen amaiera-puntuek eskaerak ukatzen dituzte. Utzi markatu gabe (balio lehenetsia) jarduera bat kudea dezaketenek paketeak Moodletik edita ditzaten.'; +$string['editordisabledbyadmin'] = '~Editatze txertatua desaktibatuta dago gune honetan. Jarduerak igotako eXeLearning paketeak erreproduzitzen jarraitzen du; jarri harremanetan administratzailearekin Moodletik editatu behar baduzu.'; $string['editorreaderror'] = 'Ezin izan dira eXeLearning editore txertatuaren fitxategiak irakurri. Egiaztatu fitxategien baimenak eta jarri harremanetan administratzailearekin.'; +$string['editorsettings'] = '~Editore txertatua'; $string['editwitheditor'] = '~Editatu eXeLearning-ekin'; $string['embeddednotinstalledadmin'] = '~Editore txertatua ez dago instalazio honetan sartuta. Instalatu plugina bertsio ofizialeko ZIP batetik; horrek editorea barne dakar.'; $string['embeddednotinstalledcontactadmin'] = '~Editore txertatua ez dago erabilgarri gune honetan. Jarri harremanetan guneko administratzailearekin.'; diff --git a/lang/gl/exelearning.php b/lang/gl/exelearning.php index 86ba4ee..99c75d1 100644 --- a/lang/gl/exelearning.php +++ b/lang/gl/exelearning.php @@ -51,7 +51,11 @@ $string['deleteattempt_confirm'] = '~Eliminarase definitivamente o intento {$a} deste estudante e a súa cualificación recalcularase a partir dos intentos restantes. Esta acción non se pode desfacer.'; $string['detecteditems'] = '~iDevices cualificables detectados:'; $string['downloadreport'] = '~Descargar os datos do informe como'; +$string['editordisabled'] = '~Desactivar o editor integrado'; +$string['editordisabled_desc'] = '~Se se marca, o complemento funciona como un reprodutor puro: subir e amosar paquetes .elpx segue funcionando, pero o botón «Editar con eXeLearning» ocúltase e os puntos de acceso do editor rexeitan as peticións. Déixeo sen marcar (o valor por defecto) para que quen pode xestionar unha actividade edite os paquetes desde Moodle.'; +$string['editordisabledbyadmin'] = '~A edición integrada está desactivada neste sitio. A actividade segue reproducindo os paquetes eXeLearning subidos; contacte co administrador se precisa editar desde Moodle.'; $string['editorreaderror'] = 'Non se puideron ler os ficheiros do editor integrado eXeLearning. Comprobe os permisos dos ficheiros e contacte co administrador.'; +$string['editorsettings'] = '~Editor integrado'; $string['editwitheditor'] = '~Editar con eXeLearning'; $string['embeddednotinstalledadmin'] = '~O editor integrado non está incluído nesta instalación. Instale o complemento desde un ZIP de versión oficial, que inclúe o editor.'; $string['embeddednotinstalledcontactadmin'] = '~O editor integrado non está dispoñible neste sitio. Contacte co administrador do sitio.'; diff --git a/lib.php b/lib.php index a0bde7a..dae06b3 100644 --- a/lib.php +++ b/lib.php @@ -1055,17 +1055,45 @@ function exelearning_get_embedded_editor_index_source(): ?string { } /** - * Returns whether the bundled embedded editor is available (DEC-0065). + * Returns whether embedded editing is available on this site. + * + * Two conditions must hold (DEC-0065, DEC-0066): the bundled editor passes + * validation, and the administrator has not switched embedded editing off via + * the site-wide `editordisabled` setting. The toggle is deliberately negative + * (like `stylesblockimport`) so the unset state and the unticked checkbox both + * mean "editing on" — a positive default would render unticked until + * upgradesettings materialises it, contradicting the real behaviour. * * Used by view.php to decide whether to show the "Edit with eXeLearning" button - * and by editor/static.php before serving editor assets. + * and by editor/static.php before serving editor assets. Activities keep + * accepting `.elpx` uploads either way — the toggle only affects in-place + * editing. * - * @return bool True when the bundled editor passes validation. + * @return bool True when the editor is bundled, valid and not disabled. */ function exelearning_embedded_editor_enabled(): bool { + if (!empty(get_config('exelearning', 'editordisabled'))) { + return false; + } return \mod_exelearning\local\embedded_editor_source_resolver::is_available(); } +/** + * Aborts the request when embedded editing is disabled site-wide (DEC-0066). + * + * Guard for the editor endpoints (editor/index.php bootstrap, editor/save.php): + * hiding the button is not enough, a direct request must be refused too. + * + * @return void + * @throws moodle_exception editordisabledbyadmin when the toggle is off or no + * valid bundle is available. + */ +function exelearning_require_embedded_editor_enabled(): void { + if (!exelearning_embedded_editor_enabled()) { + throw new moodle_exception('editordisabledbyadmin', 'mod_exelearning'); + } +} + /** * Absolute path to the bundled editor static directory, used by * editor/static.php to serve the editor's assets. diff --git a/research/decisiones/adr/DEC-0066-interruptor-global-editor-embebido.md b/research/decisiones/adr/DEC-0066-interruptor-global-editor-embebido.md new file mode 100644 index 0000000..a1d7a76 --- /dev/null +++ b/research/decisiones/adr/DEC-0066-interruptor-global-editor-embebido.md @@ -0,0 +1,78 @@ +--- +id: DEC-0066 +titulo: "Interruptor global del editor embebido: modo reproductor puro vía ajuste de sitio" +estado: Aceptada +fecha: 2026-07-24 +agentes: + - erseco + - claude-code +fuentes: + - REPO-002 + - REPO-004 +relacionados: + - DEC-0009 + - DEC-0065 + - DEC-0024 +herramienta_ia: + interfaz: claude-code + modelo: claude-fable-5 +--- + +## Contexto + +Hay sitios que quieren usar `mod_exelearning` como **reproductor puro**: los docentes suben +paquetes `.elpx` producidos fuera (eXeLearning de escritorio, repositorios institucionales) +y el sitio no quiere ofrecer la edición en Moodle — por política editorial, por flujo de +trabajo (el contenido se autoriza fuera) o por simplicidad de soporte. Hoy el botón +"Editar con eXeLearning" aparece para cualquiera con `moodle/course:manageactivities` +siempre que el bundle del editor sea válido ([[DEC-0065]]); no existe forma soportada de +apagarlo sin mutilar el paquete. + +Se valoraron dos mecanismos: + +1. **Ajuste global de sitio** (checkbox en la página de ajustes del plugin). +2. **Capability nueva** (`mod/exelearning:useeditor`) concedida por defecto a + editingteacher/manager — un "perfil mínimo de edición". + +## Decisión + +**Ajuste global `exelearning/editordisabled`** (checkbox negativo, desmarcado por defecto = edición activa), por ser lo +más sencillo y lo más parecido a cómo los plugins de actividad de Moodle apagan +funcionalidades completas de sitio (los ajustes `scorm_*` de mod_scorm, los toggles de +características en admin settings). La capability se descarta como mecanismo primario: el +"quién" ya lo gobierna `moodle/course:manageactivities` (paridad con el resto del plugin), +y una capability nueva obligaría a editar roles para lograr el caso de uso real, que es +binario y de sitio ("aquí no se edita"), no por-rol. + +Alcance del interruptor: + +- **Apagado**: el botón de edición no se muestra (`view.php` vía + `exelearning_embedded_editor_enabled()`, que ahora combina ajuste + validez del bundle); + los endpoints del editor **rechazan** peticiones directas — `editor/index.php` con página + de error explicativa y `editor/save.php` con `exelearning_require_embedded_editor_enabled()` + (ocultar el botón no es un control de acceso); `editor/static.php` deja de servir assets + (ya pasaba por el helper). La subida y reproducción de `.elpx` no cambian. +- **Encendido** (default): comportamiento actual. El checkbox es **negativo a propósito** + ("Desactivar el editor integrado", patrón de `stylesblockimport`): así config ausente y + casilla desmarcada significan lo mismo (edición activa) y se evita la confusión de un + default positivo que se muestra desmarcado hasta que `upgradesettings` lo materializa + (observada en la revisión del PR). + +## Consecuencias + +- Positivas: caso "reproductor puro" soportado con un clic; superficie del editor + desactivable de golpe (bootstrap, guardado y assets); sin cambios de roles ni upgrade de + BD (es un config, no una columna). +- Negativas (asumidas): no permite granularidad por rol o por curso; si mañana hiciera + falta, la capability del punto 2 puede añadirse *encima* del interruptor sin romper nada + (el ajuste seguiría siendo el interruptor maestro). +- El CTA de "crear desde cero" ([[DEC-0024]]) degrada solo: con el editor apagado el + docente ve el aviso de "sube un paquete" en lugar del de "créalo con el editor". + +## Alternativas consideradas + +| Alternativa | Por qué se rechaza | +|---|---| +| Capability `mod/exelearning:useeditor` | Resuelve un problema distinto (quién, no si); exige editar roles para el caso real; más superficie de configuración. Puede añadirse después si aparece demanda por-rol. | +| Ajuste por actividad | Dispersa la política editorial en N actividades; el caso de uso es de sitio. | +| No hacer nada (quitar el bundle del paquete) | Rompe [[DEC-0065]] (el empaquetado exige el editor) y castiga a los sitios que sí editan. | diff --git a/research/docs/indices/adrs.yaml b/research/docs/indices/adrs.yaml index ce19edf..ba11318 100644 --- a/research/docs/indices/adrs.yaml +++ b/research/docs/indices/adrs.yaml @@ -61,3 +61,4 @@ items: - id: 'DEC-0063', titulo: 'Reglas de validación canónica del endpoint xAPI y política de versión (1.0.3 con tolerancia a 2.0)', estado: 'Propuesta', fecha: '2026-06-17', ruta: 'decisiones/adr/DEC-0063-validacion-canonica-endpoint-xapi-y-version.md' - id: 'DEC-0064', titulo: 'Implementación de la ingesta xAPI (TAREA-015): xAPI-primary para paquetes nuevos, SCORM inerte, overall desde el statement de paquete, siempre activo', estado: 'Aceptada', fecha: '2026-06-18', ruta: 'decisiones/adr/DEC-0064-implementacion-ingesta-xapi.md' - id: 'DEC-0065', titulo: 'Editor embebido exclusivamente empaquetado en la release: eliminar el instalador/actualizador en runtime', estado: 'Aceptada', fecha: '2026-07-24', ruta: 'decisiones/adr/DEC-0065-editor-empaquetado-solo-en-release.md' + - id: 'DEC-0066', titulo: 'Interruptor global del editor embebido: modo reproductor puro vía ajuste de sitio', estado: 'Aceptada', fecha: '2026-07-24', ruta: 'decisiones/adr/DEC-0066-interruptor-global-editor-embebido.md' diff --git a/settings.php b/settings.php index 1648bac..2a06bce 100644 --- a/settings.php +++ b/settings.php @@ -54,6 +54,26 @@ } if ($ADMIN->fulltree) { + // Embedded editor (DEC-0066): a single site-wide toggle. The editor itself + // ships inside the release package (DEC-0065) and has no runtime management; + // this switch lets a site use the plugin as a pure .elpx player — uploads + // keep working, only in-place editing is hidden and refused. The checkbox is + // negative (disable) on purpose: unset config and unticked box then agree, + // avoiding the "Default: Yes but unticked" confusion before upgradesettings + // materialises a positive default (same pattern as stylesblockimport). + $settings->add(new admin_setting_heading( + 'mod_exelearning/editorsettingsheading', + get_string('editorsettings', 'mod_exelearning'), + '' + )); + + $settings->add(new admin_setting_configcheckbox( + 'exelearning/editordisabled', + get_string('editordisabled', 'mod_exelearning'), + get_string('editordisabled_desc', 'mod_exelearning'), + 0 + )); + // Defined styles management (upload / enable / disable / lockdown). $settings->add(new admin_setting_heading( 'mod_exelearning/stylesheading', diff --git a/tests/lib_helpers_test.php b/tests/lib_helpers_test.php index 417657e..faaaa95 100644 --- a/tests/lib_helpers_test.php +++ b/tests/lib_helpers_test.php @@ -35,6 +35,7 @@ * @covers ::exelearning_grade_item_view_url * @covers ::exelearning_grade_analysis_url * @covers ::exelearning_embedded_editor_enabled + * @covers ::exelearning_require_embedded_editor_enabled * @covers ::exelearning_get_embedded_editor_index_source * @covers ::exelearning_get_embedded_editor_local_static_dir * @covers ::exelearning_xapi_primary_enabled @@ -163,5 +164,30 @@ public function test_embedded_editor_wrappers_reflect_bundle(): void { $this->assertTrue(exelearning_embedded_editor_enabled()); $this->assertSame($dir . '/index.html', exelearning_get_embedded_editor_index_source()); $this->assertSame($dir, exelearning_get_embedded_editor_local_static_dir()); + + // With no editordisabled config at all (fresh site), editing stays on and + // the endpoint guard passes. + exelearning_require_embedded_editor_enabled(); + } + + /** + * The site-wide editordisabled toggle switches embedded editing off even when + * a valid bundle is present (DEC-0066): the button helper reports false and + * the editor endpoints' guard throws. + */ + public function test_admin_toggle_disables_embedded_editing(): void { + global $CFG; + $this->resetAfterTest(); + + $dir = make_temp_directory('mod_exelearning/lw-' . random_string(6)) . '/static'; + make_writable_directory($dir . '/app'); + file_put_contents($dir . '/index.html', 'x'); + $CFG->mod_exelearning_bundled_editor_dir = $dir; + + set_config('editordisabled', 1, 'exelearning'); + + $this->assertFalse(exelearning_embedded_editor_enabled()); + $this->expectException(\moodle_exception::class); + exelearning_require_embedded_editor_enabled(); } }