feat: social image generator with GD text overlay #213
+3
-2
@@ -12,8 +12,9 @@
|
||||
- **Analytics service filter**: Filter heatmap and stats by service type with configurable date range
|
||||
- **Analytics service breakdown**: Per-service success rate, failure count, and average posts per day
|
||||
- **Analytics AJAX endpoint**: JSON heatmap data for dynamic filtering without page reload
|
||||
- **Social image generator**: Generate Open Graph images with article title overlay using PHP GD library (#157)
|
||||
- **Social image config**: Background color, text color, overlay style, and site name override in component options (#157)
|
||||
- **Social image generator**: Generate branded 1200x630 OG images with article title overlay using PHP GD (#157)
|
||||
- **Social image config**: Background color, text color, font size, and site name branding options (#157)
|
||||
- **Generate Social Image button**: One-click image generation in the Share Content panel (#157)
|
||||
- **AI caption generation**: Generate platform-optimized cross-post captions from article content using Claude or OpenAI (#161)
|
||||
- **AI provider config**: New "AI Caption Generation" fieldset in component options with provider, API key, model, and tone settings
|
||||
- **AI Generate button**: One-click AI generation button in the Share Content panel that fills all caption fields
|
||||
|
||||
@@ -266,7 +266,7 @@
|
||||
</field>
|
||||
</fieldset>
|
||||
|
||||
<fieldset name="social_image" label="COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE">
|
||||
<fieldset name="social_image" label="COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE">
|
||||
<field
|
||||
name="social_image_enabled"
|
||||
type="radio"
|
||||
|
||||
@@ -570,7 +570,24 @@ COM_MOKOSUITECROSS_AI_GENERATE_DESC="Generate platform-optimized captions from t
|
||||
COM_MOKOSUITECROSS_AI_GENERATING="Generating captions..."
|
||||
COM_MOKOSUITECROSS_AI_GENERATED="AI captions generated successfully."
|
||||
COM_MOKOSUITECROSS_AI_ERROR="AI generation failed: %s"
|
||||
COM_MOKOSUITECROSS_AI_NOT_CONFIGURED="AI is not configured. Go to Options to set up a provider and API key."
|
||||
|
||||
; Social Image Generator
|
||||
COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE="Social Image Generator"
|
||||
COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_ENABLED="Enable Social Images"
|
||||
COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_ENABLED_DESC="Generate branded OG images with article title overlay for social sharing."
|
||||
COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_BG_COLOR="Background Color"
|
||||
COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_BG_COLOR_DESC="Hex color for the image background (e.g. #1a1a2e)."
|
||||
COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_TEXT_COLOR="Text Color"
|
||||
COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_TEXT_COLOR_DESC="Hex color for the title text overlay."
|
||||
COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_FONT_SIZE="Font Size"
|
||||
COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_FONT_SIZE_DESC="Font size in pixels for the title text (24-96)."
|
||||
COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_SHOW_SITE_NAME="Show Site Name"
|
||||
COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_SHOW_SITE_NAME_DESC="Display the site name in the bottom-right corner of generated images."
|
||||
COM_MOKOSUITECROSS_SOCIAL_IMAGE_GENERATE="Generate Social Image"
|
||||
COM_MOKOSUITECROSS_SOCIAL_IMAGE_GENERATING="Generating image..."
|
||||
COM_MOKOSUITECROSS_SOCIAL_IMAGE_GENERATED="Social image generated."
|
||||
COM_MOKOSUITECROSS_SOCIAL_IMAGE_ERROR="Image generation failed: %s"
|
||||
COM_MOKOSUITECROSS_SOCIAL_IMAGE_NOT_CONFIGURED="Social image generator is not enabled. Go to Options to enable it."
|
||||
|
||||
; Analytics
|
||||
COM_MOKOSUITECROSS_SUBMENU_ANALYTICS="Analytics"
|
||||
|
||||
@@ -17,7 +17,6 @@ use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
use Joomla\CMS\Session\Session;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Component\MokoSuiteCross\Administrator\Helper\SocialImageHelper;
|
||||
|
||||
class SocialImageController extends BaseController
|
||||
@@ -33,7 +32,7 @@ class SocialImageController extends BaseController
|
||||
|
||||
$user = $this->app->getIdentity();
|
||||
|
||||
if (!$user->authorise('core.manage', 'com_mokosuitecross')) {
|
||||
if (!$user->authorise('core.edit', 'com_mokosuitecross')) {
|
||||
echo json_encode(['success' => false, 'error' => 'Permission denied']);
|
||||
$this->app->close();
|
||||
|
||||
@@ -49,47 +48,40 @@ class SocialImageController extends BaseController
|
||||
return;
|
||||
}
|
||||
|
||||
$params = ComponentHelper::getParams('com_mokosuitecross');
|
||||
|
||||
if (!(int) $params->get('social_image_enabled', 0)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Social image generator is not enabled']);
|
||||
$this->app->close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName(['id', 'title', 'images']))
|
||||
->select($db->quoteName('title'))
|
||||
->from($db->quoteName('#__content'))
|
||||
->where($db->quoteName('id') . ' = ' . $articleId);
|
||||
$db->setQuery($query);
|
||||
$article = $db->loadObject();
|
||||
$title = $db->loadResult();
|
||||
|
||||
if (!$article) {
|
||||
if (!$title) {
|
||||
echo json_encode(['success' => false, 'error' => 'Article not found']);
|
||||
$this->app->close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$params = ComponentHelper::getParams('com_mokosuitecross');
|
||||
$siteName = $params->get('social_image_site_name', '') ?: Factory::getApplication()->get('sitename', '');
|
||||
$siteName = $this->app->get('sitename', '');
|
||||
|
||||
$options = [
|
||||
'bg_color' => $params->get('social_image_bg_color', '#1a1a2e'),
|
||||
'text_color' => $params->get('social_image_text_color', '#ffffff'),
|
||||
'overlay' => $params->get('social_image_overlay', 'dark'),
|
||||
$config = [
|
||||
'bg_color' => $params->get('social_image_bg_color', '#1a1a2e'),
|
||||
'text_color' => $params->get('social_image_text_color', '#ffffff'),
|
||||
'font_size' => $params->get('social_image_font_size', 48),
|
||||
'show_site_name' => (bool) $params->get('social_image_show_site_name', 1),
|
||||
];
|
||||
|
||||
$backgroundPath = null;
|
||||
$images = json_decode($article->images ?? '{}', true);
|
||||
|
||||
if (!empty($images['image_intro'])) {
|
||||
$backgroundPath = JPATH_ROOT . '/' . ltrim($images['image_intro'], '/');
|
||||
} elseif (!empty($images['image_fulltext'])) {
|
||||
$backgroundPath = JPATH_ROOT . '/' . ltrim($images['image_fulltext'], '/');
|
||||
}
|
||||
|
||||
try {
|
||||
$imagePath = SocialImageHelper::generate($article->title, $siteName, $backgroundPath, $options);
|
||||
$imageUrl = str_replace(JPATH_ROOT, Uri::root(true), str_replace('\\', '/', $imagePath));
|
||||
|
||||
$result = ['success' => true, 'image_url' => $imageUrl, 'image_path' => $imagePath];
|
||||
} catch (\Throwable $e) {
|
||||
$result = ['success' => false, 'error' => $e->getMessage()];
|
||||
}
|
||||
$result = SocialImageHelper::generate($title, $siteName, $config);
|
||||
|
||||
$this->app->setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
echo json_encode($result);
|
||||
|
||||
@@ -257,6 +257,53 @@ XML;
|
||||
$form->load($aiXml);
|
||||
$form->setFieldAttribute('mokosuitecross_ai_generate', 'description', $aiButtonHtml, 'attribs');
|
||||
}
|
||||
// Social Image Generator button (#157)
|
||||
$siParams = ComponentHelper::getParams('com_mokosuitecross');
|
||||
$siEnabled = (bool) $siParams->get('social_image_enabled', 0);
|
||||
|
||||
if ($siEnabled && $articleId > 0) {
|
||||
$siToken = Session::getFormToken();
|
||||
$siUrl = Uri::base() . 'index.php?option=com_mokosuitecross&task=socialimage.generate&format=raw&article_id=' . $articleId . '&' . $siToken . '=1';
|
||||
|
||||
$siButtonHtml = '<div class="mb-3">'
|
||||
. '<button type="button" id="mokosuitecross-si-btn" class="btn btn-sm btn-outline-success" onclick="mokosuitecrossSiGenerate()">'
|
||||
. '<span class="icon-image" aria-hidden="true"></span> '
|
||||
. \Joomla\CMS\Language\Text::_('COM_MOKOSUITECROSS_SOCIAL_IMAGE_GENERATE')
|
||||
. '</button>'
|
||||
. '<span id="mokosuitecross-si-status" class="ms-2 small"></span>'
|
||||
. '<div id="mokosuitecross-si-preview" class="mt-2" style="display:none;">'
|
||||
. '<img id="mokosuitecross-si-thumb" src="" alt="Social image preview" style="max-width:300px;border:1px solid #ccc;border-radius:4px;" />'
|
||||
. '</div>'
|
||||
. '</div>'
|
||||
. '<script>'
|
||||
. 'function mokosuitecrossSiGenerate(){'
|
||||
. 'var btn=document.getElementById("mokosuitecross-si-btn");'
|
||||
. 'var st=document.getElementById("mokosuitecross-si-status");'
|
||||
. 'var pv=document.getElementById("mokosuitecross-si-preview");'
|
||||
. 'var img=document.getElementById("mokosuitecross-si-thumb");'
|
||||
. 'btn.disabled=true;st.textContent="' . \Joomla\CMS\Language\Text::_('COM_MOKOSUITECROSS_SOCIAL_IMAGE_GENERATING', true) . '";'
|
||||
. 'pv.style.display="none";'
|
||||
. 'fetch("' . $siUrl . '")'
|
||||
. '.then(function(r){return r.json();})'
|
||||
. '.then(function(d){'
|
||||
. 'btn.disabled=false;'
|
||||
. 'if(!d.success){st.textContent=d.error||"Error";return;}'
|
||||
. 'st.textContent="' . \Joomla\CMS\Language\Text::_('COM_MOKOSUITECROSS_SOCIAL_IMAGE_GENERATED', true) . '";'
|
||||
. 'img.src="' . Uri::root() . '"+d.image_url+"?t="+Date.now();'
|
||||
. 'pv.style.display="block";'
|
||||
. '})'
|
||||
. '.catch(function(){btn.disabled=false;st.textContent="Request failed";});'
|
||||
. '}'
|
||||
. '</script>';
|
||||
|
||||
$siXml = '<?xml version="1.0"?>
|
||||
<form><fields name="attribs"><fieldset name="mokosuitecross_share">
|
||||
<field name="mokosuitecross_si_generate" type="note"
|
||||
label="" description="" />
|
||||
</fieldset></fields></form>';
|
||||
$form->load($siXml);
|
||||
$form->setFieldAttribute('mokosuitecross_si_generate', 'description', $siButtonHtml, 'attribs');
|
||||
}
|
||||
|
||||
// Cross-post history panel for existing articles
|
||||
|
||||
|
||||
Reference in New Issue
Block a user