feat(categories): category-level OG tags (#4) #18

Merged
jmiller merged 1 commits from feature/4-category-og-tags into dev 2026-05-23 23:39:49 +00:00
2 changed files with 71 additions and 6 deletions
@@ -56,10 +56,11 @@ final class MokoOGContent extends CMSPlugin implements SubscriberInterface
$formName = $form->getName();
// Add OG fields to article and menu item edit forms
// Add OG fields to article, menu item, and category edit forms
$supportedForms = [
'com_content.article',
'com_menus.item',
'com_categories.categorycom_content',
];
if (!\in_array($formName, $supportedForms, true)) {
@@ -81,7 +82,12 @@ final class MokoOGContent extends CMSPlugin implements SubscriberInterface
}
if ($id > 0) {
$contentType = ($formName === 'com_menus.item') ? 'menu' : 'com_content';
$formTypeMap = [
'com_content.article' => 'com_content',
'com_menus.item' => 'menu',
'com_categories.categorycom_content' => 'com_content.category',
];
$contentType = $formTypeMap[$formName] ?? 'com_content';
$ogData = $this->loadOgData($contentType, $id);
if ($ogData) {
@@ -102,8 +108,9 @@ final class MokoOGContent extends CMSPlugin implements SubscriberInterface
[$context, $article, $isNew] = array_values($event->getArguments());
$supportedContexts = [
'com_content.article' => 'com_content',
'com_menus.item' => 'menu',
'com_content.article' => 'com_content',
'com_menus.item' => 'menu',
'com_categories.categorycom_content' => 'com_content.category',
];
if (!isset($supportedContexts[$context])) {
@@ -143,8 +150,9 @@ final class MokoOGContent extends CMSPlugin implements SubscriberInterface
[$context, $article] = array_values($event->getArguments());
$supportedContexts = [
'com_content.article' => 'com_content',
'com_menus.item' => 'menu',
'com_content.article' => 'com_content',
'com_menus.item' => 'menu',
'com_categories.categorycom_content' => 'com_content.category',
];
if (!isset($supportedContexts[$context])) {
@@ -68,6 +68,20 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
// Try to load custom OG data from the database
$ogData = $this->loadOgData($option, $view, $id);
// For category views, also try category-level OG data as fallback
if ($option === 'com_content' && $view === 'category' && $id > 0) {
$catOg = $this->loadOgDataByType('com_content.category', $id);
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) {
if (empty($ogData->$field) && !empty($catOg->$field)) {
$ogData->$field = $catOg->$field;
}
}
}
}
// --- SEO meta tags (set first, before OG) ---
$this->applySeoTags($doc, $ogData);
@@ -199,6 +213,29 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
return $db->loadObject() ?: $empty;
}
/**
* Load OG data by content type and ID.
*
* @param string $contentType Content type identifier
* @param int $contentId Content ID
*
* @return object|null
*/
private function loadOgDataByType(string $contentType, int $contentId): ?object
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__mokoog_tags'))
->where($db->quoteName('content_type') . ' = ' . $db->quote($contentType))
->where($db->quoteName('content_id') . ' = ' . $contentId)
->where($db->quoteName('published') . ' = 1');
$db->setQuery($query);
return $db->loadObject();
}
/**
* Load OG data by menu item ID.
*
@@ -283,6 +320,26 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
return $imagesData['image_intro'];
}
}
// Fallback: check the article's category for an image
if ($view === 'article') {
$catQuery = $db->getQuery(true)
->select($db->quoteName('cat.params'))
->from($db->quoteName('#__categories', 'cat'))
->join('INNER', $db->quoteName('#__content', 'a') . ' ON ' . $db->quoteName('a.catid') . ' = ' . $db->quoteName('cat.id'))
->where($db->quoteName('a.id') . ' = ' . (int) $id);
$db->setQuery($catQuery);
$catParams = $db->loadResult();
if ($catParams) {
$catData = json_decode($catParams, true);
if (!empty($catData['image'])) {
return $catData['image'];
}
}
}
}
return $this->params->get('default_image', '');