diff --git a/Admin-Guide.md b/Admin-Guide.md
new file mode 100644
index 0000000..927195e
--- /dev/null
+++ b/Admin-Guide.md
@@ -0,0 +1,67 @@
+# Admin Guide
+
+Open **Components → MokoSuiteOpenGraph** in the Joomla administrator.
+
+## Dashboard (default view)
+
+The dashboard is the landing screen. It shows:
+
+- **Coverage donut** — percentage of published articles that have an OG tag (green ≥ 80%, amber ≥ 50%, red below)
+- **Field coverage gaps** — how many tags are missing a custom title, description, or image
+- **Coverage by content type** — a per-`content_type` breakdown (com_content, menu, category, com_mokoshop)
+- **Articles missing OG tags** — the most recent published articles with no tag, each linking to the article editor, plus a **Batch Generate** shortcut
+
+Switch between the dashboard and the tag list using the component submenu (**Dashboard** / **Tags**).
+
+## Tag Manager (Tags)
+
+Lists every OG tag record. From here you can:
+
+- **Search and filter** by OG title and content type
+- **Open the editor** by clicking a tag's title
+- **New** — create a tag manually (you choose the content type, content ID, and language)
+- **Edit** — select a row and edit it
+- **Publish / Unpublish / Delete** — standard toolbar actions
+- **SEO health badges** — flags for missing description, overly long title, or noindex
+- **Debug links** — quick links to the Facebook Debugger, LinkedIn Inspector, and Google Rich Results test
+
+### Editing a tag
+
+The edit screen has two tabs:
+
+- **Details** — content type, content ID, language, OG title/description/image/type, og:video, published state
+- **SEO Meta Tags** — SEO title, meta description, robots, canonical URL
+
+`maxlength` hints match the database limits (SEO title 70, meta description 200). Required fields are validated on save.
+
+## Batch Generation
+
+Click **Batch Generate** (toolbar or dashboard). The component:
+
+1. Counts published articles that have no OG tag
+2. Processes them in chunks, generating OG title/description/image from the article content
+3. Shows a live progress bar
+
+Requires the `mokoog.batch` permission (or core `create`).
+
+## CSV Import / Export
+
+- **Export CSV** — downloads all OG tags (including `og_video`, `event_data`, `recipe_data`, `custom_schema`)
+- **Import CSV** — click **Import**, choose a `.csv` file, and upload. New columns are appended, so older exports still import. JSON columns are validated (objects/arrays only) and `og_video` is restricted to http/https.
+
+The CSV header row defines the columns; `content_type`, `content_id`, and `language` form the unique key used to update-or-insert each row.
+
+Requires the `mokoog.import` permission (or core `create` + `edit`).
+
+## Permissions
+
+Open **Options → Permissions** to grant per-group access to the custom actions:
+
+- `mokoog.batch` — batch generation
+- `mokoog.import` — CSV import/export
+
+These complement the standard core actions (`core.create`, `core.edit`, `core.delete`, `core.manage`).
+
+## Plugin Settings
+
+Site-wide behaviour (defaults, JSON-LD, sitemap, AI, LocalBusiness) is configured in the **System - MokoSuiteOpenGraph** plugin — see [Configuration](Configuration).
diff --git a/Architecture.md b/Architecture.md
index 9b58d9f..21e3d63 100644
--- a/Architecture.md
+++ b/Architecture.md
@@ -4,48 +4,57 @@
```
pkg_mokoog
- com_mokoog -- Admin component (dashboard, CRUD, batch, CSV)
- plg_system_mokoog -- System plugin (frontend meta tag injection)
- plg_content_mokoog -- Content plugin (editor form fields, live preview)
- plg_webservices_mokoog -- WebServices plugin (REST API routes)
+ com_mokoog -- Admin component (dashboard, tag CRUD, batch, CSV, ACL)
+ plg_system_mokoog -- System plugin (frontend meta tag injection, sitemap, AI)
+ plg_content_mokoog -- Content plugin (editor form fields, live preview)
+ plg_webservices_mokoog -- WebServices plugin (REST API routes)
```
+Targets **Joomla 6+** and **PHP 8.2+**. All extensions use the modern namespaced DI architecture: `services/provider.php` providers, `SubscriberInterface` event subscribers, and namespaced MVC. No deprecated `Factory::getDbo/getUser/getSession/getLanguage`, `Joomla\CMS\Filesystem`, or `jexit()` calls remain (forward-compatible with Joomla 7).
+
## Data Flow
-### Frontend (onBeforeCompileHead)
-1. System plugin detects current page (`option`, `view`, `id`)
+### Frontend (`onBeforeCompileHead`)
+1. System plugin detects the current page (`option`, `view`, `id`)
2. Loads custom OG data from `#__mokoog_tags` (language-aware)
3. Falls back to category/menu OG data if available
4. Auto-generates missing fields from article/product content
-5. Resolves image URL, auto-resizes if enabled
-6. Emits OG, Twitter, LinkedIn, Discord, Telegram meta tags
-7. Builds JSON-LD schema (Article, Product, or WebPage)
+5. Resolves the image URL, auto-resizes if enabled
+6. Emits OG, Twitter, LinkedIn, Discord, Telegram, Fediverse, Pinterest meta tags
+7. Builds JSON-LD schema(s) (Article/Product/WebPage + FAQ/HowTo/Event/Recipe/LocalBusiness/Video/custom)
8. Fires `onMokoOGAfterRender` for third-party extensions
-### Admin (content plugin)
-1. Content plugin adds OG fields tab to article/menu/category editor forms
-2. On save, stores custom OG data to `#__mokoog_tags` with language
-3. Live preview updates Facebook/Twitter card preview in real-time
+### Editor (content plugin)
+1. Content plugin adds the OG fields tab to article/menu/category editor forms
+2. On save, stores custom OG data to `#__mokoog_tags` keyed by language; JSON fields are validated as objects/arrays
+3. Live preview updates the Facebook/Twitter/LinkedIn/Discord/Mastodon/Slack cards in real time
+
+### Admin component
+1. **Dashboard** (default view) — `DashboardModel` computes coverage stats; `View/Dashboard` renders a donut + breakdown + missing-articles list
+2. **Tags** — list view with publish/unpublish/delete (`TagsController`) and a single-record create/edit screen (`TagController` → `View/Tag` → `tmpl/tag/edit.php`)
+3. **Batch / Import-Export** — `BatchController`, `ImportExportController` (CSRF + ACL gated)
### API
1. WebServices plugin registers routes on `onBeforeApiRoute`
-2. JSON:API controller provides full CRUD + content lookup endpoint
-3. Field whitelist prevents information leakage
+2. JSON:API controller provides full CRUD plus a content-lookup endpoint
+3. A field whitelist (`JsonapiView`) prevents information leakage
## Key Classes
| Class | Location | Purpose |
-|-------|----------|---------|
-| MokoOG | plg_system_mokoog | Main system plugin, meta tag injection |
+|---|---|---|
+| MokoOG | plg_system_mokoog | Meta tag injection, sitemap trigger, AI AJAX endpoint |
| MokoOGContent | plg_content_mokoog | Editor form injection, OG data save/load |
| MokoOGWebServices | plg_webservices_mokoog | API route registration |
-| TagsController | com_mokoog (admin) | Admin list publish/delete operations |
-| BatchController | com_mokoog | Batch OG generation for existing articles |
-| ImportExportController | com_mokoog | CSV import/export with language support |
+| DisplayController | com_mokoog | Default view dispatch (→ dashboard) |
+| DashboardModel | com_mokoog | Coverage metrics queries |
+| TagController / TagsController | com_mokoog | Single-record edit / list operations |
+| BatchController | com_mokoog | Batch OG generation |
+| ImportExportController | com_mokoog | CSV import/export (JSON/URL validated) |
| TagTable | com_mokoog | DB table with field validation |
-| ImageHelper | plg_system_mokoog | Image resize, center-crop, validation |
-| ImageGenerator | plg_system_mokoog | Text overlay image generation (GD) |
-| JsonLdBuilder | plg_system_mokoog | Article, Product, WebPage, Breadcrumb schemas |
+| ImageHelper | plg_system_mokoog | Image resize, center-crop, per-platform crops, prune |
+| JsonLdBuilder | plg_system_mokoog | All JSON-LD schema builders + `toScriptTag()` |
+| SitemapBuilder | plg_system_mokoog | Access-filtered XML sitemap, atomic write |
## Database
@@ -53,11 +62,13 @@ Single table: `#__mokoog_tags`
- Unique key: `(content_type, content_id, language)`
- Supports: `com_content`, `com_content.category`, `menu`, `com_mokoshop`
-- Language-aware queries prefer specific language over `*` wildcard
+- Columns include `og_*`, `seo_title`, `meta_description`, `robots`, `canonical_url`, `og_video`, `event_data`, `recipe_data`, `custom_schema`, `language`, `published`
+- Language-aware queries prefer a specific language over the `*` wildcard
-## Performance
+## Performance & Safety
-- `loadArticle()` and `loadShopProduct()` use static per-request caching
-- Article pages: 1 DB query instead of 5 (consolidated in v1.0)
-- Batch processing capped at 200 per request
-- Generated images cached by content hash in `images/mokoog/generated/`
+- `loadArticle()` / `loadShopProduct()` use static per-request caching (including negative lookups) — one query instead of several when the image finder, date extractor, and JSON-LD builder all need the same record
+- Batch processing is capped per request
+- Generated images are cached under `images/mokoog/generated/` and pruned after 30 days
+- The sitemap excludes non-public content and writes via a temp file + atomic rename
+- Custom JSON-LD is validated as an object/array on save **and** guarded on render, so malformed data cannot crash the public page
diff --git a/Configuration.md b/Configuration.md
index 33d1adb..03b8152 100644
--- a/Configuration.md
+++ b/Configuration.md
@@ -1,52 +1,101 @@
# Configuration
-Navigate to **Extensions > Plugins > System - MokoSuiteOpenGraph** to configure the plugin.
+Settings live in two places:
-## Basic Settings
+- **System plugin** — site-wide defaults and behaviour. Navigate to **Extensions → Plugins → System - MokoSuiteOpenGraph**.
+- **Component Options** — permissions only. Open **Components → MokoSuiteOpenGraph → Options** (the gear icon).
+
+---
+
+## System Plugin
+
+### Basic
| Parameter | Description |
-|-----------|-------------|
-| **Site Name** | The `og:site_name` value. Leave blank to use the Joomla site name. |
-| **Default OG Title** | Site-wide fallback title for social sharing. |
-| **Default OG Description** | Site-wide fallback description. |
-| **Default Image** | Fallback image. Recommended: 1200x630px. |
-| **Twitter Card Type** | Summary or Summary with Large Image. |
-| **Twitter @username** | Your site Twitter handle (e.g. @mokoconsulting). |
-| **Facebook App ID** | Your Facebook App ID for `fb:app_id` meta tag. |
-| **Telegram Channel** | Your Telegram channel handle for link previews. |
-| **Discord Embed Color** | The color of the embed sidebar when shared on Discord. |
-| **Fediverse Creator** | Your Fediverse/Mastodon handle (e.g. `@user@mastodon.social`). Outputs `fediverse:creator` meta tag. |
+|---|---|
+| **Site Name** (`og_site_name`) | The `og:site_name` value. Leave blank to use the Joomla site name. |
+| **Default OG Title** (`default_og_title`) | Site-wide fallback title for social sharing. |
+| **Default OG Description** (`default_og_description`) | Site-wide fallback description. |
+| **Default Image** (`default_image`) | Fallback image. Recommended: 1200×630px. |
+| **Twitter Card Type** (`twitter_card_type`) | `summary` or `summary_large_image`. |
+| **Twitter @username** (`twitter_site`) | Site Twitter/X handle (e.g. `@mokoconsulting`). |
+| **Facebook App ID** (`fb_app_id`) | Outputs the `fb:app_id` meta tag. |
+| **Telegram Channel** (`telegram_channel`) | Channel handle for link previews. |
+| **Discord Embed Color** (`discord_color`) | Sidebar color of the Discord embed (`theme-color`). |
+| **Fediverse Creator** (`fediverse_creator`) | Mastodon/Fediverse handle (e.g. `@user@mastodon.social`) → `fediverse:creator`. |
-## Advanced Settings
+### Advanced
| Parameter | Description |
-|-----------|-------------|
-| **Auto-generate Tags** | Automatically generate OG tags from article content. |
-| **Strip HTML** | Remove HTML tags from auto-generated descriptions. |
-| **Description Length** | Maximum character length for `og:description` (50-300, default 160). |
-| **Auto-resize Images** | Resize images to 1200x630px using center crop. |
-| **Enable JSON-LD** | Output structured data for Google rich results. |
-| **JSON-LD Breadcrumbs** | Output BreadcrumbList schema from Joomla pathway. |
+|---|---|
+| **Auto-generate Tags** (`auto_generate`) | Generate OG tags from article content when no custom value is set. |
+| **Strip HTML** (`strip_html`) | Remove HTML from auto-generated descriptions. |
+| **Description Length** (`desc_length`) | Max characters for auto `og:description` (50–300, default 160). |
+| **Auto-resize Images** (`auto_resize`) | Resize images to 1200×630 using center crop. |
+| **Per-platform Resize** (`platform_resize`) | Additionally produce platform-specific crops (Twitter 1200×600, Pinterest 1000×1500, WhatsApp 400×400). |
+| **Enable JSON-LD** (`jsonld_enabled`) | Output structured data for Google rich results. |
+| **FAQ JSON-LD** (`jsonld_faq`) | Auto-detect FAQ pairs from article headings → FAQPage schema. |
+| **HowTo JSON-LD** (`jsonld_howto`) | Auto-detect steps from ordered lists → HowTo schema. |
+| **JSON-LD Breadcrumbs** (`jsonld_breadcrumbs`) | Output BreadcrumbList from the Joomla pathway. |
+
+### LocalBusiness (`localbusiness`)
+
+Enable (`lb_enabled`) to output a site-wide LocalBusiness schema. Fields: `lb_name`, `lb_type` (LocalBusiness/Restaurant/Store/…), `lb_street`, `lb_city`, `lb_region`, `lb_postal`, `lb_country`, `lb_phone`, `lb_email`, `lb_url`, `lb_opening_hours`, `lb_latitude`, `lb_longitude`, `lb_price_range`. Address, geo, and contact blocks are only emitted when their fields are filled.
+
+### Sitemap (`sitemap`)
+
+| Parameter | Description |
+|---|---|
+| **Enable Sitemap** (`sitemap_enabled`) | Regenerate `sitemap.xml` on article save. |
+| **Change Frequency** (`sitemap_changefreq`) | `daily`, `weekly`, or `monthly`. |
+
+The sitemap includes only published articles at **public** view levels (registered/special-access content is excluded), skips `noindex` items, and is written atomically.
+
+### AI Meta Generation (`ai`)
+
+| Parameter | Description |
+|---|---|
+| **Enable AI** (`ai_enabled`) | Show the "Generate with AI" button in the editor. |
+| **Provider** (`ai_provider`) | `claude` or `openai`. |
+| **API Key** (`ai_api_key`) | Provider API key. |
+| **Model** (`ai_model`) | e.g. `claude-haiku-4-5-20251001`. |
+
+> AI generation requires **article-edit** permission and uses an outbound HTTP call (20s timeout). Keep your API key private — anyone who can edit articles can spend credits.
+
+---
+
+## Component Options (Permissions)
+
+The component ships an `access.xml` defining standard core actions plus two custom actions:
+
+- **`mokoog.batch`** — run batch OG tag generation
+- **`mokoog.import`** — import/export OG tags via CSV
+
+Configure them per user group under **Components → MokoSuiteOpenGraph → Options → Permissions**. Controllers fall back to the matching core action (`core.create` / `core.create`+`core.edit`) so existing setups keep working until you tighten the new actions.
+
+---
## Fallback Chain
OG tag values are resolved in this order:
-1. **Custom OG data** -- per-article/menu/category overrides set in the editor
-2. **Page metadata** -- Joomla page title, meta description, article images
-3. **Site-wide defaults** -- Default OG Title, Default OG Description, Default Image
+1. **Custom OG data** — per-article/menu/category overrides set in the editor
+2. **Page metadata** — Joomla page title, meta description, article images
+3. **Site-wide defaults** — Default OG Title, Default OG Description, Default Image
## Per-Content OG Fields
-When editing an article, menu item, or category, you will see a **MokoSuiteOpenGraph** tab with:
+When editing an article, menu item, or category, the **MokoSuiteOpenGraph** tab provides:
-- **OG Title** -- Custom title (60 optimal / 90 max chars)
-- **OG Description** -- Custom description (155 optimal / 200 max chars)
-- **OG Image** -- Custom image (Joomla media picker)
-- **OG Type** -- article, website, product, profile, book, music, video
-- **SEO Title** -- Custom `
` (60 optimal / 70 max chars)
-- **Meta Description** -- Custom meta description (155 optimal / 160 max chars)
-- **Robots** -- noindex, nofollow, noarchive, nosnippet, noimageindex
-- **Canonical URL** -- Custom canonical URL override
+- **OG Title** — custom title (60 optimal / 90 max chars)
+- **OG Description** — custom description (155 optimal / 200 max chars)
+- **OG Image** — custom image (Joomla media picker)
+- **OG Type** — article, website, product, profile, book, music, video
+- **og:video** — per-article video URL (YouTube/Vimeo/direct)
+- **SEO Title** — custom `` (60 optimal / 70 max chars)
+- **Meta Description** — custom meta description (155 optimal / 200 max chars)
+- **Robots** — noindex, nofollow, noarchive, nosnippet, noimageindex
+- **Canonical URL** — custom canonical (http/https only)
+- **Custom JSON-LD / Event / Recipe** — per-article structured data (see [JSON-LD Schemas](JSON-LD-Schemas))
-All text fields include live character count indicators with green/yellow/red color coding.
+All text fields include live character-count indicators with green/yellow/red color coding.
diff --git a/Developer-Guide.-.md b/Developer-Guide.-.md
deleted file mode 100644
index 02e4f4c..0000000
--- a/Developer-Guide.-.md
+++ /dev/null
@@ -1,120 +0,0 @@
-# Developer Guide
-
-## REST API
-
-The Web Services plugin exposes OG tags via Joomla JSON:API.
-
-### Endpoints
-
-| Method | URL | Description |
-|--------|-----|-------------|
-| GET | /api/v1/mokoog/tags | List all OG tags |
-| GET | /api/v1/mokoog/tags/:id | Get single OG tag |
-| POST | /api/v1/mokoog/tags | Create OG tag |
-| PATCH | /api/v1/mokoog/tags/:id | Update OG tag |
-| DELETE | /api/v1/mokoog/tags/:id | Delete OG tag |
-| GET | /api/v1/mokoog/lookup/:content_type/:content_id | Lookup by content |
-
-### Authentication
-
-All endpoints require Joomla API authentication (token or session).
-
-### Fields
-
-| Field | Type | Description |
-|-------|------|-------------|
-| id | int | Primary key |
-| content_type | string | e.g. `com_content`, `menu`, `com_mokoshop` |
-| content_id | int | Content item ID |
-| og_title | string | OG title (max 255) |
-| og_description | string | OG description |
-| og_image | string | Image path |
-| og_type | string | article, website, product, profile, book |
-| seo_title | string | Page title override (max 70) |
-| meta_description | string | Meta description (max 200) |
-| robots | string | Robots directives |
-| canonical_url | string | Canonical URL |
-| language | string | Language tag (e.g. en-GB) or * |
-| published | int | 0 or 1 |
-
-## Extending with Custom Plugins
-
-### onMokoOGAfterRender Event
-
-Third-party plugins can subscribe to the `onMokoOGAfterRender` event to add custom social meta tags for any platform.
-
-```php
-use Joomla\Event\Event;
-use Joomla\Event\SubscriberInterface;
-
-final class MyCustomSocialPlugin extends CMSPlugin implements SubscriberInterface
-{
- public static function getSubscribedEvents(): array
- {
- return ['onMokoOGAfterRender' => 'onMokoOGAfterRender'];
- }
-
- public function onMokoOGAfterRender(Event $event): void
- {
- $doc = $event->getArgument('subject');
- $title = $event->getArgument('title');
- $image = $event->getArgument('image');
-
- // Add Pinterest meta tag
- $doc->setMetaData('pinterest:media', $image);
- }
-}
-```
-
-### Event Arguments
-
-| Key | Type | Description |
-|-----|------|-------------|
-| subject | HtmlDocument | The Joomla document object |
-| title | string | Resolved OG title |
-| description | string | Resolved OG description |
-| image | string | Resolved image URL (absolute) |
-| url | string | Current page URL |
-| type | string | OG type (article, website, product, etc.) |
-| option | string | Component option (e.g. com_content, com_mokoshop) |
-| view | string | View name (e.g. article, product) |
-| id | int | Content item ID |
-
-## MokoSuiteShop Integration
-
-Product pages (`com_mokoshop`, view `product`) automatically receive:
-
-- `og:type` set to `product`
-- `product:price:amount` and `product:price:currency` meta tags
-- JSON-LD `Product` schema with `offers`, `sku`, and `aggregateRating`
-- Image auto-extracted from the linked `#__content` article
-
-Product data is loaded from `#__mokosuite_crm_products` joined to `#__content` via `article_id`.
-
-## Database Schema
-
-Table: `#__mokoog_tags`
-
-| Column | Type | Description |
-|--------|------|-------------|
-| id | INT UNSIGNED | Primary key |
-| content_type | VARCHAR(100) | e.g. com_content, menu, com_mokoshop |
-| content_id | INT UNSIGNED | ID of the content item |
-| og_title | VARCHAR(255) | Custom OG title |
-| og_description | TEXT | Custom OG description |
-| og_image | VARCHAR(512) | Image path relative to JPATH_ROOT |
-| og_type | VARCHAR(50) | article, website, product, etc. |
-| seo_title | VARCHAR(70) | Custom page title override |
-| meta_description | VARCHAR(200) | Custom meta description |
-| robots | VARCHAR(100) | Robots directive |
-| canonical_url | VARCHAR(512) | Canonical URL override |
-| language | CHAR(7) | Language tag (e.g. en-GB) or * |
-| published | TINYINT(1) | 0 or 1 |
-| created | DATETIME | Record creation time |
-| modified | DATETIME | Last modification time |
-
-**Unique key:** `(content_type, content_id, language)`
-
-## Performance
-
-Article page OG generation uses a single cached DB query via `loadArticle()` with static per-request caching. Product data is similarly cached via `loadShopProduct()`. This avoids duplicate queries when multiple consumers (image finder, date extractor, JSON-LD builder) need the same article data.
diff --git a/Developer-Guide.md b/Developer-Guide.md
new file mode 100644
index 0000000..ad69d3f
--- /dev/null
+++ b/Developer-Guide.md
@@ -0,0 +1,109 @@
+# Developer Guide
+
+## REST API
+
+The Web Services plugin exposes OG tags via Joomla JSON:API. Full machine-readable spec: [`openapi.yaml`](openapi.yaml).
+
+### Endpoints
+
+| Method | URL | Description |
+|---|---|---|
+| GET | `/api/index.php/v1/mokoog/tags` | List all OG tags |
+| GET | `/api/index.php/v1/mokoog/tags/:id` | Get a single OG tag |
+| POST | `/api/index.php/v1/mokoog/tags` | Create an OG tag |
+| PATCH | `/api/index.php/v1/mokoog/tags/:id` | Update an OG tag |
+| DELETE | `/api/index.php/v1/mokoog/tags/:id` | Delete an OG tag |
+| GET | `/api/index.php/v1/mokoog/lookup/:content_type/:content_id` | Look up a tag by content |
+
+### Authentication
+
+All endpoints require Joomla API authentication (token or session) — the routes are registered as non-public.
+
+### Fields
+
+| Field | Type | Description |
+|---|---|---|
+| id | int | Primary key |
+| content_type | string | e.g. `com_content`, `menu`, `com_mokoshop` |
+| content_id | int | Content item ID |
+| og_title | string | OG title |
+| og_description | string | OG description |
+| og_image | string | Image path |
+| og_type | string | article, website, product, profile, book… |
+| og_video | string | Video URL (YouTube/Vimeo/direct) |
+| seo_title | string | Page `` override (max 70) |
+| meta_description | string | Meta description (max 200) |
+| robots | string | Robots directives |
+| canonical_url | string | Canonical URL (http/https) |
+| event_data | json | Event schema fields (object/array) |
+| recipe_data | json | Recipe schema fields (object/array) |
+| custom_schema | json | Arbitrary schema.org JSON-LD (object/array) |
+| language | string | Language tag (e.g. `en-GB`) or `*` |
+| published | int | 0 or 1 |
+
+> JSON fields (`event_data`, `recipe_data`, `custom_schema`) must be JSON **objects/arrays** — scalars are rejected on save and via CSV import, and guarded on render.
+
+## Extending with Custom Plugins
+
+### `onMokoOGAfterRender` Event
+
+Third-party plugins can subscribe to add custom social meta tags for any platform.
+
+```php
+use Joomla\Event\Event;
+use Joomla\Event\SubscriberInterface;
+
+final class MyCustomSocialPlugin extends CMSPlugin implements SubscriberInterface
+{
+ public static function getSubscribedEvents(): array
+ {
+ return ['onMokoOGAfterRender' => 'onMokoOGAfterRender'];
+ }
+
+ public function onMokoOGAfterRender(Event $event): void
+ {
+ $doc = $event->getArgument('subject');
+ $image = $event->getArgument('image');
+
+ // Add a Pinterest meta tag
+ $doc->setMetaData('pinterest:media', $image);
+ }
+}
+```
+
+### Event Arguments
+
+| Key | Type | Description |
+|---|---|---|
+| subject | HtmlDocument | The Joomla document object |
+| title | string | Resolved OG title |
+| description | string | Resolved OG description |
+| image | string | Resolved image URL (absolute) |
+| url | string | Current page URL |
+| type | string | OG type (article, website, product…) |
+| option | string | Component option (e.g. com_content, com_mokoshop) |
+| view | string | View name (e.g. article, product) |
+| id | int | Content item ID |
+
+## MokoSuiteShop Integration
+
+Product pages (`com_mokoshop`, view `product`) automatically receive:
+
+- `og:type` set to `product`
+- `product:price:amount` and `product:price:currency` meta tags
+- JSON-LD `Product` schema with `offers`, `sku`, and `aggregateRating`
+- Image auto-extracted from the linked `#__content` article
+
+Product data is loaded from `#__mokosuite_crm_products` joined to `#__content` via `article_id`, with per-request caching.
+
+## Database Schema
+
+Table: `#__mokoog_tags` — unique key `(content_type, content_id, language)`. See [Architecture](Architecture) for the full column list. Fresh installs build the complete schema from `sql/install.mysql.sql`; upgrades apply incremental `sql/updates/mysql/*.sql` scripts.
+
+## Forward Compatibility
+
+The codebase is clean for Joomla 7: database access goes through `Factory::getContainer()->get(DatabaseInterface::class)`, language/app via `getApplication()`, filesystem via `Joomla\Filesystem\*`, and controllers throw exceptions instead of `jexit()`. The CI "deprecated Joomla API" check passes with zero findings.
+
+## Testing
+
+PHPUnit unit tests live under `tests/Unit/` (pure helpers — `JsonLdBuilder` schema builders and `toScriptTag()` escaping). Run via `vendor/bin/phpunit`. CI runs the suite on every PR.
diff --git a/Home.md b/Home.md
index 19f9246..35395c6 100644
--- a/Home.md
+++ b/Home.md
@@ -26,7 +26,7 @@ Open Graph, Twitter Card, and social sharing meta tag management for Joomla 6 an
- **og:video** — YouTube, Vimeo, direct files with auto MIME detection
### JSON-LD Structured Data (11+ Types)
-Article, Product, WebPage, BreadcrumbList, Organization, VideoObject, FAQPage, HowTo, Event, Recipe, LocalBusiness + custom schema builder for any schema.org type
+Article, Product, WebPage, BreadcrumbList, Organization, VideoObject, FAQPage, HowTo, Event, Recipe, LocalBusiness + custom schema builder for any schema.org type. See [JSON-LD Schemas](JSON-LD-Schemas).
### Editor UX
- **6 preview cards** — Facebook, Twitter/X, LinkedIn, Discord, Mastodon, Slack
@@ -42,6 +42,8 @@ Article, Product, WebPage, BreadcrumbList, Organization, VideoObject, FAQPage, H
- **Component permissions** — ACL actions (`mokoog.batch`, `mokoog.import`) in the component Options → Permissions
- **XML sitemap** generation — respects noindex and public access levels
+See the [Admin Guide](Admin-Guide).
+
### Developer Features
- REST API — full CRUD via Joomla Web Services ([OpenAPI spec](openapi.yaml)); includes `og_video`, `event_data`, `recipe_data`, `custom_schema`
- Per-platform image resizing (Twitter, Pinterest, WhatsApp); generated images auto-pruned after 30 days
@@ -50,18 +52,23 @@ Article, Product, WebPage, BreadcrumbList, Organization, VideoObject, FAQPage, H
- PHPUnit unit tests for JsonLdBuilder schema outputs and script-tag escaping
- MokoSuiteShop integration with product JSON-LD
+See the [Developer Guide](Developer-Guide).
+
---
-## Wiki Structure
+## Documentation
### Guides
-- [Installation](guides/Installation)
-- [Configuration](guides/Configuration)
+- [Installation](Installation)
+- [Configuration](Configuration)
+- [Admin Guide](Admin-Guide)
### Reference
-- [Developer Guide](reference/Developer-Guide)
-- [Platform Support](reference/Platform-Support)
-- [Architecture](reference/Architecture)
+- [Developer Guide](Developer-Guide) — REST API, events, schema, MokoSuiteShop
+- [JSON-LD Schemas](JSON-LD-Schemas)
+- [Platform Support](Platform-Support)
+- [Architecture](Architecture)
+- [Troubleshooting](Troubleshooting)
---
@@ -71,7 +78,7 @@ Article, Product, WebPage, BreadcrumbList, Organization, VideoObject, FAQPage, H
| Revision | Date | Author | Description |
|---|---|---|---|
-| 6.0 | 2026-06-29 | Moko Consulting | Joomla 6+/PHP 8.2+, rename to MokoSuiteOpenGraph, dashboard view, manual editor, ACL, security & forward-compat fixes |
+| 6.0 | 2026-06-29 | Moko Consulting | Joomla 6+/PHP 8.2+, rename to MokoSuiteOpenGraph, dashboard view, manual editor, ACL, security & forward-compat fixes; full wiki rebuild |
| 5.0 | 2026-06-23 | Moko Consulting | v2.0: 11+ JSON-LD types, 6 preview cards, AI, sitemap, tests |
| 4.0 | 2026-06-23 | Moko Consulting | v1.3: Fediverse, LinkedIn preview, character counters |
| 3.0 | 2026-06-21 | Moko Consulting | Full assessment: MokoSuiteShop, JSON-LD Product, multilingual |
diff --git a/JSON-LD-Schemas.md b/JSON-LD-Schemas.md
new file mode 100644
index 0000000..e2da851
--- /dev/null
+++ b/JSON-LD-Schemas.md
@@ -0,0 +1,56 @@
+# JSON-LD Schemas
+
+MokoSuiteOpenGraph emits [schema.org](https://schema.org) structured data as JSON-LD `` breakout (XSS-safe). Values pulled from content are never concatenated as raw strings.
+
+## Automatic schemas
+
+| Schema | When it's emitted | Source |
+|---|---|---|
+| **Article** | `com_content` article pages | Title, intro/full text, image, author, publish/modified dates |
+| **Product** | `com_mokoshop` product pages | Name, description, image, SKU, offers (price/currency/availability), aggregateRating |
+| **WebPage** | All pages | Page name, description, URL |
+| **BreadcrumbList** | All pages (optional) | Joomla pathway — toggle with **JSON-LD Breadcrumbs** |
+| **Organization** | Site-wide | Site name and URL |
+| **LocalBusiness** | Site-wide (optional) | The LocalBusiness fields in plugin config |
+
+## Auto-detected schemas
+
+| Schema | Trigger | Setting |
+|---|---|---|
+| **FAQPage** | Question/answer pairs detected from article `h3`/`h4` headings | **FAQ JSON-LD** |
+| **HowTo** | Steps detected from ordered (``) lists | **HowTo JSON-LD** |
+
+## Per-article schemas
+
+Set in the article's **MokoSuiteOpenGraph** tab and stored in `#__mokoog_tags`:
+
+| Schema | Field | Notes |
+|---|---|---|
+| **Event** | `event_data` | Dates, venue, ticket/offer info |
+| **Recipe** | `recipe_data` | Prep/cook times, ingredients, nutrition |
+| **VideoObject** | `og_video` | Built from the per-article video URL |
+| **Custom** | `custom_schema` | Any schema.org type — paste a JSON object |
+
+> The Event/Recipe/Custom fields must contain a JSON **object** (e.g. `{ "startDate": "…" }`). Scalars (`42`, `"x"`, `true`) are rejected on save, on CSV import, and ignored on render — they previously caused a fatal page error and are now guarded.
+
+## Custom schema example
+
+Paste into the **Custom JSON-LD** field on an article:
+
+```json
+{
+ "@type": "SoftwareApplication",
+ "name": "MokoSuiteOpenGraph",
+ "applicationCategory": "Joomla Extension",
+ "operatingSystem": "Joomla 6+",
+ "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" }
+}
+```
+
+`@context` is added automatically if you omit it. The result is emitted as a standalone JSON-LD block on that page.
+
+## Validating output
+
+Use Google's [Rich Results Test](https://search.google.com/test/rich-results) or the Schema Markup Validator. The tag manager's per-row **G** debug link opens the Rich Results test pre-filled for that content item.
diff --git a/Platform-Support.-.md b/Platform-Support.md
similarity index 71%
rename from Platform-Support.-.md
rename to Platform-Support.md
index 2041f17..faa6bdb 100644
--- a/Platform-Support.-.md
+++ b/Platform-Support.md
@@ -1,12 +1,12 @@
# Platform Support
-MokoSuiteOpenGraph outputs platform-specific meta tags optimized for each social network.
+MokoSuiteOpenGraph outputs platform-specific meta tags optimized for each social network. See [JSON-LD Schemas](JSON-LD-Schemas) for structured-data details.
## Meta Tags by Platform
### Facebook / Meta
- `og:title`, `og:description`, `og:image`, `og:url`, `og:type`, `og:site_name`, `og:locale`
-- `og:image:width`, `og:image:height` (dynamically detected from actual image)
+- `og:image:width`, `og:image:height` (dynamically detected from the actual image)
- `og:video`, `og:video:secure_url`, `og:video:type`, `og:video:width`, `og:video:height` (per-article video URL)
- `fb:app_id` (optional)
@@ -21,7 +21,7 @@ MokoSuiteOpenGraph outputs platform-specific meta tags optimized for each social
### Pinterest
- `article:tag` for article rich pins (auto-extracted from Joomla content tags)
-- `product:price:amount`, `product:price:currency`, `product:availability` for product rich(pins (MokoSuiteShop)
+- `product:price:amount`, `product:price:currency`, `product:availability` for product rich pins (MokoSuiteShop)
### Discord
- Reads standard `og:*` tags
@@ -38,7 +38,7 @@ MokoSuiteOpenGraph outputs platform-specific meta tags optimized for each social
### WhatsApp
- Reads standard `og:*` tags
-- Images auto-resized to meet minimum 300x200px requirement
+- Images auto-resized to meet the minimum 300×200px requirement
### Slack / Reddit
- Read standard `og:*` tags natively — no special tags needed
@@ -46,24 +46,9 @@ MokoSuiteOpenGraph outputs platform-specific meta tags optimized for each social
### E-commerce / Product Pages
- `og:type` set to `product` on MokoSuiteShop product pages
- `product:price:amount` and `product:price:currency` for rich product previews
-- ~product:availability` derived from stock quantity (instock/outofstock)
+- `product:availability` derived from stock quantity (instock/outofstock)
- Automatic on any `com_mokoshop` product view
-## JSON-LD Structured Data
-
-### Article pages (`com_content`)
-- `Article` schema with headline, description, image, author, datePublished, dateModified
-
-### Product pages (`com_mokoshop`)
-- `Product` schema with name, description, image, sku
-- `Offer` with price, priceCurrency, availability (InStock/OutOfStock)
-- `AggregateRating` from approved reviews (if any)
-
-### All pages
-- `WebPage` schema with name, description, URL
-- `BreadcrumbList` schema from Joomla pathway (optional)
-- `Organization` schema from site configuration
-
## Editor Preview Cards
When editing articles or menu items, live preview cards show how your content will appear on:
@@ -71,5 +56,14 @@ When editing articles or menu items, live preview cards show how your content wi
- **Facebook** — standard link preview card with image, title, description
- **Twitter / X** — large image card with rounded corners
- **LinkedIn** — professional card with image and title
+- **Discord** — embed with `theme-color` accent
+- **Mastodon** — Fediverse-style card
+- **Slack** — unfurl-style preview
-All previews update in real-time as you type. Character count indicators show green/yellow/red warnings when text exceeds platform limits.
+All previews update in real time as you type. Character-count indicators show green/yellow/red warnings when text exceeds platform limits.
+
+## Image Sizing
+
+- Default OG image: 1200×630 (center crop)
+- Per-platform crops (when "Per-platform Resize" is enabled): Twitter 1200×600, Pinterest 1000×1500, WhatsApp 400×400
+- Generated images are cached and pruned after 30 days
diff --git a/Troubleshooting.md b/Troubleshooting.md
new file mode 100644
index 0000000..1eff695
--- /dev/null
+++ b/Troubleshooting.md
@@ -0,0 +1,47 @@
+# Troubleshooting
+
+## Meta tags don't appear on the frontend
+
+- Confirm the **System - MokoSuiteOpenGraph** plugin is **enabled** (Extensions → Plugins).
+- Meta tags are only injected on the **site** (frontend), on **HTML** documents — not in the admin or on JSON/feed views.
+- View the page source and search for `og:title`. If your template or another SEO plugin also sets OG tags, the last one to run wins — disable the duplicate.
+- Tags are emitted on `onBeforeCompileHead`; a template that builds its own `` without calling the document head may bypass them.
+
+## A page shows a 500 error after editing structured data
+
+Older builds could crash if a **scalar** (e.g. `42`) was saved into the Custom JSON-LD / Event / Recipe field. This is fixed: such values are now rejected on save and ignored on render. If you hit it on an older version, edit the record (or re-import a corrected CSV) so the JSON field contains an **object** `{ … }` or is empty.
+
+## OG image is missing or 404s
+
+- Set a **Default Image** (1200×630) in the plugin config as a fallback.
+- Ensure the GD PHP extension is installed if you use auto-resize.
+- Auto-resized files are written to `images/mokoog/generated/` — that directory must be writable. Failed writes are logged (Joomla logs, category `mokoog`).
+
+## Sitemap is empty or missing entries
+
+- Enable **Sitemap** in the plugin config; `sitemap.xml` is regenerated when an article is saved.
+- Only **published** articles at **public** view levels are included — registered/special-access and `noindex` items are intentionally excluded.
+- `JPATH_ROOT/sitemap.xml` must be writable; failures are logged.
+
+## AI generation returns nothing / an error
+
+- AI must be enabled and an **API key** and **model** configured for the chosen provider.
+- The user must have **article-edit** permission — otherwise the request is rejected.
+- Non-200 responses from the provider now surface an error (HTTP status) instead of a blank result; check the message and your key/quota.
+- A hung provider times out after 20 seconds.
+
+## "Forbidden" when running Batch or Import
+
+Grant the relevant action under **Options → Permissions**: `mokoog.batch` for batch generation, `mokoog.import` for CSV import/export. Users with core `create` (batch) or core `create`+`edit` (import) also pass.
+
+## The Options screen is empty
+
+You're on an older build without `config.xml`. Update to the current version — the component now ships a `config.xml` with a Permissions tab. OG/SEO settings themselves live in the **System** plugin, not the component Options.
+
+## Multilingual: wrong language tags
+
+OG data is stored per language with the unique key `(content_type, content_id, language)`. Language-specific rows take priority over the `*` (All) wildcard. If an article's language is changed after saving, re-open and re-save its OG tab so the row is keyed to the new language.
+
+## Reporting issues
+
+Open an issue at the [repository](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteOpenGraph/issues) with your Joomla and PHP versions and steps to reproduce.
diff --git a/_Sidebar.md b/_Sidebar.md
new file mode 100644
index 0000000..cd826b9
--- /dev/null
+++ b/_Sidebar.md
@@ -0,0 +1,14 @@
+### MokoSuiteOpenGraph
+
+**Guides**
+- [Home](Home)
+- [Installation](Installation)
+- [Configuration](Configuration)
+- [Admin Guide](Admin-Guide)
+
+**Reference**
+- [Developer Guide](Developer-Guide)
+- [JSON-LD Schemas](JSON-LD-Schemas)
+- [Platform Support](Platform-Support)
+- [Architecture](Architecture)
+- [Troubleshooting](Troubleshooting)