src/EventSubscriber/AlertForecastReportTwitterEventSubscriber.php line 19

Open in your IDE?
  1. <?php
  2. namespace App\EventSubscriber;
  3. use App\Service\ReportModerationService;
  4. use App\Service\TwitterService;
  5. use Pimcore\Event\Model\DataObjectEvent;
  6. use Pimcore\Log\ApplicationLogger;
  7. use Pimcore\Model\DataObject\Report;
  8. class AlertForecastReportTwitterEventSubscriber
  9. {
  10.     public function __construct(
  11.         private TwitterService $twitterService,
  12.         private ApplicationLogger $logger,
  13.     ) {
  14.     }
  15.     public function onAlertForecastReportPublished(DataObjectEvent $event): void
  16.     {
  17.         $report $event->getObject();
  18.         if (!$report instanceof Report) {
  19.             return;
  20.         }
  21.         if (!$this->shouldPostToTwitter($report)) {
  22.             return;
  23.         }
  24.         $baseUrl rtrim((string) ($_ENV['PUBLIC_URL'] ?? NCM_PUBLIC_PORTAL_URL), '/');
  25.         $reportId $report->getId();
  26.         $publicReportUrlEn $baseUrl '/en/weather/weather-reports/' $reportId;
  27.         $publicReportUrlAr $baseUrl '/ar/weather/weather-reports/' $reportId;
  28.         $tweetMessage $this->buildTweetMessage($report$publicReportUrlEn$publicReportUrlAr);
  29.         try {
  30.             $tweet $this->twitterService->postTweet($tweetMessage);
  31.             $this->logger->info('Alert forecast report Twitter post response: ' json_encode($tweet));
  32.             if (!empty($tweet['tweetId'])) {
  33.                 $report->setTwitterId($tweet['tweetId']);
  34.             }
  35.             if (isset($tweet['data'])) {
  36.                 $report->setTwitterLog($tweet['data']);
  37.             }
  38.             $report->save();
  39.         } catch (\Throwable $e) {
  40.             $this->logger->error('Alert forecast report Twitter post failed: ' $e->getMessage());
  41.         }
  42.     }
  43.     private function buildTweetMessage(Report $reportstring $publicReportUrlEnstring $publicReportUrlAr): string
  44.     {
  45.         $titleEn trim((string) $report->getReportTitle('en'));
  46.         $titleAr trim((string) $report->getReportTitle('ar'));
  47.         $typeEn trim((string) ($report->getReportType()?->getName('en') ?? ''));
  48.         $typeAr trim((string) ($report->getReportType()?->getName('ar') ?? ''));
  49.         $twitterDescription $this->buildTwitterDescriptionBlock($report);
  50.         $trimOrder array_values(array_filter([
  51.             $twitterDescription !== '' $twitterDescription null,
  52.             $this->buildTitleBlock($titleEn$titleAr) ?: null,
  53.             $this->buildTypeBlock($typeEn$typeAr) ?: null,
  54.         ]));
  55.         $linkBlock $this->buildLinkBlock($publicReportUrlAr$publicReportUrlEn);
  56.         return $this->fitTweetLength($trimOrder$linkBlock);
  57.     }
  58.     private function buildTwitterDescriptionBlock(Report $report): string
  59.     {
  60.         return trim((string) $report->getTwitterDescription());
  61.     }
  62.     private function buildTitleBlock(string $titleEnstring $titleAr): string
  63.     {
  64.         $lines = [];
  65.         if ($titleEn !== '') {
  66.             $lines[] = '⚠️ ' $titleEn;
  67.         }
  68.         if ($titleAr !== '' && $titleAr !== $titleEn) {
  69.             $lines[] = $titleAr;
  70.         }
  71.         return implode("\n"$lines);
  72.     }
  73.     private function buildTypeBlock(string $typeEnstring $typeAr): string
  74.     {
  75.         if ($typeEn === '' && $typeAr === '') {
  76.             return '';
  77.         }
  78.         if ($typeEn !== '' && $typeAr !== '' && $typeEn !== $typeAr) {
  79.             return '📋 ' $typeEn ' | ' $typeAr;
  80.         }
  81.         return '📋 ' . ($typeEn !== '' $typeEn $typeAr);
  82.     }
  83.     private function buildLinkBlock(string $publicReportUrlArstring $publicReportUrlEn): string
  84.     {
  85.         $lines = [];
  86.         if ($publicReportUrlAr !== '') {
  87.             $lines[] = "🔗 عربي\n" $publicReportUrlAr;
  88.         }
  89.         if ($publicReportUrlEn !== '') {
  90.             $lines[] = "🔗 EN\n" $publicReportUrlEn;
  91.         }
  92.         return implode("\n"$lines);
  93.     }
  94.     /**
  95.      * Trims sections in order (twitterDescription → titles → type) while keeping the link block intact.
  96.      *
  97.      * @param string[] $bodySections
  98.      */
  99.     private function fitTweetLength(array $bodySectionsstring $linkBlockint $maxLength 280): string
  100.     {
  101.         if ($linkBlock === '') {
  102.             return $this->truncateToTweetLength(implode("\n\n"$bodySections), $maxLength);
  103.         }
  104.         $sectionIndex 0;
  105.         $safety 500;
  106.         while ($safety-- > 0) {
  107.             $body implode("\n\n"array_filter($bodySections, static fn (string $section): bool => $section !== ''));
  108.             $message $body === '' $linkBlock $body "\n\n" $linkBlock;
  109.             if ($this->getTweetLength($message) <= $maxLength) {
  110.                 return $message;
  111.             }
  112.             if ($sectionIndex >= count($bodySections)) {
  113.                 return $linkBlock;
  114.             }
  115.             if ($bodySections[$sectionIndex] === '') {
  116.                 ++$sectionIndex;
  117.                 continue;
  118.             }
  119.             $currentLength mb_strlen($bodySections[$sectionIndex]);
  120.             if ($currentLength <= 40) {
  121.                 $bodySections[$sectionIndex] = '';
  122.                 ++$sectionIndex;
  123.                 continue;
  124.             }
  125.             $bodySections[$sectionIndex] = $this->truncateText($bodySections[$sectionIndex], $currentLength 30);
  126.         }
  127.         return $linkBlock;
  128.     }
  129.     private function truncateToTweetLength(string $textint $maxLength): string
  130.     {
  131.         if ($this->getTweetLength($text) <= $maxLength) {
  132.             return $text;
  133.         }
  134.         $length mb_strlen($text);
  135.         while ($length && $this->getTweetLength($this->truncateText($text$length)) > $maxLength) {
  136.             $length -= 10;
  137.         }
  138.         return $this->truncateText($text$length);
  139.     }
  140.     private function truncateText(string $textint $maxLength): string
  141.     {
  142.         if ($maxLength <= 0) {
  143.             return '';
  144.         }
  145.         if (mb_strlen($text) <= $maxLength) {
  146.             return $text;
  147.         }
  148.         if ($maxLength === 1) {
  149.             return '…';
  150.         }
  151.         return rtrim(mb_substr($text0$maxLength 1)) . '…';
  152.     }
  153.     private function getTweetLength(string $text): int
  154.     {
  155.         $length mb_strlen($text);
  156.         if (preg_match_all('#https?://\S+#ui'$text$matches)) {
  157.             foreach ($matches[0] as $url) {
  158.                 $length -= mb_strlen($url);
  159.                 $length += 23;
  160.             }
  161.         }
  162.         return $length;
  163.     }
  164.     
  165.     private function shouldPostToTwitter($report): bool
  166.     {
  167.         return ($report instanceof Report) &&
  168.             ReportModerationService::isReportApproved($report) &&
  169.             $report->isPublished(true) &&
  170.             $report->getForTwitterChannel() == true &&
  171.             $report->getReportType()?->getReportKey() == ALERT_FORECAST_REPORT_TYPE_KEY &&
  172.             empty($report->getTwitterId()) &&
  173.             empty($report->getTwitterLog());
  174.     }
  175. }