diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4676243..3b6708a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Fediverse/Mastodon `fediverse:creator` meta tag — first extension on any CMS to support this (#57)
- Live character count indicators on OG title, OG description, SEO title, meta description fields with color-coded warnings (#58)
- LinkedIn social preview card in article/menu editor alongside Facebook and Twitter/X previews (#61)
+- `og:video` meta tag support with per-article video URL field, auto-detect MIME type for YouTube/Vimeo/direct files (#59)
+- Pinterest rich pin tags: `article:tag` from Joomla content tags, `product:availability` from MokoSuiteShop stock (#60)
- Site-wide default OG title and description plugin parameters
- Discord embed color via `theme-color` meta tag (color picker in plugin config)
- LinkedIn article tags: `article:published_time`, `article:modified_time`, `article:author`
diff --git a/source/packages/com_mokoog/forms/tag.xml b/source/packages/com_mokoog/forms/tag.xml
index 87d8068..7f87824 100644
--- a/source/packages/com_mokoog/forms/tag.xml
+++ b/source/packages/com_mokoog/forms/tag.xml
@@ -60,6 +60,14 @@
Product
Profile
+
Music
Video
+
diff --git a/source/packages/plg_content_mokoog/language/en-GB/plg_content_mokoog.ini b/source/packages/plg_content_mokoog/language/en-GB/plg_content_mokoog.ini
index 1da4c1e..e5859fa 100644
--- a/source/packages/plg_content_mokoog/language/en-GB/plg_content_mokoog.ini
+++ b/source/packages/plg_content_mokoog/language/en-GB/plg_content_mokoog.ini
@@ -14,6 +14,9 @@ PLG_CONTENT_MOKOOG_FIELD_OG_IMAGE_DESC="Custom image for social sharing. Recomme
PLG_CONTENT_MOKOOG_FIELD_OG_TYPE="OG Type"
PLG_CONTENT_MOKOOG_FIELD_OG_TYPE_DESC="The Open Graph content type for this page."
+PLG_CONTENT_MOKOOG_FIELD_OG_VIDEO="Video URL"
+PLG_CONTENT_MOKOOG_FIELD_OG_VIDEO_DESC="URL of a video to embed in social sharing previews. Supports direct video URLs and YouTube/Vimeo links. Outputs og:video meta tags."
+
PLG_CONTENT_MOKOOG_FIELDSET_SEO_LABEL="SEO Meta Tags"
PLG_CONTENT_MOKOOG_FIELDSET_SEO_DESC="Control search engine meta tags for this page."
diff --git a/source/packages/plg_content_mokoog/language/en-US/plg_content_mokoog.ini b/source/packages/plg_content_mokoog/language/en-US/plg_content_mokoog.ini
index 1da4c1e..e5859fa 100644
--- a/source/packages/plg_content_mokoog/language/en-US/plg_content_mokoog.ini
+++ b/source/packages/plg_content_mokoog/language/en-US/plg_content_mokoog.ini
@@ -14,6 +14,9 @@ PLG_CONTENT_MOKOOG_FIELD_OG_IMAGE_DESC="Custom image for social sharing. Recomme
PLG_CONTENT_MOKOOG_FIELD_OG_TYPE="OG Type"
PLG_CONTENT_MOKOOG_FIELD_OG_TYPE_DESC="The Open Graph content type for this page."
+PLG_CONTENT_MOKOOG_FIELD_OG_VIDEO="Video URL"
+PLG_CONTENT_MOKOOG_FIELD_OG_VIDEO_DESC="URL of a video to embed in social sharing previews. Supports direct video URLs and YouTube/Vimeo links. Outputs og:video meta tags."
+
PLG_CONTENT_MOKOOG_FIELDSET_SEO_LABEL="SEO Meta Tags"
PLG_CONTENT_MOKOOG_FIELDSET_SEO_DESC="Control search engine meta tags for this page."
diff --git a/source/packages/plg_content_mokoog/src/Extension/MokoOGContent.php b/source/packages/plg_content_mokoog/src/Extension/MokoOGContent.php
index 7bd1b9b..e7362d9 100644
--- a/source/packages/plg_content_mokoog/src/Extension/MokoOGContent.php
+++ b/source/packages/plg_content_mokoog/src/Extension/MokoOGContent.php
@@ -194,7 +194,7 @@ final class MokoOGContent extends CMSPlugin implements SubscriberInterface
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName([
- 'og_title', 'og_description', 'og_image', 'og_type',
+ 'og_title', 'og_description', 'og_image', 'og_type', 'og_video',
'seo_title', 'meta_description', 'robots', 'canonical_url',
]))
->from($db->quoteName('#__mokoog_tags'))
@@ -249,6 +249,7 @@ final class MokoOGContent extends CMSPlugin implements SubscriberInterface
'og_description' => trim($ogData['og_description'] ?? ''),
'og_image' => trim($ogData['og_image'] ?? ''),
'og_type' => trim($ogData['og_type'] ?? 'article'),
+ 'og_video' => trim($ogData['og_video'] ?? ''),
'seo_title' => trim($ogData['seo_title'] ?? ''),
'meta_description' => trim($ogData['meta_description'] ?? ''),
'robots' => trim($robots),
diff --git a/source/packages/plg_system_mokoog/src/Extension/MokoOG.php b/source/packages/plg_system_mokoog/src/Extension/MokoOG.php
index b700fdc..760ee77 100644
--- a/source/packages/plg_system_mokoog/src/Extension/MokoOG.php
+++ b/source/packages/plg_system_mokoog/src/Extension/MokoOG.php
@@ -92,7 +92,7 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
if ($catOg) {
// Merge: category fills any gaps in the content-level data
- foreach (['og_title', 'og_description', 'og_image', 'og_type', 'seo_title', 'meta_description', 'robots', 'canonical_url'] as $field) {
+ foreach (['og_title', 'og_description', 'og_image', 'og_type', 'og_video', 'seo_title', 'meta_description', 'robots', 'canonical_url'] as $field) {
if (empty($ogData->$field) && !empty($catOg->$field)) {
$ogData->$field = $catOg->$field;
}
@@ -184,6 +184,27 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
$doc->setMetaData('fediverse:creator', $fediverseCreator);
}
+ // og:video tags
+ $videoUrl = $ogData->og_video ?? '';
+
+ if ($videoUrl) {
+ $doc->setMetaData('og:video', $videoUrl, 'property');
+ $doc->setMetaData('og:video:secure_url', $videoUrl, 'property');
+
+ // Detect video type from URL
+ if (str_contains($videoUrl, 'youtube.com') || str_contains($videoUrl, 'youtu.be')
+ || str_contains($videoUrl, 'vimeo.com')) {
+ $doc->setMetaData('og:video:type', 'text/html', 'property');
+ } else {
+ $ext = strtolower(pathinfo(parse_url($videoUrl, PHP_URL_PATH) ?: '', PATHINFO_EXTENSION));
+ $mimeMap = ['mp4' => 'video/mp4', 'webm' => 'video/webm', 'ogg' => 'video/ogg'];
+ $doc->setMetaData('og:video:type', $mimeMap[$ext] ?? 'video/mp4', 'property');
+ }
+
+ $doc->setMetaData('og:video:width', '1280', 'property');
+ $doc->setMetaData('og:video:height', '720', 'property');
+ }
+
// LinkedIn article tags
if ($option === 'com_content' && $view === 'article' && $id > 0) {
$doc->setMetaData('article:published_time', $this->getArticleDate($id, 'publish_up'), 'property');
@@ -206,6 +227,39 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
}
}
+ // Pinterest rich pin tags
+ if ($option === 'com_content' && $view === 'article' && $id > 0) {
+ $article = $this->loadArticle($id);
+
+ if ($article) {
+ // Extract Joomla content tags for article:tag (used by Pinterest article pins)
+ $db = Factory::getDbo();
+ $tagQuery = $db->getQuery(true)
+ ->select($db->quoteName('t.title'))
+ ->from($db->quoteName('#__tags', 't'))
+ ->join('INNER', $db->quoteName('#__contentitem_tag_map', 'm')
+ . ' ON ' . $db->quoteName('m.tag_id') . ' = ' . $db->quoteName('t.id'))
+ ->where($db->quoteName('m.type_alias') . ' = ' . $db->quote('com_content.article'))
+ ->where($db->quoteName('m.content_item_id') . ' = ' . $id)
+ ->where($db->quoteName('t.published') . ' = 1');
+ $db->setQuery($tagQuery);
+ $tags = $db->loadColumn();
+
+ foreach ($tags as $tag) {
+ $doc->setMetaData('article:tag', $tag, 'property');
+ }
+ }
+ }
+
+ if ($option === 'com_mokoshop' && $view === 'product' && $id > 0) {
+ $productData = $this->loadShopProduct($id);
+
+ if ($productData) {
+ $availability = ((int) ($productData->stock_qty ?? 0) > 0) ? 'instock' : 'outofstock';
+ $doc->setMetaData('product:availability', $availability, 'property');
+ }
+ }
+
// Fire event so third-party plugins can add custom OG/social tags
$eventData = [
'subject' => $doc,
@@ -306,6 +360,7 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
'og_description' => '',
'og_image' => '',
'og_type' => '',
+ 'og_video' => '',
'seo_title' => '',
'meta_description' => '',
'robots' => '',