diff --git a/source/packages/com_mokosuitecross/src/Controller/PreviewController.php b/source/packages/com_mokosuitecross/src/Controller/PreviewController.php new file mode 100644 index 00000000..7dc45502 --- /dev/null +++ b/source/packages/com_mokosuitecross/src/Controller/PreviewController.php @@ -0,0 +1,86 @@ + + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +namespace Joomla\Component\MokoSuiteCross\Administrator\Controller; + +defined('_JEXEC') or die; + +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\CrossPostDispatcher; +use Joomla\Component\MokoSuiteCross\Administrator\Helper\PreviewHelper; + +class PreviewController extends BaseController +{ + public function render(): void + { + if (!Session::checkToken('get')) { + echo json_encode(['error' => 'Invalid token']); + $this->app->close(); + + return; + } + + $articleId = $this->input->getInt('article_id', 0); + $platform = $this->input->getCmd('platform', 'twitter'); + + if ($articleId < 1) { + echo json_encode(['error' => 'Missing article ID']); + $this->app->close(); + + return; + } + + $db = Factory::getDbo(); + + $query = $db->getQuery(true) + ->select('*') + ->from($db->quoteName('#__content')) + ->where($db->quoteName('id') . ' = ' . $articleId); + $db->setQuery($query); + $article = $db->loadObject(); + + if (!$article) { + echo json_encode(['error' => 'Article not found']); + $this->app->close(); + + return; + } + + $meta = CrossPostDispatcher::buildArticleMeta($article); + + $title = $meta['{title}'] ?? ''; + $text = $meta['{introtext}'] ?? ''; + $url = $meta['{url}'] ?? ''; + $imageUrl = $meta['{image}'] ?? ''; + $authorName = $meta['{author}'] ?? ''; + + $supportedPlatforms = PreviewHelper::getSupportedPlatforms(); + $html = ''; + + if ($platform === 'all') { + foreach ($supportedPlatforms as $p) { + $html .= '
' + . '
' . htmlspecialchars(ucfirst($p)) . '
' + . PreviewHelper::render($p, $title, $text, $url, $imageUrl, $authorName) + . '
'; + } + } else { + $html = PreviewHelper::render($platform, $title, $text, $url, $imageUrl, $authorName); + } + + $this->app->setHeader('Content-Type', 'text/html; charset=utf-8'); + echo $html; + $this->app->close(); + } +} diff --git a/source/packages/com_mokosuitecross/src/Helper/PreviewHelper.php b/source/packages/com_mokosuitecross/src/Helper/PreviewHelper.php new file mode 100644 index 00000000..fc0fd0ad --- /dev/null +++ b/source/packages/com_mokosuitecross/src/Helper/PreviewHelper.php @@ -0,0 +1,207 @@ + + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +namespace Joomla\Component\MokoSuiteCross\Administrator\Helper; + +defined('_JEXEC') or die; + +class PreviewHelper +{ + public static function render(string $platform, string $title, string $text, string $url, string $imageUrl = '', string $authorName = ''): string + { + $title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); + $text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); + $url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8'); + $authorName = htmlspecialchars($authorName, ENT_QUOTES, 'UTF-8'); + + $imageHtml = ''; + + if (!empty($imageUrl)) { + $imageUrl = htmlspecialchars($imageUrl, ENT_QUOTES, 'UTF-8'); + $imageHtml = ''; + } + + return match ($platform) { + 'twitter' => self::renderTwitter($title, $text, $url, $imageHtml, $authorName), + 'facebook' => self::renderFacebook($title, $text, $url, $imageHtml, $authorName), + 'mastodon' => self::renderMastodon($title, $text, $url, $imageHtml, $authorName), + 'linkedin' => self::renderLinkedIn($title, $text, $url, $imageHtml, $authorName), + 'bluesky' => self::renderBluesky($title, $text, $url, $imageHtml, $authorName), + 'telegram' => self::renderTelegram($title, $text, $url, $imageHtml), + default => self::renderGeneric($platform, $title, $text, $url, $imageHtml), + }; + } + + public static function getSupportedPlatforms(): array + { + return ['twitter', 'facebook', 'mastodon', 'linkedin', 'bluesky', 'telegram']; + } + + private static function renderTwitter(string $title, string $text, string $url, string $imageHtml, string $author): string + { + $displayText = !empty($text) ? $text : $title; + $charCount = mb_strlen(strip_tags($displayText)); + + return << +
+
X
+
+
{$author}
+
@username
+
+
+
{$displayText}
+ {$imageHtml} +
+
yoursite.com
+
{$title}
+
+
{$charCount}/280
+ +HTML; + } + + private static function renderFacebook(string $title, string $text, string $url, string $imageHtml, string $author): string + { + $displayText = !empty($text) ? $text : $title; + + return << +
+
+
f
+
+
{$author}
+
Just now
+
+
+
{$displayText}
+
+ {$imageHtml} +
+
yoursite.com
+
{$title}
+
+ +HTML; + } + + private static function renderMastodon(string $title, string $text, string $url, string $imageHtml, string $author): string + { + $displayText = !empty($text) ? $text : $title; + $charCount = mb_strlen(strip_tags($displayText)); + + return << +
+
M
+
+
{$author}
+
@user@mastodon.social
+
+
+
{$displayText}
+ {$imageHtml} +
+
{$title}
+
yoursite.com
+
+
{$charCount}/500
+ +HTML; + } + + private static function renderLinkedIn(string $title, string $text, string $url, string $imageHtml, string $author): string + { + $displayText = !empty($text) ? $text : $title; + + return << +
+
+
in
+
+
{$author}
+
Just now
+
+
+
{$displayText}
+
+ {$imageHtml} +
+
{$title}
+
yoursite.com
+
+ +HTML; + } + + private static function renderBluesky(string $title, string $text, string $url, string $imageHtml, string $author): string + { + $displayText = !empty($text) ? $text : $title; + $charCount = mb_strlen(strip_tags($displayText)); + + return << +
+
B
+
+
{$author}
+
@user.bsky.social
+
+
+
{$displayText}
+ {$imageHtml} +
+
{$title}
+
yoursite.com
+
+
{$charCount}/300
+ +HTML; + } + + private static function renderTelegram(string $title, string $text, string $url, string $imageHtml): string + { + $displayText = !empty($text) ? $text : $title; + + return << + {$imageHtml} +
{$displayText}
+
+
{$title}
+
yoursite.com
+
+
Just now
+ +HTML; + } + + private static function renderGeneric(string $platform, string $title, string $text, string $url, string $imageHtml): string + { + $platformLabel = htmlspecialchars(ucfirst($platform), ENT_QUOTES, 'UTF-8'); + $displayText = !empty($text) ? $text : $title; + + return << +
{$platformLabel}
+
{$displayText}
+ {$imageHtml} +
+
{$title}
+
yoursite.com
+
+ +HTML; + } +} diff --git a/source/packages/plg_content_mokosuitecross/language/en-GB/plg_content_mokosuitecross.ini b/source/packages/plg_content_mokosuitecross/language/en-GB/plg_content_mokosuitecross.ini index 50ff4171..990c1de2 100644 --- a/source/packages/plg_content_mokosuitecross/language/en-GB/plg_content_mokosuitecross.ini +++ b/source/packages/plg_content_mokosuitecross/language/en-GB/plg_content_mokosuitecross.ini @@ -31,3 +31,6 @@ PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE_CUSTOM="Custom Image" PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE_NONE="No Image" PLG_CONTENT_MOKOSUITECROSS_CUSTOM_IMAGE="Custom Share Image" PLG_CONTENT_MOKOSUITECROSS_CUSTOM_IMAGE_DESC="Select an image from the media manager to use for cross-posting." + +PLG_CONTENT_MOKOSUITECROSS_FIELDSET_PREVIEW="Social Preview" +PLG_CONTENT_MOKOSUITECROSS_PREVIEW="Preview Post" diff --git a/source/packages/plg_content_mokosuitecross/src/Extension/MokoSuiteCrossContent.php b/source/packages/plg_content_mokosuitecross/src/Extension/MokoSuiteCrossContent.php index 4f863368..376b4003 100644 --- a/source/packages/plg_content_mokosuitecross/src/Extension/MokoSuiteCrossContent.php +++ b/source/packages/plg_content_mokosuitecross/src/Extension/MokoSuiteCrossContent.php @@ -19,6 +19,7 @@ use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Plugin\CMSPlugin; +use Joomla\CMS\Session\Session; use Joomla\CMS\Uri\Uri; use Joomla\Component\MokoSuiteCross\Administrator\Helper\CrossPostDispatcher; use Joomla\Event\SubscriberInterface; @@ -265,6 +266,47 @@ XML; $form->load($historyXml); $form->setFieldAttribute('mokosuitecross_history', 'description', $historyHtml, 'attribs'); } + + // Social Preview panel (#156) + $token = Session::getFormToken(); + $previewUrl = Uri::base() . 'index.php?option=com_mokosuitecross&task=preview.render&format=raw&article_id=' . $articleId . '&' . $token . '=1'; + + $previewButtonHtml = '
' + . '
' + . ' ' + . '' + . '
' + . '
' + . '
' + . ''; + + $previewXml = ' +
+ +
'; + $form->load($previewXml); + $form->setFieldAttribute('mokosuitecross_preview_panel', 'description', $previewButtonHtml, 'attribs'); } }