Skip to content

feat(themes): runtime themeRegistryOverride hook for host integrations#1722

Merged
eXeLearningProject merged 1 commit intomainfrom
feature/allow-installation-and-management-of-styles-in-embedded-plugins
Apr 23, 2026
Merged

feat(themes): runtime themeRegistryOverride hook for host integrations#1722
eXeLearningProject merged 1 commit intomainfrom
feature/allow-installation-and-management-of-styles-in-embedded-plugins

Conversation

@erseco
Copy link
Copy Markdown
Collaborator

@erseco erseco commented Apr 23, 2026

Summary

Adds an opt-in, backward-compatible runtime hook that lets host
integrations (WordPress, Moodle, Omeka-S) inject an admin-approved style
registry into the static editor without rebuilding the bundle.

Host pages (e.g. the editor bootstrap in each plugin) may set:

window.eXeLearning.config.themeRegistryOverride = {
    disabledBuiltins: ['zen', 'universal'],   // hide these built-ins
    uploaded: [                               // append uploaded themes
        {
            name: 'acme-2026',
            dirName: 'acme-2026',
            title: 'Acme 2026',
            type: 'uploaded',
            url: '/wp-content/uploads/exelearning-styles/acme-2026',
            cssFiles: ['style.css'],
            version: '1.0.0',
            valid: true,
        },
    ],
    blockImportInstall: true,   // refuse installs from imported content
    fallbackTheme: 'base',      // used when a project references a theme
                                //   the admin has disabled or deleted
};

With no override present, behavior is unchanged.

Changes

  • StaticDataProvider.getThemes() merges the override with the
    bundled themes payload. A pure helper (applyThemeRegistryOverride)
    is exported so plugin smoke tests can reuse the exact contract.
  • ThemeList.addUserTheme and loadUserThemesFromIndexedDB
    bail out with console.warn when blockImportInstall is truthy,
    closing the install-from-content path that embedded hosts must not
    expose.
  • ThemesManager.selectTheme uses fallbackTheme (with a warning)
    when both the requested theme and the configured default are missing,
    so existing projects that reference a removed style still open.

Test plan

  • public/app/core/providers/StaticDataProvider.test.js — unit
    coverage of the merge helper and the getThemes() path
    (hide builtins, append uploads, override shadows on id collision,
    malformed override is ignored).
  • public/app/workarea/themes/themeList.test.js — guards against
    addUserTheme and loadUserThemesFromIndexedDB when
    blockImportInstall is set.
  • public/app/workarea/themes/themesManager.test.js — fallback
    to themeRegistryOverride.fallbackTheme when default theme is
    also unavailable, with warning.
  • Full frontend unit suite runs green (155 tests across the
    three affected files).

Companion work

The admin UIs that will consume this hook live in the four integration
repos, each on the matching feature branch:

  • exelearning/wp-exelearning — feature/allow-installation-and-management-of-styles
  • exelearning/mod_exeweb — 28-allow-installation-and-management-of-styles
  • exelearning/mod_exescorm — feature/allow-installation-and-management-of-styles
  • exelearning/omeka-s-exelearning — feature/allow-installation-and-management-of-styles

Those PRs bump the editor version once this one merges.

Host integrations (WordPress/Moodle/Omeka-S) need to let administrators
upload approved style ZIPs and hide built-in ones without rebuilding the
static editor bundle. Expose a tiny, additive hook:

  window.eXeLearning.config.themeRegistryOverride = {
    disabledBuiltins: ['zen'],       // hide these built-ins
    uploaded: [ { name, title, ... } ], // append admin-approved themes
    blockImportInstall: true,         // refuse installs from content
    fallbackTheme: 'base',            // used when a project references
                                      //   a style the admin disabled
  }

- StaticDataProvider.getThemes() merges override with the bundle payload
  (pure, null-safe helper applyThemeRegistryOverride exported for tests).
- ThemeList.addUserTheme / loadUserThemesFromIndexedDB bail out with a
  console.warn when blockImportInstall is set, closing the
  import-from-content install path that embedded hosts must not expose.
- ThemesManager.selectTheme uses the configured fallbackTheme (with a
  warning) when both the requested theme and the default are missing,
  so projects referencing a removed style still open.

All changes are opt-in: with no override present, behavior is identical
to today. Unit tests cover the merge, block-import guards, and fallback.
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.34%. Comparing base (188b03c) to head (ad6fb11).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1722      +/-   ##
==========================================
- Coverage   91.34%   91.34%   -0.01%     
==========================================
  Files         353      353              
  Lines       76270    76295      +25     
==========================================
+ Hits        69672    69692      +20     
- Misses       6598     6603       +5     
Flag Coverage Δ
frontend 91.34% <100.00%> (-0.01%) ⬇️
unittests 91.34% <100.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 23, 2026

Deploying exelearning with  Cloudflare Pages  Cloudflare Pages

Latest commit: ad6fb11
Status: ✅  Deploy successful!
Preview URL: https://c1f54eb8.exelearning.pages.dev
Branch Preview URL: https://feature-allow-installation-a.exelearning.pages.dev

View logs

@eXeLearningProject eXeLearningProject merged commit e2e4882 into main Apr 23, 2026
7 checks passed
@eXeLearningProject eXeLearningProject deleted the feature/allow-installation-and-management-of-styles-in-embedded-plugins branch April 23, 2026 17:20
erseco added a commit to exelearning/mod_exeweb that referenced this pull request Apr 23, 2026
…ditor

Adds an admin Styles management page (Site administration → Plugins →
Activity modules → eXeLearning (website) → Styles) where managers can
upload eXeLearning style .zip packages, enable/disable built-in styles,
and enable/disable/delete uploaded ones — without rebuilding the
static editor bundle.

Architecture

- `mod_exeweb\local\styles_service` owns the pure logic: ZIP
  validation (path traversal, absolute paths, size cap, extension
  allow-list, single config.xml), slug allocation with collision
  suffix, registry persistence in config_plugin(exeweb), and the
  `build_theme_registry_override()` payload the editor consumes.
- `admin/styles.php` is a standard Moodle admin_externalpage that
  renders the upload form and lists, using HTTP POST + sesskey
  (no custom AJAX).
- `editor/styles.php/{slug}/{file}` serves the extracted style
  assets from moodledata with PATH_INFO, gated by site capability
  checks and a registry membership check.
- Uploaded style bundles extract to
  `{dataroot}/mod_exeweb/styles/{slug}/` — a sibling of
  `mod_exeweb/embedded_editor/` so reinstalling the embedded editor
  never destroys admin-managed styles.
- `editor/index.php` injects
  `window.eXeLearning.config.themeRegistryOverride` and mirrors
  `blockImportInstall` onto the pre-existing `userStyles`
  (ONLINE_THEMES_INSTALL) flag so the 'Import this project style?'
  modal is suppressed end-to-end.

Admin toggle

- `stylesblockimport` (default: 1) controls whether imported
  project styles are refused. When on, the editor hides the
  'Imported' tab (see companion core PR) and silently falls back
  to the default style instead of offering to install.

Behavior

- Disabled built-ins disappear from the editor's selector.
- Uploaded styles show up alongside built-ins with stable ids.
- Projects referencing a missing/disabled style fall back to `base`.
- The admin link appears only when editor mode is 'embedded'.

Tests

- `tests/local/styles_service_test.php`: ZIP validator edge cases,
  install, unique-slug on collision, delete, override enabled flag,
  import-blocked config contract.

Language

- Adds strings under `styles*` in `lang/en/exeweb.php`.

Depends on

- Core editor hook: exelearning/exelearning#1722 (merged).
- Core editor UI follow-up: exelearning/exelearning#1724 (hides
  the 'Imported' tab when blockImportInstall is set).
erseco added a commit to exelearning/wp-exelearning that referenced this pull request Apr 23, 2026
…ditor

Adds a Settings → eXeLearning → Styles section where administrators can
upload eXeLearning style ZIPs, enable/disable built-in styles, and
enable/disable/delete uploaded ones — without rebuilding the static
editor bundle or touching dist/static.

Architecture

- `ExeLearning_Styles_Service` owns the pure logic: ZIP validation
  (path traversal, absolute paths, size cap, extension allow-list,
  single config.xml), slug allocation with collision suffix, registry
  persistence in the `exelearning_styles_registry` option, and the
  `build_theme_registry_override()` payload the editor consumes.
- `ExeLearning_Admin_Styles` exposes nonce-protected admin-ajax
  endpoints for upload/toggle/delete, gated on `manage_options`.
- Uploaded style bundles extract to
  `wp-content/uploads/exelearning-styles/<slug>/` — a sibling of the
  editor install, so `make build-editor` / the installer cannot
  destroy admin-managed styles.
- `admin/views/editor-bootstrap.php` injects the approved registry
  into the page as `window.eXeLearning.config.themeRegistryOverride`
  before the editor scripts load. The static editor merges/filters
  the bundle themes and refuses install-from-content (core hook
  landed upstream in exelearning/exelearning#1722).

Behavior

- Disabled built-ins disappear from the editor's style selector.
- Uploaded styles show up alongside built-ins with stable ids.
- Projects referencing a missing/disabled style fall back to `base`.
- Default max ZIP size is 20 MB, filterable via
  `exelearning_styles_max_zip_size`.
- No mandatory platform-wide "default style" setting is introduced.

Tests

- `tests/unit/StylesServiceTest.php` covers:
  reject-missing-config, reject-traversal, reject-disallowed-ext,
  accept-valid-root, accept-single-root-folder, install extracts +
  registers, unique-slug on collision, delete removes files + entry,
  override respects enabled flag and includes the block-import flag.
- A standalone smoke run of the ZIP validator + registry math (7
  assertions) was executed locally during development.

Docs

- README "Managing styles" section describes the admin UX, storage
  location, fallback behavior, and the size filter.
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 24, 2026

Bundle Report

Changes will increase total bundle size by 935 bytes (0.07%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
app-bundle 831.21kB 935 bytes (0.11%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: app-bundle

Assets Changed:

Asset Name Size Change Total Size Change (%)
app.bundle.js 935 bytes 831.21kB 0.11%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants