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
117 changes: 96 additions & 21 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,96 @@
# AGENTS.md — eXeLearning

WordPress integration for uploading, managing, editing and embedding eXeLearning
ELPX content. Keep the WordPress 6.1 / PHP 8.0 minimum declared in `exelearning.php`.
WordPress plugin for uploading, managing, editing and embedding eXeLearning `.elpx`
packages. Keep the WordPress 6.1 / PHP 8.0 minimum declared in `exelearning.php`.
Use the existing Bootstrap 5/jQuery UI and registered block; do not scaffold a
second plugin or replace the build pipeline when following a generic skill.

This is the single guide for every coding agent; `CLAUDE.md` only imports it.

## How the plugin works

Packages are plain WordPress **attachments** — there is no custom post type.

1. An `.elpx` is uploaded to the Media Library (`includes/class-mime-types.php`
registers the type; legacy `.elp` v2 files are rejected).
2. `ExeLearning_Elp_File_Service` validates it (ZIP + `content.xml`, zip-slip,
entry-count and size limits) and extracts it to
`wp-content/uploads/exelearning/{sha1}/`.
3. Package metadata is stored as attachment post meta.
4. Content is served by the REST content proxy and embedded with the
`[exelearning]` shortcode or the `exelearning/elp-upload` block.

| Area | Files |
|------|-------|
| Bootstrap | `exelearning.php` (`require_once` for every class), `includes/class-exelearning.php` (creates components; most register their own hooks), `includes/class-upgrader.php` (option migrations keyed on `exelearning_db_version`) |
| Ingestion | `includes/class-elp-upload-handler.php` (upload, extraction, delete cleanup), `includes/class-elp-file-service.php` |
| Reprocessing | `includes/class-elp-reprocessor.php` (class `ExeLearning_Reprocessor`), used by REST save/reprocess, the Media Library bulk action and `wp exelearning reprocess` (`includes/class-cli-command.php`); `includes/class-content-hash-aliases.php` keeps retired hashes as redirects (ADR-68-01) |
| Content delivery | `includes/class-content-proxy.php`: `GET /wp-json/exelearning/v1/content/{hash}/{file}` with security headers and CSP. A generated `.htaccess` blocks direct HTML/SVG/XML access under `uploads/exelearning/` (Apache only); the `exelearning_content_origin` filter serves content from a separate host |
| Frontend | `public/class-shortcodes.php`, `includes/class-elp-upload-block.php` (Block API v3), `includes/class-viewer-enhancements.php` (shortcode only), `includes/class-download-button-renderer.php` + `includes/class-export-bootstrap.php` (exports through the editor in a hidden `?exe_export=1` iframe) |
| Admin | `admin/class-admin-settings.php`, `includes/integrations/class-media-library.php` (columns, meta boxes, previews, bulk reprocess), styles: `admin/class-admin-styles.php`, `includes/class-styles-service.php`, `includes/class-style-package.php` (`uploads/exelearning-styles/{slug}/`) |
| Embedded editor | `includes/class-exelearning-editor.php`, `admin/views/editor-bootstrap.php` (loads `dist/static/index.html` with WordPress config), `assets/js/exelearning-editor.js` (modal) ↔ postMessage ↔ `assets/js/wp-exe-bridge.js` (inside the editor iframe), `includes/class-editor-bundle.php` |

### REST API (`exelearning/v1`)

| Route | Method | Permission |
|-------|--------|------------|
| `/content/{hash}/{file}` | GET | public (hash is the capability) |
| `/save/{id}` | POST | `edit_post` on the attachment |
| `/create` | POST | `upload_files` |
| `/elp-data/{id}` | GET | `edit_post` |
| `/reprocess/{id}` | POST | `edit_post` |

### Data

- Post meta: `_exelearning_title`, `_exelearning_description`,
`_exelearning_license`, `_exelearning_language`, `_exelearning_resource_type`,
`_exelearning_version`, `_exelearning_extracted` (SHA1 of the extraction folder),
`_exelearning_has_preview` (`1` when `index.html` exists),
`_exelearning_obsolete_hash` (retired hashes kept as redirect aliases).
- Options: `exelearning_db_version`, `exelearning_proxy_assets`,
`exelearning_styles_registry`, `exelearning_styles_block_import`,
`exelearning_disabled_styles`; `uninstall.php` deletes them and keeps user content.

## Project boundaries

- `exelearning.php` bootstraps `includes/`; admin screens live in `admin/`,
shortcode rendering in `public/class-shortcodes.php`.
- Archive processing, styles and content delivery go through the existing
file-service, style-service and content-proxy classes. Preserve capability,
nonce, path-validation and content-delivery boundaries when changing them.
Package HTML is untrusted author content served on the site origin. Do not
drop `allow-same-origin` from its iframes piecemeal: packages need storage and
cookies, embedded video needs the parent relay, and Playground cannot serve
opaque-origin subframes. The opaque-origin viewer lands as a whole in #56.
- Update `docs/SHORTCODES.md` with shortcode attributes and `docs/HOOKS.md` with
public actions/filters in the same change.
- The embedded editor is built with `make build-editor`; `npm run build` is only
a reminder, not a build. Do not hand-edit generated editor output.
- The block uses Block API version 3 (change 89), which WordPress 6.1 still loads.
Do not move it to script modules while WordPress 6.1 remains supported.
- `.distignore` controls `wp dist-archive` releases; `.gitattributes` controls
source archives used by Playground. They are different contracts, not lists to
synchronize. Root-only dist rules need `/` so they do not strip editor assets.
- For durable architecture changes, use `docs/architecture/README.md` and its
ADR/change guides. Records use the carrying PR number (issues are disabled).
Preserve accepted history, record `ai_assistance`, and run `make architecture-check`.

### Embedded editor build

`exelearning/` is a gitignored clone of `exelearning/exelearning`, not a submodule;
`dist/static/` is its generated build. Do not hand-edit either. `npm run build`
is only a reminder, not a build.

- `make build-editor`: fetch the source and build it (needs Bun).
- `make build-editor-if-needed`: build only when `dist/static/.build-commit` differs
from the clone's HEAD (used by `make up`; keeps the existing build when offline).
- `EXELEARNING_EDITOR_REF` / `_REF_TYPE` (`auto`, `branch`, `tag`; env or `.env`)
choose the ref, default `main`. Releases pin the editor tag in `.editor-version`.

## Local environment

- `make up` / `make down`: wp-env on http://localhost:8888 (admin/password); the
tests site is on 8889. `make clean` resets it, `make destroy` removes it.
- The tests container's uploads directory persists between runs: register every
file or folder a test creates and delete it in `tear_down()`, never inline after
the assertions.

## Verification

`composer install` and the committed dependency configuration provide tooling;
Expand All @@ -31,10 +99,14 @@ do not add coding-standard packages just because an upstream example does.
formatting needs correction. Do not replace the repository ruleset with a bare
`--standard=WordPress .` scan.

- PHP: `make lint`, `make test`, `make phpmd`; do not raise `phpmd.xml` budgets.
- PHP: `make lint`, `make test` (`FILTER=Name` narrows it), `make phpmd`; do not
raise `phpmd.xml` budgets.
- Browser JS/block behavior: `npm run test:js`; affected UI flows: `make test-e2e`.
- Translation changes: `make check-untranslated` and commit the generated catalogs.
- Translations: `make translations` (`composer make-translations`, vendored WP-CLI;
never the wp-env container's `wp`), then `make check-untranslated`; commit the
generated catalogs.
- Architecture records: `make architecture-check`.
- Workflows: `actionlint`.
- Plugin distribution: `make check-plugin`; inspect the actual release archive.
- `make check` also applies automatic fixes; it is not a read-only verification command.

Expand All @@ -43,7 +115,9 @@ commands, read [testing notes](.agents/references/testing.md).

## Working conventions

- Branches use English names with `feature/` or `hotfix/`; PRs target `main`.
- Branches use English names with `feature/` or `hotfix/`. Standalone PRs target
`main`; a stacked PR may target the previous branch of its stack, and the stack
is merged in order.
- Follow the repository PHPCS ruleset and current source. English PHPDoc precedes
functions/methods. Unslash request data before sanitizing; escape at output.
- Check capabilities and resource ownership as well as nonces at write boundaries;
Expand All @@ -55,10 +129,10 @@ commands, read [testing notes](.agents/references/testing.md).
- No production deployment, release publication or data mutation is implied by
a local implementation task. Respect authorization already given in the session.

English source strings use the plugin text domain; Spanish translations and
assertions preserve the user-facing language. Update catalogs with string changes,
use plural-aware translation functions, and add `translators:` comments for
placeholders. JS strings/nonces/URLs use the existing localization pipeline.
English source strings use the plugin text domain; every shipped locale gets a
translation, and assertions preserve the user-facing language (the test site runs
`es_ES`). Use plural-aware translation functions and add `translators:` comments
for placeholders. JS strings/nonces/URLs use the existing localization pipeline.

## Skills

Expand All @@ -80,23 +154,24 @@ Install upstream skills with `gh skills install OWNER/REPO skills/NAME --dir .ag
Keep upstream text and `metadata.github-*` unchanged; fix upstream and reinstall.
Local skills have no GitHub provenance and the updater skips them. Put project
exceptions in local guidance, not inside installed upstream folders.
New Claude entries are symlinks to `../../.agents/skills/NAME`, so Claude reads
the same skill directories.

WordPress skills may target 7.0+: verify APIs against this project's supported
versions. Do not upgrade requirements, scaffold new packages or change architecture
merely because a generic skill recommends it. Resolve example `skills/...` paths
under the actual `.agents/skills/` installation; use existing commands first.

New Claude entries are symlinks to `../../.agents/skills/NAME`.
Claude reads the same skill directories through those symlinks.

`.github/workflows/update-agent-skills.yml` checks weekly/on dispatch, scoped to
installed skills, and opens a review PR on `main`. It never merges updates.
Review prompt diffs as behavior changes. PRs made with the default GitHub token
may not trigger CI; do not assume green checks will appear automatically.

The block skill must not force apiVersion 3 or script modules while WordPress 6.1
remains supported. Preserve the existing block registration/build compatibility.
### GitHub Actions

Maintainer preference: use `actions/checkout@v7` and
`devantler-tech/actions/update-agent-skills@v13.3.3`; prefer the floating major
`v13` when upstream provides it. Use `peter-evans/create-pull-request@v8` too. Keep all actions in the skill-update workflow on version tags, not SHAs.
Keep actions on version tags maintained by Renovate, not SHAs: `actions/checkout@v7`,
`peter-evans/create-pull-request@v8`, and `devantler-tech/actions/update-agent-skills@v13.3.3`
(prefer the floating `v13` once upstream provides it). Pass external values
(release tags, dispatch inputs, event fields) to `run:` steps through `env:`, never
`${{ }}` inside the script, and use `persist-credentials: false` on checkouts in
jobs that install third-party packages.
151 changes: 1 addition & 150 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,150 +1 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

It describes *how the plugin works*. The coding conventions — WPCS, tabs, translations, PHPMD
thresholds, the ADR/change-document policy — live in [`AGENTS.md`](AGENTS.md); read that before writing code.

## Skills

`.agents/skills/` holds vendored third-party skills; `.claude/skills/` symlinks to them.
Read the relevant one before working in its area — `wp-plugin-development` (hooks, Settings API,
packaging), `wp-rest-api` (the `/exelearning/v1/` routes below), `wp-plugin-directory-guidelines`
(`readme.txt`, licensing, what `make check-plugin` enforces), `blueprint` (`blueprint.json`,
Playground), `security-audit`. Never reformat or edit a skill in place. Details in `AGENTS.md`.

## Project Overview

eXeLearning is a WordPress plugin for managing eXeLearning .elp files. It allows uploading, extracting, and embedding eXeLearning content in WordPress pages and posts.

**Key principle**: The plugin works directly with WordPress **attachments** (Media Library). No custom post type is needed - ELP files are uploaded as attachments and their metadata is stored in attachment post meta.

## Development Commands

### Static Editor Build
```bash
make build-editor # Build static eXeLearning editor from submodule (requires Bun)
make build-editor-no-update # Build without updating submodule (for CI/CD)
make update-submodule # Update eXeLearning submodule to correct branch
make clean-editor # Remove static editor build and node_modules
```

**Note**: The static editor is built from the `exelearning/` submodule (branch `release/3.1-embedable-version-refactor`). Output is placed in `dist/static/`.

### Environment Setup
```bash
make up # Start wp-env Docker containers (http://localhost:8888, admin/password)
make down # Stop containers
make clean # Reset WordPress environment
make destroy # Completely remove wp-env
```

### Testing
```bash
make test # Run all PHPUnit tests
make test FILTER=MyTest # Run tests matching pattern
```

### Code Quality
```bash
make fix # Auto-fix code style with PHPCBF
make lint # Check code style with PHPCS
```

### Translations
```bash
make pot # Generate .pot file
make po # Update .po files
make mo # Generate .mo files
```

## Architecture

### How It Works

1. User uploads `.elp` file to Media Library
2. Plugin validates the file using ElpParser
3. File is extracted to `wp-content/uploads/exelearning/{sha1_hash}/`
4. Metadata from ELP is stored in attachment post meta
5. Content is embedded via shortcode or Gutenberg block

### Core Components

- **exelearning.php**: Main plugin file
- **includes/class-exelearning.php**: Core class that initializes all components
- **includes/class-hooks.php**: WordPress action registration
- **includes/class-filters.php**: WordPress filter registration

### ELP File Handling

- **includes/class-elp-upload-handler.php**: Handles ELP file upload and extraction
- **includes/class-elp-file-service.php**: Validates, parses, and extracts ELP files (inline ZipArchive + SimpleXML)
- **includes/class-elp-upload-block.php**: Gutenberg block for embedding ELP content
- **includes/class-mime-types.php**: Registers `.elp` MIME type for WordPress uploads

### Admin

- **admin/class-admin-settings.php**: Settings page
- **admin/class-admin-upload.php**: Admin upload handler

### Public/Frontend

- **public/class-shortcodes.php**: `[exelearning]` shortcode handler
- **includes/integrations/class-media-library.php**: Media library integration (columns, meta boxes)

## Data Storage

ELP files use WordPress attachments. Metadata is stored in post meta:

| Meta Key | Description |
|----------|-------------|
| `_exelearning_title` | Title from ELP file |
| `_exelearning_description` | Description from ELP file |
| `_exelearning_license` | License information |
| `_exelearning_language` | Content language |
| `_exelearning_resource_type` | Learning resource type |
| `_exelearning_extracted` | SHA1 hash pointing to extraction folder |

## File Storage

When an ELP file is uploaded:
1. Original `.elp` file stored in Media Library
2. Extracted to `wp-content/uploads/exelearning/{sha1_hash}/`
3. Content accessible via `index.html` in extraction folder

## Embedded Editor

The plugin includes a fully embedded eXeLearning editor built from the static PWA version.

### Key Files

- **dist/static/**: Static build of eXeLearning editor (generated by `make build-editor`)
- **admin/views/editor-bootstrap.php**: Loads static editor with WordPress configuration
- **assets/js/wp-exe-bridge.js**: Bridge JavaScript connecting editor with WordPress
- **includes/class-exelearning-rest-api.php**: REST endpoints for saving/creating ELP files

### How the Editor Works

1. User clicks "Edit in eXeLearning" on an attachment
2. Plugin loads `dist/static/index.html` and injects WordPress config
3. Bridge JS (`wp-exe-bridge.js`) handles:
- Loading ELP file from WordPress into editor
- Saving edited content back to WordPress via REST API
- Keyboard shortcuts (Ctrl+S to save)
4. Editor runs 100% client-side (static PWA)

### REST API Endpoints

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/exelearning/v1/save/{id}` | POST | Update existing ELP file |
| `/exelearning/v1/create` | POST | Create new ELP file |
| `/exelearning/v1/elp-data/{id}` | GET | Get ELP file metadata |

## Key Patterns

- WordPress Coding Standards enforced via PHPCS
- Tests run inside wp-env container
- ELP files are ZIP archives with XML metadata
- No custom post type - uses WordPress attachments
- Static editor (PWA) runs client-side, no backend required
@AGENTS.md
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ index and `make architecture-check` validates it.

### Working with AI coding agents

Coding conventions live in [`AGENTS.md`](AGENTS.md); [`CLAUDE.md`](CLAUDE.md)
describes how the plugin is put together.
[`AGENTS.md`](AGENTS.md) is the single guide for coding agents: how the plugin is
put together, commands and conventions. [`CLAUDE.md`](CLAUDE.md) only imports it.

Reusable procedures ship as agent skills in `.agents/skills/` (symlinked from
`.claude/skills/`):
Expand Down
2 changes: 1 addition & 1 deletion exelearning.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
// Integration classes.
require_once EXELEARNING_PLUGIN_DIR . 'includes/integrations/class-media-library.php';

// ELP File Service (validates, parses and extracts .elp files).
// ELP File Service (validates, parses and extracts .elpx packages).
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-elp-file-service.php';

// Reprocessor for existing attachments (reused by the REST API and entry points).
Expand Down
Loading