src/Twig/AppRuntime.php line 188

  1. <?php
  2. namespace App\Twig;
  3. use App\Entity\News;
  4. use App\Repository\NewsRepository;
  5. use App\Repository\UseCaseRepository;
  6. use DateTime;
  7. use Sulu\Bundle\MediaBundle\Api\Media;
  8. use Sulu\Component\SmartContent\ArrayAccessItem;
  9. use Sulu\Component\Webspace\Analyzer\Attributes\RequestAttributes;
  10. use Sulu\Component\Webspace\Manager\WebspaceManagerInterface;
  11. use Sulu\Component\Webspace\Webspace;
  12. use Symfony\Component\Filesystem\Exception\FileNotFoundException;
  13. use Symfony\Component\HttpFoundation\RequestStack;
  14. use Twig\Extension\RuntimeExtensionInterface;
  15. class AppRuntime implements RuntimeExtensionInterface
  16. {
  17.     // Defines the max words count for each element in teaser-grid/news-grid
  18.     public const MAX_WORDS_COUNT 20;
  19.     private string $flyOutNavigationPath;
  20.     public function __construct(
  21.         private readonly NewsRepository    $newsRepository,
  22.         private readonly UseCaseRepository $useCaseRepository,
  23.         private readonly RequestStack      $requestStack,
  24.         string                             $projectDir,
  25.         private readonly bool              $forceWebpThumbnails,
  26.         private readonly array             $forceWebpThumbnailsTypes
  27.     )
  28.     {
  29.         $this->flyOutNavigationPath $projectDir '/navigation/flyOutNavigation.json';
  30.     }
  31.     /**
  32.      * @return array
  33.      */
  34.     public function getFlyOutNavigation(): array
  35.     {
  36.         if (!file_exists($this->flyOutNavigationPath)) {
  37.             throw new FileNotFoundException('Fly-Out-Navigation JSON file does not exist!');
  38.         }
  39.         $contentJSON file_get_contents($this->flyOutNavigationPath);
  40.         $webSpaceKey $this->getCurrentWebspace()->getKey();
  41.         $flyoutData json_decode($contentJSONtrue);
  42.         return $flyoutData[$webSpaceKey];
  43.     }
  44.     /**
  45.      * Return all news from database.
  46.      * News are filtered by tags from news-grid.
  47.      *
  48.      * @param string|null $newsGridTags
  49.      * @return array
  50.      */
  51.     public function getNews(?string $newsGridTags): array
  52.     {
  53.         $newsArray = [];
  54.         $news $this->newsRepository->findAll();
  55.         if (count($news) > 0) {
  56.             /** @var News $item */
  57.             foreach ($news as $item) {
  58.                 $itemTags $item->getTags();
  59.                 // news-grid has Tags
  60.                 if (!empty($newsGridTags) && !empty(trim($newsGridTags))) {
  61.                     // But news item does not --> Continue to next news item!
  62.                     if (empty($itemTags)) {
  63.                         continue;
  64.                     }
  65.                     if (str_contains($itemTags',') !== false) {
  66.                         $newsItemTagsArray explode(','$itemTags);
  67.                         $newsItemTagsArray array_map('trim'$newsItemTagsArray);
  68.                     } else {
  69.                         $newsItemTagsArray = [trim($itemTags)];
  70.                     }
  71.                     $matched false;
  72.                     foreach ($newsItemTagsArray as $newsItemTag) {
  73.                         // news item has at least one tag that matches news-grid Tags
  74.                         if (str_contains(strtolower($newsGridTags), strtolower($newsItemTag)) !== false) {
  75.                             $matched true;
  76.                             break;
  77.                         }
  78.                     }
  79.                     // news item does not have any tag that matches news-grid Tags --> Continue to next news item!
  80.                     if (!$matched) {
  81.                         continue;
  82.                     }
  83.                 }
  84.                 $image json_decode($item->getImage(), true);
  85.                 $dateObject = new DateTime($item->getDate());
  86.                 $newsArray[] = [
  87.                     'title' => $item->getTitle(),
  88.                     'description' => $item->getDescription(),
  89.                     'source' => $item->getSource(),
  90.                     'date' => $item->getDate(),
  91.                     'dateGerman' => $dateObject->format('d.m.Y'),
  92.                     'image' => $image['id'],
  93.                     'thumbnails' => null,
  94.                     'url' => $item->getUrl(),
  95.                 ];
  96.             }
  97.         }
  98.         return $newsArray;
  99.     }
  100.     /**
  101.      * Return all news from database.
  102.      * News are filtered by tags from news-grid.
  103.      *
  104.      * @return array
  105.      */
  106.     public function getUseCases(string $locale, array $tags): array
  107.     {
  108.         $useCasesArray = [];
  109.         $useCases $tags $this->useCaseRepository->findByTagsWithTopics($locale$tags) : $this->useCaseRepository->findAllWithTopics($locale);
  110.         if (count($useCases) > 0) {
  111.             foreach ($useCases as $item) {
  112.                 $image json_decode($item->getImage(), true);
  113.                 $useCasesArray[] = [
  114.                     'topic' => [
  115.                         'title' => $item->getUseCaseTopic()->getTitle(),
  116.                         'description' => $item->getUseCaseTopic()->getDescription(),
  117.                         'color' => $item->getUseCaseTopic()->getColor(),
  118.                     ],
  119.                     'title' => $item->getTitle(),
  120.                     'titleMobile' => $item->getTitleMobile(),
  121.                     'description' => $item->getDescription(),
  122.                     'image' => $image['id'],
  123.                     'tags' => $item->getTags(),
  124.                     'thumbnails' => null,
  125.                 ];
  126.             }
  127.         }
  128.         return $useCasesArray;
  129.     }
  130.     /**
  131.      * - Map stories to array like news
  132.      * - Merge stories and news
  133.      * - Sort by date
  134.      *
  135.      * @param array $stories
  136.      * @param array $publications
  137.      * @param array $pages
  138.      * @param array $news
  139.      * @param bool $contentMixed
  140.      * @return array
  141.      */
  142.     public function sortByDate(
  143.         array $stories,
  144.         array $publications,
  145.         array $pages,
  146.         array $news,
  147.         bool $contentMixed false
  148.     ): array {
  149.         $pagesArray = [];
  150.         // Get other pages only if contentMixed is true
  151.         if ($contentMixed) {
  152.             $pagesArray array_merge($stories$publications$pages);
  153.         }
  154.         $newsArray $news;
  155.         if (count($pagesArray) > 0) {
  156.             $newsArray array_merge($pagesArray$news);
  157.         }
  158.         // Sort descending by date
  159.         usort($newsArray, function ($a$b) {
  160.             return $a['date'] < $b['date'];
  161.         });
  162.         return $newsArray;
  163.     }
  164.     /**
  165.      * Prepare data from smart content for news-grid.
  166.      * If description has more than 20 words, the first 20 words will be cut and ... will be added to the end.
  167.      * If alternativeText is available, replace description. Text will not be cut. Length remains unchanged.
  168.      *
  169.      * @param array $items
  170.      * @param string $locale
  171.      * @return array
  172.      */
  173.     public function getDataFromSmartContent(array $itemsstring $locale): array
  174.     {
  175.         $data = [];
  176.         /** @var ArrayAccessItem $item */
  177.         foreach ($items as $item) {
  178.             /** @var Media $image */
  179.             $excerptImage $item['excerptAlternativeImage'] ?? $item['excerptImages'] ?? null;
  180.             $thumbnails $excerptImage instanceof Media $excerptImage->getThumbnails() : null;
  181.             // Teaser grid title is preferred, then title!
  182.             $title $item['excerptTitleTeaserGrid'] ?? '';
  183.             if (empty($title)) {
  184.                 $title $item['excerptTitle'] ?? '';
  185.             }
  186.             // Custom Text is preferred, then description!
  187.             $description $item['excerptCustomText'] ?? '';
  188.             if (empty($description)) {
  189.                 $description $item['excerptDescription'] ?? '';
  190.                 if (!empty($description)) {
  191.                     $descriptionArray explode(' '$description);
  192.                     // Get the first 20 words
  193.                     if (count($descriptionArray) > self::MAX_WORDS_COUNT) {
  194.                         $descriptionArray array_slice($descriptionArray0self::MAX_WORDS_COUNT);
  195.                         $description implode(' '$descriptionArray);
  196.                         $description .= ' ...';
  197.                     }
  198.                 }
  199.             }
  200.             // Creation date from settings is date of story!
  201.             $authored $item['authored'];
  202.             $data[] = [
  203.                 'title' => $title,
  204.                 'description' => $description,
  205.                 'source' => $item['excerptType'] ?? '',
  206.                 'date' => $authored->format('Y-m-d'),
  207.                 'dateGerman' => $authored->format('d.m.Y'),
  208.                 'image' => $thumbnails['2880x'] ?? null,
  209.                 'thumbnails' => $thumbnails,
  210.                 'url' => '/' $locale $item['url'],  // Add locale to URL
  211.             ];
  212.         }
  213.         return $data;
  214.     }
  215.     public function getThumbnailUrl($media$size)
  216.     {
  217.         if($media instanceof Media) {
  218.             $thumbnails $media->getThumbnails();
  219.             if(!array_key_exists($size$thumbnails)) {
  220.                 throw new \InvalidArgumentException('Unknown thumbnail size "' $size '".');
  221.             }
  222.             if($this->forceWebpThumbnails && in_array($media->getFileVersion()->getMimeType(), $this->forceWebpThumbnailsTypes)) {
  223.                 $size .= '.webp';
  224.             }
  225.             return $thumbnails[$size];
  226.         } else if(is_array($media) && array_key_exists('thumbnails'$media)) {
  227.             $thumbnails $media['thumbnails'];
  228.             if(!array_key_exists($size$thumbnails)) {
  229.                 throw new \InvalidArgumentException('Unknown thumbnail size "' $size '".');
  230.             }
  231.             $extensionMimeMap = [
  232.                 'jpg' => 'image/jpeg',
  233.                 'jpeg' => 'image/jpeg',
  234.                 'png' => 'image/png',
  235.                 'webp' => 'image/webp'
  236.             ];
  237.             $ext strtolower(pathinfo(parse_url($media['image'],  PHP_URL_PATH), PATHINFO_EXTENSION));
  238.             if($this->forceWebpThumbnails && array_key_exists($ext$extensionMimeMap) && in_array($extensionMimeMap[$ext], $this->forceWebpThumbnailsTypes)) {
  239.                 $size .= '.webp';
  240.             }
  241.             return $thumbnails[$size];
  242.         }
  243.     }
  244.     private function getCurrentWebspace(): ?Webspace
  245.     {
  246.         $currentRequest $this->requestStack->getCurrentRequest();
  247.         if (!$currentRequest) {
  248.             return null;
  249.         }
  250.         $suluAttributes $currentRequest->attributes->get('_sulu');
  251.         if (!$suluAttributes instanceof RequestAttributes) {
  252.             return null;
  253.         }
  254.         return $suluAttributes->getAttribute('webspace');
  255.     }
  256. }