<?php
namespace App\EventSubscriber;
use App\Service\ReportModerationService;
use App\Service\TwitterService;
use Pimcore\Event\Model\DataObjectEvent;
use Pimcore\Log\ApplicationLogger;
use Pimcore\Model\DataObject\Report;
class AlertForecastReportTwitterEventSubscriber
{
public function __construct(
private TwitterService $twitterService,
private ApplicationLogger $logger,
) {
}
public function onAlertForecastReportPublished(DataObjectEvent $event): void
{
$report = $event->getObject();
if (!$report instanceof Report) {
return;
}
if (!$this->shouldPostToTwitter($report)) {
return;
}
$baseUrl = rtrim((string) ($_ENV['PUBLIC_URL'] ?? NCM_PUBLIC_PORTAL_URL), '/');
$reportId = $report->getId();
$publicReportUrlEn = $baseUrl . '/en/weather/weather-reports/' . $reportId;
$publicReportUrlAr = $baseUrl . '/ar/weather/weather-reports/' . $reportId;
$tweetMessage = $this->buildTweetMessage($report, $publicReportUrlEn, $publicReportUrlAr);
try {
$tweet = $this->twitterService->postTweet($tweetMessage);
$this->logger->info('Alert forecast report Twitter post response: ' . json_encode($tweet));
if (!empty($tweet['tweetId'])) {
$report->setTwitterId($tweet['tweetId']);
}
if (isset($tweet['data'])) {
$report->setTwitterLog($tweet['data']);
}
$report->save();
} catch (\Throwable $e) {
$this->logger->error('Alert forecast report Twitter post failed: ' . $e->getMessage());
}
}
private function buildTweetMessage(Report $report, string $publicReportUrlEn, string $publicReportUrlAr): string
{
$titleEn = trim((string) $report->getReportTitle('en'));
$titleAr = trim((string) $report->getReportTitle('ar'));
$typeEn = trim((string) ($report->getReportType()?->getName('en') ?? ''));
$typeAr = trim((string) ($report->getReportType()?->getName('ar') ?? ''));
$twitterDescription = $this->buildTwitterDescriptionBlock($report);
$trimOrder = array_values(array_filter([
$twitterDescription !== '' ? $twitterDescription : null,
$this->buildTitleBlock($titleEn, $titleAr) ?: null,
$this->buildTypeBlock($typeEn, $typeAr) ?: null,
]));
$linkBlock = $this->buildLinkBlock($publicReportUrlAr, $publicReportUrlEn);
return $this->fitTweetLength($trimOrder, $linkBlock);
}
private function buildTwitterDescriptionBlock(Report $report): string
{
return trim((string) $report->getTwitterDescription());
}
private function buildTitleBlock(string $titleEn, string $titleAr): string
{
$lines = [];
if ($titleEn !== '') {
$lines[] = '⚠️ ' . $titleEn;
}
if ($titleAr !== '' && $titleAr !== $titleEn) {
$lines[] = $titleAr;
}
return implode("\n", $lines);
}
private function buildTypeBlock(string $typeEn, string $typeAr): string
{
if ($typeEn === '' && $typeAr === '') {
return '';
}
if ($typeEn !== '' && $typeAr !== '' && $typeEn !== $typeAr) {
return '📋 ' . $typeEn . ' | ' . $typeAr;
}
return '📋 ' . ($typeEn !== '' ? $typeEn : $typeAr);
}
private function buildLinkBlock(string $publicReportUrlAr, string $publicReportUrlEn): string
{
$lines = [];
if ($publicReportUrlAr !== '') {
$lines[] = "🔗 عربي\n" . $publicReportUrlAr;
}
if ($publicReportUrlEn !== '') {
$lines[] = "🔗 EN\n" . $publicReportUrlEn;
}
return implode("\n", $lines);
}
/**
* Trims sections in order (twitterDescription → titles → type) while keeping the link block intact.
*
* @param string[] $bodySections
*/
private function fitTweetLength(array $bodySections, string $linkBlock, int $maxLength = 280): string
{
if ($linkBlock === '') {
return $this->truncateToTweetLength(implode("\n\n", $bodySections), $maxLength);
}
$sectionIndex = 0;
$safety = 500;
while ($safety-- > 0) {
$body = implode("\n\n", array_filter($bodySections, static fn (string $section): bool => $section !== ''));
$message = $body === '' ? $linkBlock : $body . "\n\n" . $linkBlock;
if ($this->getTweetLength($message) <= $maxLength) {
return $message;
}
if ($sectionIndex >= count($bodySections)) {
return $linkBlock;
}
if ($bodySections[$sectionIndex] === '') {
++$sectionIndex;
continue;
}
$currentLength = mb_strlen($bodySections[$sectionIndex]);
if ($currentLength <= 40) {
$bodySections[$sectionIndex] = '';
++$sectionIndex;
continue;
}
$bodySections[$sectionIndex] = $this->truncateText($bodySections[$sectionIndex], $currentLength - 30);
}
return $linkBlock;
}
private function truncateToTweetLength(string $text, int $maxLength): string
{
if ($this->getTweetLength($text) <= $maxLength) {
return $text;
}
$length = mb_strlen($text);
while ($length > 0 && $this->getTweetLength($this->truncateText($text, $length)) > $maxLength) {
$length -= 10;
}
return $this->truncateText($text, $length);
}
private function truncateText(string $text, int $maxLength): string
{
if ($maxLength <= 0) {
return '';
}
if (mb_strlen($text) <= $maxLength) {
return $text;
}
if ($maxLength === 1) {
return '…';
}
return rtrim(mb_substr($text, 0, $maxLength - 1)) . '…';
}
private function getTweetLength(string $text): int
{
$length = mb_strlen($text);
if (preg_match_all('#https?://\S+#ui', $text, $matches)) {
foreach ($matches[0] as $url) {
$length -= mb_strlen($url);
$length += 23;
}
}
return $length;
}
private function shouldPostToTwitter($report): bool
{
return ($report instanceof Report) &&
ReportModerationService::isReportApproved($report) &&
$report->isPublished(true) &&
$report->getForTwitterChannel() == true &&
$report->getReportType()?->getReportKey() == ALERT_FORECAST_REPORT_TYPE_KEY &&
empty($report->getTwitterId()) &&
empty($report->getTwitterLog());
}
}