vendor/contao/news-bundle/src/Resources/contao/classes/News.php line 280

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. /**
  11.  * Provide methods regarding news archives.
  12.  *
  13.  * @author Leo Feyer <https://github.com/leofeyer>
  14.  */
  15. class News extends Frontend
  16. {
  17.     /**
  18.      * URL cache array
  19.      * @var array
  20.      */
  21.     private static $arrUrlCache = array();
  22.     /**
  23.      * Page cache array
  24.      * @var array
  25.      */
  26.     private static $arrPageCache = array();
  27.     /**
  28.      * Update a particular RSS feed
  29.      *
  30.      * @param integer $intId
  31.      */
  32.     public function generateFeed($intId)
  33.     {
  34.         $objFeed NewsFeedModel::findByPk($intId);
  35.         if ($objFeed === null)
  36.         {
  37.             return;
  38.         }
  39.         $objFeed->feedName $objFeed->alias ?: 'news' $objFeed->id;
  40.         // Delete XML file
  41.         if (Input::get('act') == 'delete')
  42.         {
  43.             $this->import(Files::class, 'Files');
  44.             $this->Files->delete($objFeed->feedName '.xml');
  45.         }
  46.         // Update XML file
  47.         else
  48.         {
  49.             $this->generateFiles($objFeed->row());
  50.             $this->log('Generated news feed "' $objFeed->feedName '.xml"'__METHOD__TL_CRON);
  51.         }
  52.     }
  53.     /**
  54.      * Delete old files and generate all feeds
  55.      */
  56.     public function generateFeeds()
  57.     {
  58.         $this->import(Automator::class, 'Automator');
  59.         $this->Automator->purgeXmlFiles();
  60.         $objFeed NewsFeedModel::findAll();
  61.         if ($objFeed !== null)
  62.         {
  63.             while ($objFeed->next())
  64.             {
  65.                 $objFeed->feedName $objFeed->alias ?: 'news' $objFeed->id;
  66.                 $this->generateFiles($objFeed->row());
  67.                 $this->log('Generated news feed "' $objFeed->feedName '.xml"'__METHOD__TL_CRON);
  68.             }
  69.         }
  70.     }
  71.     /**
  72.      * Generate all feeds including a certain archive
  73.      * #
  74.      * @param integer $intId
  75.      */
  76.     public function generateFeedsByArchive($intId)
  77.     {
  78.         $objFeed NewsFeedModel::findByArchive($intId);
  79.         if ($objFeed !== null)
  80.         {
  81.             while ($objFeed->next())
  82.             {
  83.                 $objFeed->feedName $objFeed->alias ?: 'news' $objFeed->id;
  84.                 // Update the XML file
  85.                 $this->generateFiles($objFeed->row());
  86.                 $this->log('Generated news feed "' $objFeed->feedName '.xml"'__METHOD__TL_CRON);
  87.             }
  88.         }
  89.     }
  90.     /**
  91.      * Generate an XML files and save them to the root directory
  92.      *
  93.      * @param array $arrFeed
  94.      */
  95.     protected function generateFiles($arrFeed)
  96.     {
  97.         $arrArchives StringUtil::deserialize($arrFeed['archives']);
  98.         if (empty($arrArchives) || !\is_array($arrArchives))
  99.         {
  100.             return;
  101.         }
  102.         $strType = ($arrFeed['format'] == 'atom') ? 'generateAtom' 'generateRss';
  103.         $strLink $arrFeed['feedBase'] ?: Environment::get('base');
  104.         $strFile $arrFeed['feedName'];
  105.         $objFeed = new Feed($strFile);
  106.         $objFeed->link $strLink;
  107.         $objFeed->title $arrFeed['title'];
  108.         $objFeed->description $arrFeed['description'];
  109.         $objFeed->language $arrFeed['language'];
  110.         $objFeed->published $arrFeed['tstamp'];
  111.         // Get the items
  112.         if ($arrFeed['maxItems'] > 0)
  113.         {
  114.             $objArticle NewsModel::findPublishedByPids($arrArchivesnull$arrFeed['maxItems']);
  115.         }
  116.         else
  117.         {
  118.             $objArticle NewsModel::findPublishedByPids($arrArchives);
  119.         }
  120.         // Parse the items
  121.         if ($objArticle !== null)
  122.         {
  123.             $arrUrls = array();
  124.             $request System::getContainer()->get('request_stack')->getCurrentRequest();
  125.             if ($request)
  126.             {
  127.                 $origScope $request->attributes->get('_scope');
  128.                 $request->attributes->set('_scope''frontend');
  129.             }
  130.             $origObjPage $GLOBALS['objPage'] ?? null;
  131.             while ($objArticle->next())
  132.             {
  133.                 $jumpTo $objArticle->getRelated('pid')->jumpTo;
  134.                 // No jumpTo page set (see #4784)
  135.                 if (!$jumpTo)
  136.                 {
  137.                     continue;
  138.                 }
  139.                 $objParent $this->getPageWithDetails($jumpTo);
  140.                 // A jumpTo page is set but does no longer exist (see #5781)
  141.                 if ($objParent === null)
  142.                 {
  143.                     continue;
  144.                 }
  145.                 // Override the global page object (#2946)
  146.                 $GLOBALS['objPage'] = $objParent;
  147.                 // Get the jumpTo URL
  148.                 if (!isset($arrUrls[$jumpTo]))
  149.                 {
  150.                     $arrUrls[$jumpTo] = $objParent->getAbsoluteUrl(Config::get('useAutoItem') ? '/%s' '/items/%s');
  151.                 }
  152.                 $strUrl $arrUrls[$jumpTo];
  153.                 $objItem = new FeedItem();
  154.                 $objItem->title $objArticle->headline;
  155.                 $objItem->link $this->getLink($objArticle$strUrl);
  156.                 $objItem->published $objArticle->date;
  157.                 /** @var UserModel $objAuthor */
  158.                 if (($objAuthor $objArticle->getRelated('author')) instanceof UserModel)
  159.                 {
  160.                     $objItem->author $objAuthor->name;
  161.                 }
  162.                 // Prepare the description
  163.                 if ($arrFeed['source'] == 'source_text')
  164.                 {
  165.                     $strDescription '';
  166.                     $objElement ContentModel::findPublishedByPidAndTable($objArticle->id'tl_news');
  167.                     if ($objElement !== null)
  168.                     {
  169.                         // Overwrite the request (see #7756)
  170.                         $strRequest Environment::get('request');
  171.                         Environment::set('request'$objItem->link);
  172.                         while ($objElement->next())
  173.                         {
  174.                             $strDescription .= $this->getContentElement($objElement->current());
  175.                         }
  176.                         Environment::set('request'$strRequest);
  177.                     }
  178.                 }
  179.                 else
  180.                 {
  181.                     $strDescription $objArticle->teaser;
  182.                 }
  183.                 $strDescription $this->replaceInsertTags($strDescriptionfalse);
  184.                 $objItem->description $this->convertRelativeUrls($strDescription$strLink);
  185.                 // Add the article image as enclosure
  186.                 if ($objArticle->addImage)
  187.                 {
  188.                     $objFile FilesModel::findByUuid($objArticle->singleSRC);
  189.                     if ($objFile !== null)
  190.                     {
  191.                         $objItem->addEnclosure($objFile->path$strLink'media:content');
  192.                     }
  193.                 }
  194.                 // Enclosures
  195.                 if ($objArticle->addEnclosure)
  196.                 {
  197.                     $arrEnclosure StringUtil::deserialize($objArticle->enclosuretrue);
  198.                     if (\is_array($arrEnclosure))
  199.                     {
  200.                         $objFile FilesModel::findMultipleByUuids($arrEnclosure);
  201.                         if ($objFile !== null)
  202.                         {
  203.                             while ($objFile->next())
  204.                             {
  205.                                 $objItem->addEnclosure($objFile->path$strLink);
  206.                             }
  207.                         }
  208.                     }
  209.                 }
  210.                 $objFeed->addItem($objItem);
  211.             }
  212.             if ($request)
  213.             {
  214.                 $request->attributes->set('_scope'$origScope);
  215.             }
  216.             $GLOBALS['objPage'] = $origObjPage;
  217.         }
  218.         $webDir StringUtil::stripRootDir(System::getContainer()->getParameter('contao.web_dir'));
  219.         // Create the file
  220.         File::putContent($webDir '/share/' $strFile '.xml'$this->replaceInsertTags($objFeed->$strType(), false));
  221.     }
  222.     /**
  223.      * Add news items to the indexer
  224.      *
  225.      * @param array   $arrPages
  226.      * @param integer $intRoot
  227.      * @param boolean $blnIsSitemap
  228.      *
  229.      * @return array
  230.      */
  231.     public function getSearchablePages($arrPages$intRoot=0$blnIsSitemap=false)
  232.     {
  233.         $arrRoot = array();
  234.         if ($intRoot 0)
  235.         {
  236.             $arrRoot $this->Database->getChildRecords($intRoot'tl_page');
  237.         }
  238.         $arrProcessed = array();
  239.         $time time();
  240.         // Get all news archives
  241.         $objArchive NewsArchiveModel::findByProtected('');
  242.         // Walk through each archive
  243.         if ($objArchive !== null)
  244.         {
  245.             while ($objArchive->next())
  246.             {
  247.                 // Skip news archives without target page
  248.                 if (!$objArchive->jumpTo)
  249.                 {
  250.                     continue;
  251.                 }
  252.                 // Skip news archives outside the root nodes
  253.                 if (!empty($arrRoot) && !\in_array($objArchive->jumpTo$arrRoot))
  254.                 {
  255.                     continue;
  256.                 }
  257.                 // Get the URL of the jumpTo page
  258.                 if (!isset($arrProcessed[$objArchive->jumpTo]))
  259.                 {
  260.                     $objParent PageModel::findWithDetails($objArchive->jumpTo);
  261.                     // The target page does not exist
  262.                     if ($objParent === null)
  263.                     {
  264.                         continue;
  265.                     }
  266.                     // The target page has not been published (see #5520)
  267.                     if (!$objParent->published || ($objParent->start && $objParent->start $time) || ($objParent->stop && $objParent->stop <= $time))
  268.                     {
  269.                         continue;
  270.                     }
  271.                     if ($blnIsSitemap)
  272.                     {
  273.                         // The target page is protected (see #8416)
  274.                         if ($objParent->protected)
  275.                         {
  276.                             continue;
  277.                         }
  278.                         // The target page is exempt from the sitemap (see #6418)
  279.                         if ($objParent->robots == 'noindex,nofollow')
  280.                         {
  281.                             continue;
  282.                         }
  283.                     }
  284.                     // Generate the URL
  285.                     $arrProcessed[$objArchive->jumpTo] = $objParent->getAbsoluteUrl(Config::get('useAutoItem') ? '/%s' '/items/%s');
  286.                 }
  287.                 $strUrl $arrProcessed[$objArchive->jumpTo];
  288.                 // Get the items
  289.                 $objArticle NewsModel::findPublishedDefaultByPid($objArchive->id);
  290.                 if ($objArticle !== null)
  291.                 {
  292.                     while ($objArticle->next())
  293.                     {
  294.                         if ($blnIsSitemap && $objArticle->robots === 'noindex,nofollow')
  295.                         {
  296.                             continue;
  297.                         }
  298.                         $arrPages[] = $this->getLink($objArticle$strUrl);
  299.                     }
  300.                 }
  301.             }
  302.         }
  303.         return $arrPages;
  304.     }
  305.     /**
  306.      * Generate a URL and return it as string
  307.      *
  308.      * @param NewsModel $objItem
  309.      * @param boolean   $blnAddArchive
  310.      * @param boolean   $blnAbsolute
  311.      *
  312.      * @return string
  313.      */
  314.     public static function generateNewsUrl($objItem$blnAddArchive=false$blnAbsolute=false)
  315.     {
  316.         $strCacheKey 'id_' $objItem->id . ($blnAbsolute '_absolute' '');
  317.         // Load the URL from cache
  318.         if (isset(self::$arrUrlCache[$strCacheKey]))
  319.         {
  320.             return self::$arrUrlCache[$strCacheKey];
  321.         }
  322.         // Initialize the cache
  323.         self::$arrUrlCache[$strCacheKey] = null;
  324.         switch ($objItem->source)
  325.         {
  326.             // Link to an external page
  327.             case 'external':
  328.                 if (=== strncmp($objItem->url'mailto:'7))
  329.                 {
  330.                     self::$arrUrlCache[$strCacheKey] = StringUtil::encodeEmail($objItem->url);
  331.                 }
  332.                 else
  333.                 {
  334.                     self::$arrUrlCache[$strCacheKey] = StringUtil::ampersand($objItem->url);
  335.                 }
  336.                 break;
  337.             // Link to an internal page
  338.             case 'internal':
  339.                 if (($objTarget $objItem->getRelated('jumpTo')) instanceof PageModel)
  340.                 {
  341.                     /** @var PageModel $objTarget */
  342.                     self::$arrUrlCache[$strCacheKey] = StringUtil::ampersand($blnAbsolute $objTarget->getAbsoluteUrl() : $objTarget->getFrontendUrl());
  343.                 }
  344.                 break;
  345.             // Link to an article
  346.             case 'article':
  347.                 if (($objArticle ArticleModel::findByPk($objItem->articleId)) instanceof ArticleModel && ($objPid $objArticle->getRelated('pid')) instanceof PageModel)
  348.                 {
  349.                     $params '/articles/' . ($objArticle->alias ?: $objArticle->id);
  350.                     /** @var PageModel $objPid */
  351.                     self::$arrUrlCache[$strCacheKey] = StringUtil::ampersand($blnAbsolute $objPid->getAbsoluteUrl($params) : $objPid->getFrontendUrl($params));
  352.                 }
  353.                 break;
  354.         }
  355.         // Link to the default page
  356.         if (self::$arrUrlCache[$strCacheKey] === null)
  357.         {
  358.             $objPage PageModel::findByPk($objItem->getRelated('pid')->jumpTo);
  359.             if (!$objPage instanceof PageModel)
  360.             {
  361.                 self::$arrUrlCache[$strCacheKey] = StringUtil::ampersand(Environment::get('request'));
  362.             }
  363.             else
  364.             {
  365.                 $params = (Config::get('useAutoItem') ? '/' '/items/') . ($objItem->alias ?: $objItem->id);
  366.                 self::$arrUrlCache[$strCacheKey] = StringUtil::ampersand($blnAbsolute $objPage->getAbsoluteUrl($params) : $objPage->getFrontendUrl($params));
  367.             }
  368.             // Add the current archive parameter (news archive)
  369.             if ($blnAddArchive && Input::get('month'))
  370.             {
  371.                 self::$arrUrlCache[$strCacheKey] .= '?month=' Input::get('month');
  372.             }
  373.         }
  374.         return self::$arrUrlCache[$strCacheKey];
  375.     }
  376.     /**
  377.      * Return the schema.org data from a news article
  378.      *
  379.      * @param NewsModel $objArticle
  380.      *
  381.      * @return array
  382.      */
  383.     public static function getSchemaOrgData(NewsModel $objArticle): array
  384.     {
  385.         $jsonLd = array(
  386.             '@type' => 'NewsArticle',
  387.             'identifier' => '#/schema/news/' $objArticle->id,
  388.             'url' => self::generateNewsUrl($objArticle),
  389.             'headline' => StringUtil::inputEncodedToPlainText($objArticle->headline),
  390.             'datePublished' => date('Y-m-d\TH:i:sP'$objArticle->date),
  391.         );
  392.         if ($objArticle->teaser)
  393.         {
  394.             $jsonLd['description'] = StringUtil::htmlToPlainText($objArticle->teaser);
  395.         }
  396.         /** @var UserModel $objAuthor */
  397.         if (($objAuthor $objArticle->getRelated('author')) instanceof UserModel)
  398.         {
  399.             $jsonLd['author'] = array(
  400.                 '@type' => 'Person',
  401.                 'name' => $objAuthor->name,
  402.             );
  403.         }
  404.         return $jsonLd;
  405.     }
  406.     /**
  407.      * Return the link of a news article
  408.      *
  409.      * @param NewsModel $objItem
  410.      * @param string    $strUrl
  411.      * @param string    $strBase
  412.      *
  413.      * @return string
  414.      */
  415.     protected function getLink($objItem$strUrl$strBase='')
  416.     {
  417.         switch ($objItem->source)
  418.         {
  419.             // Link to an external page
  420.             case 'external':
  421.                 return $objItem->url;
  422.             // Link to an internal page
  423.             case 'internal':
  424.                 if (($objTarget $objItem->getRelated('jumpTo')) instanceof PageModel)
  425.                 {
  426.                     /** @var PageModel $objTarget */
  427.                     return $objTarget->getAbsoluteUrl();
  428.                 }
  429.                 break;
  430.             // Link to an article
  431.             case 'article':
  432.                 if (($objArticle ArticleModel::findByPk($objItem->articleId)) instanceof ArticleModel && ($objPid $objArticle->getRelated('pid')) instanceof PageModel)
  433.                 {
  434.                     /** @var PageModel $objPid */
  435.                     return StringUtil::ampersand($objPid->getAbsoluteUrl('/articles/' . ($objArticle->alias ?: $objArticle->id)));
  436.                 }
  437.                 break;
  438.         }
  439.         // Backwards compatibility (see #8329)
  440.         if ($strBase && !preg_match('#^https?://#'$strUrl))
  441.         {
  442.             $strUrl $strBase $strUrl;
  443.         }
  444.         // Link to the default page
  445.         return sprintf(preg_replace('/%(?!s)/''%%'$strUrl), ($objItem->alias ?: $objItem->id));
  446.     }
  447.     /**
  448.      * Return the names of the existing feeds so they are not removed
  449.      *
  450.      * @return array
  451.      */
  452.     public function purgeOldFeeds()
  453.     {
  454.         $arrFeeds = array();
  455.         $objFeeds NewsFeedModel::findAll();
  456.         if ($objFeeds !== null)
  457.         {
  458.             while ($objFeeds->next())
  459.             {
  460.                 $arrFeeds[] = $objFeeds->alias ?: 'news' $objFeeds->id;
  461.             }
  462.         }
  463.         return $arrFeeds;
  464.     }
  465.     /**
  466.      * Return the page object with loaded details for the given page ID
  467.      *
  468.      * @param  integer        $intPageId
  469.      * @return PageModel|null
  470.      */
  471.     private function getPageWithDetails($intPageId)
  472.     {
  473.         if (!isset(self::$arrPageCache[$intPageId]))
  474.         {
  475.             self::$arrPageCache[$intPageId] = PageModel::findWithDetails($intPageId);
  476.         }
  477.         return self::$arrPageCache[$intPageId];
  478.     }
  479. }
  480. class_alias(News::class, 'News');