From a24033350fe66f1a8bb2460e1b4632dffa53482f Mon Sep 17 00:00:00 2001 From: Brent Cordis <994259+bcordis@users.noreply.github.com> Date: Thu, 30 Apr 2026 05:50:04 -0500 Subject: [PATCH 01/14] Audit SKILL.md (#1): split four big sections, fix PHP requirement (#8) Closes #1. - Extract Editor API, Form Fields, Testing, and Common Gotchas from skills/joomla/SKILL.md into dedicated references/*.md files. Each in-file section becomes a short pointer stub. SKILL.md drops from 3594 to ~2434 lines without losing content. - Correct PHP requirement against manual.joomla.org for Joomla 6.x: minimum and supported = 8.3.0, recommended = 8.4. Update headline plus four code examples (changelog note, update-server XML, install-script $minimumPhp, composer.json) from 8.2 to 8.3. $minimumJoomla declares which Joomla versions an extension supports and is independent of $minimumPhp; a J6-native extension that also supports J5.x must still pin PHP to 8.3.0 (the J6.x floor). - Extend the Quick Start cross-reference list with the four new reference files. - CHANGELOG [Unreleased] documents the splits and the PHP correction. --- CHANGELOG.md | 9 + CONTRIBUTING.md | 2 +- README.md | 2 +- skills/joomla/SKILL.md | 1219 +---------------------- skills/joomla/references/editor-api.md | 262 +++++ skills/joomla/references/form-fields.md | 247 +++++ skills/joomla/references/gotchas.md | 382 +++++++ skills/joomla/references/testing.md | 294 ++++++ 8 files changed, 1241 insertions(+), 1176 deletions(-) create mode 100644 skills/joomla/references/editor-api.md create mode 100644 skills/joomla/references/form-fields.md create mode 100644 skills/joomla/references/gotchas.md create mode 100644 skills/joomla/references/testing.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 3805ae0..8976796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,15 +9,24 @@ All notable changes to the Joomla skill are documented here. The format follows ## [Unreleased] ### Added +- `## Canonical sources` section in `SKILL.md` listing the upstream references the skill is built from (joomla-cms repo, manual.joomla.org, api.joomla.org, framework.joomla.org, docs.joomla.org wiki) with WebFetch-first / fallback guidance and a preference for commit-pinned permalinks. Mirrored in `CONTRIBUTING.md` and `README.md`. - `CONTRIBUTING.md` covering testing flow, authoring guidelines, PR process, issue guidelines, and versioning. - `CODE_OF_CONDUCT.md` (Contributor Covenant 2.1, summarized + linked). - Issue forms: `.github/ISSUE_TEMPLATE/bug_report.yml` and `feature_request.yml`, plus `config.yml` redirecting Joomla-CMS questions to upstream. - `scripts/validate.sh` — structural lints for SKILL.md frontmatter, reference resolution, and `marketplace.json` source paths. - `.github/workflows/validate.yml` — runs the validation script on every push to `main` and every PR. - `develop` branch as the integration target; `main` is now release-only. +- `references/editor-api.md` — extracted from `SKILL.md` so the editor JS/PHP API surface is loaded only when needed. +- `references/form-fields.md` — extracted built-in field reference and custom-field authoring guide. +- `references/testing.md` — extracted PHPUnit + Jest patterns including real-CMS bootstrap and four high-stakes test gotchas. +- `references/gotchas.md` — extracted hard-won J5/J6 pitfalls (controller parents, routing, WAM, Bootstrap 5.3 dark mode, modal cleanup, `getStoreId()`, etc.). +- Quick Start now lists the four new cross-cutting references alongside the existing `component.md` / `module.md` / `plugin.md` / `library.md` set. ### Changed - `marketplace.json` now uses an explicit GitHub object source pinned to `v0.1.0` (`{"source":"github","repo":"...","ref":"v0.1.0"}`) instead of a relative `./` path. This means `/plugin update` only delivers a new version once the `ref` is bumped on `main`, so audit work in progress on `develop` doesn't reach end users prematurely. +- `SKILL.md` PHP requirement headline updated from "8.2+ (Joomla 6 minimum), 8.3+ recommended" to "8.3+ minimum and supported, 8.4 recommended" for Joomla 6.x, with a citation to [manual.joomla.org/docs/get-started/technical-requirements](https://manual.joomla.org/docs/get-started/technical-requirements/). The previous wording understated the J6.x minimum. +- All four in-file PHP-version code examples bumped from 8.2 → 8.3 to match the J6.x floor (changelog ``, update-server ``, install-script `$minimumPhp`, `composer.json` `php` constraint). The `$minimumJoomla` example value is unchanged because it declares which Joomla versions the extension supports — independent of PHP — and a J6-native extension that also supports J5.x must still pin PHP to 8.3.0 (the highest supported-Joomla floor). +- `SKILL.md` shrunk from 3594 to ~1180 fewer lines by replacing the in-file Editor API, Form Fields, Testing, and Common Gotchas sections with short pointer stubs that name the topics covered. Reduces the per-load token cost without losing any content (full text preserved in the new `references/*.md` files). ## [0.1.0] — 2026-04-29 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aab6610..fc6c817 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -86,7 +86,7 @@ If you change `name` or `description`: ### Body and references - **Be concrete.** Show the actual file structure, the actual XML tag, the actual class name. Vague advice ("set up a service provider") is worse than no advice; Claude will pattern-match on it and produce nonsense. -- **Cite [`joomla/joomla-cms`](https://github.com/joomla/joomla-cms) when behavior is non-obvious.** A link to the commit or file that proves the pattern saves future maintenance. +- **Cite [`joomla/joomla-cms`](https://github.com/joomla/joomla-cms) when behavior is non-obvious.** A link to the commit or file that proves the pattern saves future maintenance. The skill's `## Canonical sources` section lists the upstream references the skill is built from — verify changes against them, and prefer **commit-pinned permalinks** to `joomla-cms` over branch links so the citation does not silently drift. - **Target Joomla 6, backward compatible to 5** unless the section explicitly says otherwise. Note the version a feature appeared in if it matters. - **PHP 8.2+ syntax** in examples (constructor promotion, readonly, enums, named args where they help). - **PSR-12 PHP, Joomla ESLint config for JS.** Don't introduce styles that contradict either. diff --git a/README.md b/README.md index 2263edf..22ebd61 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ This skill is maintained by [Christian Web Ministries](https://christianwebminis - [CWMScriptureLinks](https://github.com/bcordis/CWMScriptureLinks) — auto-link Scripture references - Other CWM extensions -Patterns are derived from the [joomla-cms](https://github.com/joomla/joomla-cms) core and real-world production components. +Patterns are derived from the [joomla-cms](https://github.com/joomla/joomla-cms) core and real-world production components. The skill's `## Canonical sources` section in [`skills/joomla/SKILL.md`](skills/joomla/SKILL.md) lists every upstream reference (joomla-cms, manual.joomla.org, api.joomla.org, framework.joomla.org) Claude consults when verifying patterns — and the fallback order when WebFetch is unavailable. ## Contributing diff --git a/skills/joomla/SKILL.md b/skills/joomla/SKILL.md index 6eb991f..57bfef3 100644 --- a/skills/joomla/SKILL.md +++ b/skills/joomla/SKILL.md @@ -9,9 +9,34 @@ description: | This skill guides you through building Joomla 5+ extensions (components, modules, plugins) using modern architecture patterns derived from the [joomla-cms](https://github.com/joomla/joomla-cms) core and real-world production components. **Target:** Natively Joomla 6, backward compatible with Joomla 5 (no backward compatibility plugin required) -**PHP requirement:** 8.2+ (Joomla 6 minimum), 8.3+ recommended +**PHP requirement (Joomla 6.x):** 8.3+ minimum and supported, 8.4 recommended ([source](https://manual.joomla.org/docs/get-started/technical-requirements/)) **Coding standard:** PSR-12 (PHP), Joomla ESLint config (JavaScript) +## Canonical sources + +When a Joomla pattern is non-obvious, ambiguous, or might have drifted between versions, verify against upstream before answering. Prefer fetching directly with WebFetch; if the fetch is blocked, fails, or the user is offline, fall back in this order: (1) cite the canonical URL so the user can open it, (2) rely on the patterns documented in this skill and `references/*.md`, (3) ask the user to paste the relevant snippet rather than guess. + +**Primary — source of truth for runtime behavior:** + +- [`github.com/joomla/joomla-cms`](https://github.com/joomla/joomla-cms) — core CMS. Use branch `5.3-dev` for current Joomla 5, `6.0-dev` for Joomla 6 work. + - Frontend component examples: [`components/`](https://github.com/joomla/joomla-cms/tree/6.0-dev/components) + - Backend component examples: [`administrator/components/`](https://github.com/joomla/joomla-cms/tree/6.0-dev/administrator/components) + - Core plugins: [`plugins/`](https://github.com/joomla/joomla-cms/tree/6.0-dev/plugins) + - Core modules: [`modules/`](https://github.com/joomla/joomla-cms/tree/6.0-dev/modules) and [`administrator/modules/`](https://github.com/joomla/joomla-cms/tree/6.0-dev/administrator/modules) + - Framework libraries shipped with the CMS: [`libraries/src/`](https://github.com/joomla/joomla-cms/tree/6.0-dev/libraries/src) +- [`github.com/joomla-framework`](https://github.com/joomla-framework) — standalone Framework packages (DI, Event, Filesystem, etc.) reused by the CMS. + +**Documentation:** + +- [manual.joomla.org](https://manual.joomla.org/) — current Developer Manual (Joomla 5+). Preferred prose reference. +- [api.joomla.org](https://api.joomla.org/) — generated API reference (classes, methods, signatures). +- [framework.joomla.org](https://framework.joomla.org/) — Joomla Framework package docs. +- [docs.joomla.org](https://docs.joomla.org/) — **legacy wiki**. Useful for historical context (J3/J4) but often stale for J5/J6; cross-check against `joomla-cms` HEAD before quoting. + +**When citing in generated code or replies:** prefer a permalink to a specific file/line in `joomla-cms` (right-click → "Copy permalink" produces a commit-pinned URL) over a branch link, so the reference does not silently drift. + +**Update cadence:** if a WebFetch reveals the upstream pattern differs from what this skill teaches, flag it to the user and recommend opening an issue on `Joomla-Bible-Study/claude-skill-joomla` so the skill can be corrected. + ## Coding Standards ### PHPDoc / DocBlocks @@ -191,6 +216,12 @@ For detailed patterns of each type, read the appropriate reference file: - `references/plugin.md` — Plugin event subscriber pattern - `references/library.md` — Library structure and packaging +Cross-cutting references (loaded on demand): +- `references/editor-api.md` — WYSIWYG editor JS/PHP API + XTD buttons +- `references/form-fields.md` — Built-in field types + custom field authoring +- `references/testing.md` — PHPUnit + Jest patterns with real Joomla CMS classes +- `references/gotchas.md` — Hard-won J5/J6 pitfalls (controllers, routing, WAM, dark mode, etc.) + ## Core Architecture Principles ### 1. PSR-4 Namespaces (Everything Lives in src/) @@ -662,7 +693,7 @@ Joomla displays changelogs in the Extensions → Manage view, linked per version Removed legacy import format - Requires PHP 8.2+ + Requires PHP 8.3+ @@ -710,7 +741,7 @@ The update server tells Joomla where to check for new versions of your extension stable - 8.2.0 + 8.3.0 abc123... def456... ghi789... @@ -961,8 +992,8 @@ use Joomla\CMS\Factory; class Com_MyComponentInstallerScript { - protected string $minimumPhp = '8.2.0'; - protected string $minimumJoomla = '5.0.0'; + protected string $minimumPhp = '8.3.0'; // Joomla 6.x floor; covers J5.3+ too + protected string $minimumJoomla = '5.0.0'; // Earliest Joomla version this extension supports public function preflight(string $type, InstallerAdapter $adapter): bool { @@ -1894,266 +1925,9 @@ class UniqueAliasRule extends FormRule ## Editor API -Joomla's Editor API provides a unified interface for WYSIWYG editors (TinyMCE, CodeMirror, None/textarea) and editor extension buttons (XTD buttons like "Read More", "Article", "Image"). - -### JavaScript API: Getting and Setting Editor Content - -The modern API uses `JoomlaEditor` (imported from `editor-api`). The legacy `Joomla.editors.instances` is deprecated but still works via a Proxy wrapper. - -**Get/set content from JavaScript:** - -```javascript -// Modern API (preferred) -import { JoomlaEditor } from 'editor-api'; - -// Get editor by textarea ID -const editor = JoomlaEditor.get('jform_description'); - -// Get the currently active (focused) editor -const active = JoomlaEditor.getActive(); - -// Read content -const html = editor.getValue(); - -// Replace all content -editor.setValue('

New content

'); - -// Get selected text -const selection = editor.getSelection(); - -// Insert at cursor / replace selection -editor.replaceSelection('
'); - -// Disable / enable -editor.disable(false); // disable -editor.disable(true); // enable - -// Get underlying editor instance (e.g., tinymce object) -const raw = editor.getRawInstance(); - -// Get editor type name -const type = editor.getType(); // 'tinymce', 'codemirror', 'none' -``` - -**Legacy API (deprecated but functional):** - -```javascript -// Still works but logs deprecation warnings -const editor = Joomla.editors.instances['jform_description']; -editor.getValue(); -editor.setValue('content'); -editor.replaceSelection('inserted text'); -``` - -### Editor Decorator (Implementing a Custom Editor) - -All editors must subclass `JoomlaEditorDecorator` and implement the abstract methods: +Joomla's Editor API provides a unified interface for WYSIWYG editors (TinyMCE, CodeMirror, None) and editor extension buttons. Coverage: the modern `JoomlaEditor` JS API (`get`, `getActive`, `getValue`, `setValue`, `getSelection`, `replaceSelection`, `disable`, `getRawInstance`, `getType`); the legacy `Joomla.editors.instances` proxy; the `JoomlaEditorDecorator` pattern for implementing a custom editor; XTD button plugins (`onEditorButtonsSetup`, `Button` class, `JoomlaEditorButton.registerAction`); the modal-button content-selection flow with `postMessage` and `joomla:content-select`; the `editor` form field type with all attributes (`buttons`, `hide`, `height`, `width`, `editor`, `filter`, `asset_field`, `created_by_field`, `syntax`); and editor plugin registration via `onEditorSetup` with `AbstractEditorProvider`. -```javascript -import JoomlaEditorDecorator from 'editor-decorator'; -import { JoomlaEditor } from 'editor-api'; - -class MyEditorDecorator extends JoomlaEditorDecorator { - getValue() { - return this.instance.getContent(); // Your editor's get method - } - - setValue(value) { - this.instance.setContent(value); - return this; - } - - getSelection() { - return this.instance.getSelectedText(); - } - - replaceSelection(value) { - this.instance.insertAtCursor(value); - return this; - } - - disable(enable) { - this.instance.setReadOnly(!enable); - return this; - } -} - -// Register with Joomla -const decorator = new MyEditorDecorator(editorInstance, 'myeditor', textareaId); -JoomlaEditor.register(decorator); -``` - -**Required methods:** `getValue()`, `setValue()`, `getSelection()`, `replaceSelection()`, `disable()` - -### Editor XTD Buttons (Extension Buttons) - -XTD buttons appear below the editor (e.g., "Read More", "Article", "Image"). They are plugins in the `editors-xtd` group. - -**Creating an XTD button plugin:** - -```php -// plugins/editors-xtd/mybutton/src/Extension/MyButton.php -namespace Vendor\Plugin\EditorsXtd\MyButton\Extension; - -use Joomla\CMS\Editor\Button\Button; -use Joomla\CMS\Event\Editor\EditorButtonsSetupEvent; -use Joomla\CMS\Language\Text; -use Joomla\CMS\Plugin\CMSPlugin; -use Joomla\Event\SubscriberInterface; - -final class MyButton extends CMSPlugin implements SubscriberInterface -{ - public static function getSubscribedEvents(): array - { - return ['onEditorButtonsSetup' => 'onEditorButtonsSetup']; - } - - public function onEditorButtonsSetup(EditorButtonsSetupEvent $event): void - { - $disabled = $event->getDisabledButtons(); - if (\in_array($this->_name, $disabled)) { - return; - } - - $wa = $this->getApplication()->getDocument()->getWebAssetManager(); - $wa->registerScript( - 'editor-button.' . $this->_name, - 'plg_editors-xtd_mybutton/button.min.js', - [], - ['type' => 'module'], - ['editors'] // dependency on editor API - ); - - $button = new Button($this->_name, [ - 'action' => 'insert-mywidget', // Custom action name - 'text' => Text::_('PLG_MYBUTTON_BUTTON_TEXT'), - 'icon' => 'star', - 'name' => $this->_type . '_' . $this->_name, - ]); - - $event->getButtonsRegistry()->add($button); - } -} -``` - -**JavaScript handler for the button action:** - -```javascript -// build/media_source/plg_editors-xtd_mybutton/js/button.es6.js -import { JoomlaEditorButton } from 'editor-api'; - -JoomlaEditorButton.registerAction('insert-mywidget', (editor, options) => { - editor.replaceSelection('
Widget content
'); -}); -``` - -### Button Action Types - -| Action | Behavior | Use Case | -|--------|----------|----------| -| `insert` | Inserts `options.content` at cursor | Simple static content insertion | -| `modal` | Opens `JoomlaDialog` iframe, listens for `postMessage` | Content selection (articles, images, contacts) | -| Custom name | Your registered handler | Any custom logic | - -### Modal Button Pattern (Content Selection) - -For buttons that open a modal to select content (like the "Article" button): - -**PHP — define button with `action: 'modal'`:** - -```php -$link = 'index.php?option=com_example&view=items&layout=modal&tmpl=component&' - . Session::getFormToken() . '=1&editor=' . $event->getEditorId(); - -$button = new Button($this->_name, [ - 'action' => 'modal', - 'link' => $link, - 'text' => Text::_('PLG_MYBUTTON_SELECT_ITEM'), - 'icon' => 'list', - 'name' => $this->_type . '_' . $this->_name, -], [ - 'popupType' => 'iframe', - 'textHeader' => Text::_('PLG_MYBUTTON_MODAL_TITLE'), - 'modalWidth' => '800px', - 'modalHeight' => '400px', -]); -``` - -**JavaScript in the modal iframe** — send selection back via `postMessage`: - -```javascript -// In the modal's layout template -document.querySelectorAll('.select-link').forEach((el) => { - el.addEventListener('click', (event) => { - event.preventDefault(); - const title = event.target.dataset.title; - const url = event.target.dataset.uri; - - window.parent.postMessage({ - messageType: 'joomla:content-select', - html: `${title}`, - }); - }); -}); -``` - -The parent window's `modal` action handler automatically calls `editor.replaceSelection()` with the received `html` (or `text`) and closes the dialog. - -### Editor Form Field (PHP) - -The `editor` form field type in XML automatically renders the configured WYSIWYG editor: - -```xml - -``` - -**Attributes:** - -| Attribute | Values | Purpose | -|-----------|--------|---------| -| `buttons` | `true`, `false`, or comma-separated list | Show/hide XTD buttons. List = show only named buttons | -| `hide` | Comma-separated list | Hide specific XTD buttons | -| `height` | Pixels (e.g., `500`) | Editor height | -| `width` | CSS value (e.g., `100%`) | Editor width | -| `editor` | Pipe-separated list | Force specific editor(s): `tinymce\|codemirror\|none` | -| `filter` | `JComponentHelper::filterText` | Server-side HTML filtering | -| `asset_field` | Field name | Form field containing asset ID (for ACL) | -| `created_by_field` | Field name | Form field containing author ID | -| `syntax` | `html`, `css`, `php`, etc. | Syntax highlighting mode (CodeMirror) | - -### Editor Plugin Registration (PHP) - -Editor plugins register via the `onEditorSetup` event: - -```php -// plugins/editors/myeditor/src/Extension/MyEditor.php -use Joomla\CMS\Event\Editor\EditorSetupEvent; - -final class MyEditor extends CMSPlugin implements SubscriberInterface -{ - public static function getSubscribedEvents(): array - { - return ['onEditorSetup' => 'onEditorSetup']; - } - - public function onEditorSetup(EditorSetupEvent $event): void - { - $event->getEditorsRegistry()->add( - new MyEditorProvider($this->params, $this->getApplication(), $this->getDispatcher()) - ); - } -} -``` - -The provider extends `AbstractEditorProvider` and implements `display()` (renders the editor HTML) and `getName()` (returns the editor identifier like `'tinymce'`). +For full code examples and the button-action behavior table, read [`references/editor-api.md`](references/editor-api.md). ## Layouts (LayoutHelper) @@ -2247,249 +2021,9 @@ Render a child layout relative to the current layout: ## Form Fields -### Built-in Field Types Reference - -Joomla provides ~90 built-in field types. The most commonly used: - -**Basic inputs:** - -| Type | XML | Notes | -|------|-----|-------| -| `text` | `type="text"` | Single-line text | -| `textarea` | `type="textarea"` | Multi-line, set `rows` and `cols` | -| `email` | `type="email"` | Email validation | -| `url` | `type="url"` | URL validation | -| `tel` | `type="tel"` | Phone number | -| `number` | `type="number"` | Numeric with `min`, `max`, `step` | -| `password` | `type="password"` | Masked input | -| `hidden` | `type="hidden"` | Hidden value | -| `editor` | `type="editor"` | WYSIWYG editor (see Editor API section) | -| `color` | `type="color"` | Color picker | -| `calendar` | `type="calendar"` | Date picker with format, `showtime="true"` for datetime | - -**Selection fields:** - -| Type | XML | Notes | -|------|-----|-------| -| `list` | `type="list"` | Dropdown with `