vendor/contao/calendar-bundle/src/Resources/contao/classes/Calendar.php line 398

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Contao.
  4.  *
  5.  * (c) Leo Feyer
  6.  *
  7.  * @license LGPL-3.0-or-later
  8.  */
  9. namespace Contao;
  10. use Symfony\Component\HttpFoundation\Request;
  11. use Symfony\Component\HttpFoundation\RequestStack;
  12. use Symfony\Component\HttpFoundation\Session\Session;
  13. use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
  14. /**
  15.  * Provide methods regarding calendars.
  16.  */
  17. class Calendar extends Frontend
  18. {
  19.     /**
  20.      * Current events
  21.      * @var array
  22.      */
  23.     protected $arrEvents = array();
  24.     /**
  25.      * Page cache array
  26.      * @var array
  27.      */
  28.     private static $arrPageCache = array();
  29.     /**
  30.      * Update a particular RSS feed
  31.      *
  32.      * @param integer $intId
  33.      */
  34.     public function generateFeed($intId)
  35.     {
  36.         $objCalendar CalendarFeedModel::findByPk($intId);
  37.         if ($objCalendar === null)
  38.         {
  39.             return;
  40.         }
  41.         $objCalendar->feedName $objCalendar->alias ?: 'calendar' $objCalendar->id;
  42.         // Delete XML file
  43.         if (Input::get('act') == 'delete')
  44.         {
  45.             $webDir StringUtil::stripRootDir(System::getContainer()->getParameter('contao.web_dir'));
  46.             $this->import(Files::class, 'Files');
  47.             $this->Files->delete($webDir '/share/' $objCalendar->feedName '.xml');
  48.         }
  49.         // Update XML file
  50.         else
  51.         {
  52.             $this->generateFiles($objCalendar->row());
  53.             System::getContainer()->get('monolog.logger.contao.cron')->info('Generated calendar feed "' $objCalendar->feedName '.xml"');
  54.         }
  55.     }
  56.     /**
  57.      * Delete old files and generate all feeds
  58.      */
  59.     public function generateFeeds()
  60.     {
  61.         $this->import(Automator::class, 'Automator');
  62.         $this->Automator->purgeXmlFiles();
  63.         $objCalendar CalendarFeedModel::findAll();
  64.         if ($objCalendar !== null)
  65.         {
  66.             while ($objCalendar->next())
  67.             {
  68.                 $objCalendar->feedName $objCalendar->alias ?: 'calendar' $objCalendar->id;
  69.                 $this->generateFiles($objCalendar->row());
  70.                 System::getContainer()->get('monolog.logger.contao.cron')->info('Generated calendar feed "' $objCalendar->feedName '.xml"');
  71.             }
  72.         }
  73.     }
  74.     /**
  75.      * Generate all feeds including a certain calendar
  76.      *
  77.      * @param integer $intId
  78.      */
  79.     public function generateFeedsByCalendar($intId)
  80.     {
  81.         $objFeed CalendarFeedModel::findByCalendar($intId);
  82.         if ($objFeed !== null)
  83.         {
  84.             while ($objFeed->next())
  85.             {
  86.                 $objFeed->feedName $objFeed->alias ?: 'calendar' $objFeed->id;
  87.                 // Update the XML file
  88.                 $this->generateFiles($objFeed->row());
  89.                 System::getContainer()->get('monolog.logger.contao.cron')->info('Generated calendar feed "' $objFeed->feedName '.xml"');
  90.             }
  91.         }
  92.     }
  93.     /**
  94.      * Generate an XML file and save it to the root directory
  95.      *
  96.      * @param array $arrFeed
  97.      */
  98.     protected function generateFiles($arrFeed)
  99.     {
  100.         $arrCalendars StringUtil::deserialize($arrFeed['calendars']);
  101.         if (empty($arrCalendars) || !\is_array($arrCalendars))
  102.         {
  103.             return;
  104.         }
  105.         $strType = ($arrFeed['format'] == 'atom') ? 'generateAtom' 'generateRss';
  106.         $strLink $arrFeed['feedBase'] ?: Environment::get('base');
  107.         $strFile $arrFeed['feedName'];
  108.         $objFeed = new Feed($strFile);
  109.         $objFeed->link $strLink;
  110.         $objFeed->title $arrFeed['title'];
  111.         $objFeed->description $arrFeed['description'];
  112.         $objFeed->language $arrFeed['language'];
  113.         $objFeed->published $arrFeed['tstamp'];
  114.         $arrUrls = array();
  115.         $this->arrEvents = array();
  116.         $time time();
  117.         // Get the upcoming events
  118.         $objArticle CalendarEventsModel::findUpcomingByPids($arrCalendars$arrFeed['maxItems']);
  119.         // Parse the items
  120.         if ($objArticle !== null)
  121.         {
  122.             while ($objArticle->next())
  123.             {
  124.                 // Never add unpublished elements to the RSS feeds
  125.                 if (!$objArticle->published || ($objArticle->start && $objArticle->start $time) || ($objArticle->stop && $objArticle->stop <= $time))
  126.                 {
  127.                     continue;
  128.                 }
  129.                 $jumpTo $objArticle->getRelated('pid')->jumpTo;
  130.                 // No jumpTo page set (see #4784)
  131.                 if (!$jumpTo)
  132.                 {
  133.                     continue;
  134.                 }
  135.                 $objParent $this->getPageWithDetails($jumpTo);
  136.                 // A jumpTo page is set but does no longer exist (see #5781)
  137.                 if ($objParent === null)
  138.                 {
  139.                     continue;
  140.                 }
  141.                 // Get the jumpTo URL
  142.                 if (!isset($arrUrls[$jumpTo]))
  143.                 {
  144.                     $arrUrls[$jumpTo] = $objParent->getAbsoluteUrl(Config::get('useAutoItem') ? '/%s' '/events/%s');
  145.                 }
  146.                 $strUrl $arrUrls[$jumpTo];
  147.                 $this->addEvent($objArticle$objArticle->startTime$objArticle->endTime$strUrl);
  148.                 // Recurring events
  149.                 if ($objArticle->recurring)
  150.                 {
  151.                     $arrRepeat StringUtil::deserialize($objArticle->repeatEach);
  152.                     if (!isset($arrRepeat['unit'], $arrRepeat['value']) || $arrRepeat['value'] < 1)
  153.                     {
  154.                         continue;
  155.                     }
  156.                     $count 0;
  157.                     $intStartTime $objArticle->startTime;
  158.                     $intEndTime $objArticle->endTime;
  159.                     $strtotime '+ ' $arrRepeat['value'] . ' ' $arrRepeat['unit'];
  160.                     // Do not include more than 20 recurrences
  161.                     while ($count++ < 20)
  162.                     {
  163.                         if ($objArticle->recurrences && $count >= $objArticle->recurrences)
  164.                         {
  165.                             break;
  166.                         }
  167.                         $intStartTime strtotime($strtotime$intStartTime);
  168.                         $intEndTime strtotime($strtotime$intEndTime);
  169.                         if ($intStartTime >= $time)
  170.                         {
  171.                             $this->addEvent($objArticle$intStartTime$intEndTime$strUrl''true);
  172.                         }
  173.                     }
  174.                 }
  175.             }
  176.         }
  177.         $count 0;
  178.         ksort($this->arrEvents);
  179.         $container System::getContainer();
  180.         /** @var RequestStack $requestStack */
  181.         $requestStack System::getContainer()->get('request_stack');
  182.         $currentRequest $requestStack->getCurrentRequest();
  183.         $origObjPage $GLOBALS['objPage'] ?? null;
  184.         // Add the feed items
  185.         foreach ($this->arrEvents as $days)
  186.         {
  187.             foreach ($days as $events)
  188.             {
  189.                 foreach ($events as $event)
  190.                 {
  191.                     if ($arrFeed['maxItems'] > && $count++ >= $arrFeed['maxItems'])
  192.                     {
  193.                         break 3;
  194.                     }
  195.                     // Override the global page object (#2946)
  196.                     $GLOBALS['objPage'] = $this->getPageWithDetails(CalendarModel::findByPk($event['pid'])->jumpTo);
  197.                     // Push a new request to the request stack (#3856)
  198.                     $request $this->createSubRequest($event['link'], $currentRequest);
  199.                     $request->attributes->set('_scope''frontend');
  200.                     $requestStack->push($request);
  201.                     $objItem = new FeedItem();
  202.                     $objItem->title $event['title'];
  203.                     $objItem->link $event['link'];
  204.                     $objItem->published $event['tstamp'];
  205.                     $objItem->begin $event['startTime'];
  206.                     $objItem->end $event['endTime'];
  207.                     if ($event['isRepeated'] ?? null)
  208.                     {
  209.                         $objItem->guid $event['link'] . '#' date('Y-m-d'$event['startTime']);
  210.                     }
  211.                     if (($objAuthor UserModel::findById($event['author'])) !== null)
  212.                     {
  213.                         $objItem->author $objAuthor->name;
  214.                     }
  215.                     // Prepare the description
  216.                     if ($arrFeed['source'] == 'source_text')
  217.                     {
  218.                         $strDescription '';
  219.                         $objElement ContentModel::findPublishedByPidAndTable($event['id'], 'tl_calendar_events');
  220.                         if ($objElement !== null)
  221.                         {
  222.                             // Overwrite the request (see #7756)
  223.                             $strRequest Environment::get('request');
  224.                             Environment::set('request'$objItem->link);
  225.                             while ($objElement->next())
  226.                             {
  227.                                 $strDescription .= $this->getContentElement($objElement->current());
  228.                             }
  229.                             Environment::set('request'$strRequest);
  230.                         }
  231.                     }
  232.                     else
  233.                     {
  234.                         $strDescription $event['teaser'] ?? '';
  235.                     }
  236.                     $strDescription System::getContainer()->get('contao.insert_tag.parser')->replaceInline($strDescription);
  237.                     $objItem->description $this->convertRelativeUrls($strDescription$strLink);
  238.                     if (\is_array($event['media:content']))
  239.                     {
  240.                         foreach ($event['media:content'] as $enclosure)
  241.                         {
  242.                             $objItem->addEnclosure($enclosure$strLink'media:content'$arrFeed['imgSize']);
  243.                         }
  244.                     }
  245.                     if (\is_array($event['enclosure']))
  246.                     {
  247.                         foreach ($event['enclosure'] as $enclosure)
  248.                         {
  249.                             $objItem->addEnclosure($enclosure$strLink);
  250.                         }
  251.                     }
  252.                     $objFeed->addItem($objItem);
  253.                     $requestStack->pop();
  254.                 }
  255.             }
  256.         }
  257.         $GLOBALS['objPage'] = $origObjPage;
  258.         $webDir StringUtil::stripRootDir($container->getParameter('contao.web_dir'));
  259.         // Create the file
  260.         File::putContent($webDir '/share/' $strFile '.xml'System::getContainer()->get('contao.insert_tag.parser')->replaceInline($objFeed->$strType()));
  261.     }
  262.     /**
  263.      * Add events to the indexer
  264.      *
  265.      * @param array   $arrPages
  266.      * @param integer $intRoot
  267.      * @param boolean $blnIsSitemap
  268.      *
  269.      * @return array
  270.      */
  271.     public function getSearchablePages($arrPages$intRoot=0$blnIsSitemap=false)
  272.     {
  273.         $arrRoot = array();
  274.         if ($intRoot 0)
  275.         {
  276.             $arrRoot $this->Database->getChildRecords($intRoot'tl_page');
  277.         }
  278.         $arrProcessed = array();
  279.         $time time();
  280.         // Get all calendars
  281.         $objCalendar CalendarModel::findByProtected('');
  282.         // Walk through each calendar
  283.         if ($objCalendar !== null)
  284.         {
  285.             while ($objCalendar->next())
  286.             {
  287.                 // Skip calendars without target page
  288.                 if (!$objCalendar->jumpTo)
  289.                 {
  290.                     continue;
  291.                 }
  292.                 // Skip calendars outside the root nodes
  293.                 if (!empty($arrRoot) && !\in_array($objCalendar->jumpTo$arrRoot))
  294.                 {
  295.                     continue;
  296.                 }
  297.                 // Get the URL of the jumpTo page
  298.                 if (!isset($arrProcessed[$objCalendar->jumpTo]))
  299.                 {
  300.                     $objParent PageModel::findWithDetails($objCalendar->jumpTo);
  301.                     // The target page does not exist
  302.                     if ($objParent === null)
  303.                     {
  304.                         continue;
  305.                     }
  306.                     // The target page has not been published (see #5520)
  307.                     if (!$objParent->published || ($objParent->start && $objParent->start $time) || ($objParent->stop && $objParent->stop <= $time))
  308.                     {
  309.                         continue;
  310.                     }
  311.                     if ($blnIsSitemap)
  312.                     {
  313.                         // The target page is protected (see #8416)
  314.                         if ($objParent->protected)
  315.                         {
  316.                             continue;
  317.                         }
  318.                         // The target page is exempt from the sitemap (see #6418)
  319.                         if ($objParent->robots == 'noindex,nofollow')
  320.                         {
  321.                             continue;
  322.                         }
  323.                     }
  324.                     // Generate the URL
  325.                     $arrProcessed[$objCalendar->jumpTo] = $objParent->getAbsoluteUrl(Config::get('useAutoItem') ? '/%s' '/events/%s');
  326.                 }
  327.                 $strUrl $arrProcessed[$objCalendar->jumpTo];
  328.                 // Get the items
  329.                 $objEvents CalendarEventsModel::findPublishedDefaultByPid($objCalendar->id);
  330.                 if ($objEvents !== null)
  331.                 {
  332.                     while ($objEvents->next())
  333.                     {
  334.                         if ($blnIsSitemap && $objEvents->robots === 'noindex,nofollow')
  335.                         {
  336.                             continue;
  337.                         }
  338.                         $arrPages[] = sprintf(preg_replace('/%(?!s)/''%%'$strUrl), ($objEvents->alias ?: $objEvents->id));
  339.                     }
  340.                 }
  341.             }
  342.         }
  343.         return $arrPages;
  344.     }
  345.     /**
  346.      * Add an event to the array of active events
  347.      *
  348.      * @param CalendarEventsModel $objEvent
  349.      * @param integer             $intStart
  350.      * @param integer             $intEnd
  351.      * @param string              $strUrl
  352.      * @param string              $strBase
  353.      * @param boolean             $isRepeated
  354.      */
  355.     protected function addEvent($objEvent$intStart$intEnd$strUrl$strBase=''$isRepeated=false)
  356.     {
  357.         if ($intEnd time())
  358.         {
  359.             return; // see #3917
  360.         }
  361.         $intKey date('Ymd'$intStart);
  362.         $span self::calculateSpan($intStart$intEnd);
  363.         $format $objEvent->addTime 'datimFormat' 'dateFormat';
  364.         /** @var PageModel $objPage */
  365.         global $objPage;
  366.         if ($objPage instanceof PageModel)
  367.         {
  368.             $date $objPage->$format;
  369.             $dateFormat $objPage->dateFormat;
  370.             $timeFormat $objPage->timeFormat;
  371.         }
  372.         else
  373.         {
  374.             // Called in the back end (see #4026)
  375.             $date Config::get($format);
  376.             $dateFormat Config::get('dateFormat');
  377.             $timeFormat Config::get('timeFormat');
  378.         }
  379.         // Add date
  380.         if ($span 0)
  381.         {
  382.             $title Date::parse($date$intStart) . $GLOBALS['TL_LANG']['MSC']['cal_timeSeparator'] . Date::parse($date$intEnd);
  383.         }
  384.         else
  385.         {
  386.             $title Date::parse($dateFormat$intStart) . ($objEvent->addTime ' (' Date::parse($timeFormat$intStart) . (($intStart $intEnd) ? $GLOBALS['TL_LANG']['MSC']['cal_timeSeparator'] . Date::parse($timeFormat$intEnd) : '') . ')' '');
  387.         }
  388.         // Add title and link
  389.         $title .= ' ' $objEvent->title;
  390.         // Backwards compatibility (see #8329)
  391.         if ($strBase && !preg_match('#^https?://#'$strUrl))
  392.         {
  393.             $strUrl $strBase $strUrl;
  394.         }
  395.         $link '';
  396.         switch ($objEvent->source)
  397.         {
  398.             case 'external':
  399.                 $link $objEvent->url;
  400.                 break;
  401.             case 'internal':
  402.                 if (($objTarget $objEvent->getRelated('jumpTo')) instanceof PageModel)
  403.                 {
  404.                     /** @var PageModel $objTarget */
  405.                     $link $objTarget->getAbsoluteUrl();
  406.                 }
  407.                 break;
  408.             case 'article':
  409.                 if (($objArticle ArticleModel::findByPk($objEvent->articleId)) instanceof ArticleModel && ($objPid $objArticle->getRelated('pid')) instanceof PageModel)
  410.                 {
  411.                     /** @var PageModel $objPid */
  412.                     $link StringUtil::ampersand($objPid->getAbsoluteUrl('/articles/' . ($objArticle->alias ?: $objArticle->id)));
  413.                 }
  414.                 break;
  415.             default:
  416.                 $link sprintf(preg_replace('/%(?!s)/''%%'$strUrl), ($objEvent->alias ?: $objEvent->id));
  417.                 break;
  418.         }
  419.         // Store the whole row (see #5085)
  420.         $arrEvent $objEvent->row();
  421.         // Override link and title
  422.         $arrEvent['link'] = $link;
  423.         $arrEvent['title'] = $title;
  424.         // Set the current start and end date
  425.         $arrEvent['startDate'] = $intStart;
  426.         $arrEvent['endDate'] = $intEnd;
  427.         $arrEvent['isRepeated'] = $isRepeated;
  428.         // Reset the enclosures (see #5685)
  429.         $arrEvent['enclosure'] = array();
  430.         $arrEvent['media:content'] = array();
  431.         // Add the article image as enclosure
  432.         if ($objEvent->addImage)
  433.         {
  434.             $objFile FilesModel::findByUuid($objEvent->singleSRC);
  435.             if ($objFile !== null)
  436.             {
  437.                 $arrEvent['media:content'][] = $objFile->path;
  438.             }
  439.         }
  440.         // Enclosures
  441.         if ($objEvent->addEnclosure)
  442.         {
  443.             $arrEnclosure StringUtil::deserialize($objEvent->enclosuretrue);
  444.             if (\is_array($arrEnclosure))
  445.             {
  446.                 $objFile FilesModel::findMultipleByUuids($arrEnclosure);
  447.                 if ($objFile !== null)
  448.                 {
  449.                     while ($objFile->next())
  450.                     {
  451.                         $arrEvent['enclosure'][] = $objFile->path;
  452.                     }
  453.                 }
  454.             }
  455.         }
  456.         $this->arrEvents[$intKey][$intStart][] = $arrEvent;
  457.     }
  458.     /**
  459.      * Calculate the span between two timestamps in days
  460.      *
  461.      * @param integer $intStart
  462.      * @param integer $intEnd
  463.      *
  464.      * @return integer
  465.      */
  466.     public static function calculateSpan($intStart$intEnd)
  467.     {
  468.         return self::unixToJd($intEnd) - self::unixToJd($intStart);
  469.     }
  470.     /**
  471.      * Convert a UNIX timestamp to a Julian day
  472.      *
  473.      * @param integer $tstamp
  474.      *
  475.      * @return integer
  476.      */
  477.     public static function unixToJd($tstamp)
  478.     {
  479.         list($year$month$day) = explode(','date('Y,m,d'$tstamp));
  480.         // Make year a positive number
  481.         $year += ($year 4801 4800);
  482.         // Adjust the start of the year
  483.         if ($month 2)
  484.         {
  485.             $month -= 3;
  486.         }
  487.         else
  488.         {
  489.             $month += 9;
  490.             --$year;
  491.         }
  492.         $sdn  floor((floor($year 100) * 146097) / 4);
  493.         $sdn += floor((($year 100) * 1461) / 4);
  494.         $sdn += floor(($month 153 2) / 5);
  495.         $sdn += $day 32045;
  496.         return $sdn;
  497.     }
  498.     /**
  499.      * Return the names of the existing feeds so they are not removed
  500.      *
  501.      * @return array
  502.      */
  503.     public function purgeOldFeeds()
  504.     {
  505.         $arrFeeds = array();
  506.         $objFeeds CalendarFeedModel::findAll();
  507.         if ($objFeeds !== null)
  508.         {
  509.             while ($objFeeds->next())
  510.             {
  511.                 $arrFeeds[] = $objFeeds->alias ?: 'calendar' $objFeeds->id;
  512.             }
  513.         }
  514.         return $arrFeeds;
  515.     }
  516.     /**
  517.      * Return the page object with loaded details for the given page ID
  518.      *
  519.      * @param  integer        $intPageId
  520.      * @return PageModel|null
  521.      */
  522.     private function getPageWithDetails($intPageId)
  523.     {
  524.         if (!isset(self::$arrPageCache[$intPageId]))
  525.         {
  526.             self::$arrPageCache[$intPageId] = PageModel::findWithDetails($intPageId);
  527.         }
  528.         return self::$arrPageCache[$intPageId];
  529.     }
  530.     /**
  531.      * Creates a sub request for the given URI.
  532.      */
  533.     private function createSubRequest(string $uriRequest $request null): Request
  534.     {
  535.         $cookies null !== $request $request->cookies->all() : array();
  536.         $server null !== $request $request->server->all() : array();
  537.         unset($server['HTTP_IF_MODIFIED_SINCE'], $server['HTTP_IF_NONE_MATCH']);
  538.         $subRequest Request::create($uri'get', array(), $cookies, array(), $server);
  539.         if (null !== $request)
  540.         {
  541.             if ($request->get('_format'))
  542.             {
  543.                 $subRequest->attributes->set('_format'$request->get('_format'));
  544.             }
  545.             if ($request->getDefaultLocale() !== $request->getLocale())
  546.             {
  547.                 $subRequest->setLocale($request->getLocale());
  548.             }
  549.         }
  550.         // Always set a session (#3856)
  551.         $subRequest->setSession(new Session(new MockArraySessionStorage()));
  552.         return $subRequest;
  553.     }
  554. }
  555. class_alias(Calendar::class, 'Calendar');