feat(categories): category-level OG tags (#4) #18
@@ -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', '');
|
||||
|
||||
Reference in New Issue
Block a user