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 `<title>` (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 `<title>` 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 `<script type="application/ld+json">` blocks for Google rich results and other consumers. Enable it with **Enable JSON-LD** in the plugin's Advanced settings. + +All output is encoded with `json_encode` and `</` is escaped to `<\/` to prevent `</script>` 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 (`<ol>`) 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 `<head>` 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)