vendor/contao/news-bundle/src/Resources/contao/modules/ModuleNews.php line 128

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 Contao\CoreBundle\Security\ContaoCorePermissions;
  11. use Contao\Model\Collection;
  12. /**
  13. * Parent class for news modules.
  14. *
  15. * @property string $news_template
  16. * @property mixed $news_metaFields
  17. */
  18. abstract class ModuleNews extends Module
  19. {
  20. /**
  21. * Sort out protected archives
  22. *
  23. * @param array $arrArchives
  24. *
  25. * @return array
  26. */
  27. protected function sortOutProtected($arrArchives)
  28. {
  29. if (empty($arrArchives) || !\is_array($arrArchives))
  30. {
  31. return $arrArchives;
  32. }
  33. $objArchive = NewsArchiveModel::findMultipleByIds($arrArchives);
  34. $arrArchives = array();
  35. if ($objArchive !== null)
  36. {
  37. $security = System::getContainer()->get('security.helper');
  38. while ($objArchive->next())
  39. {
  40. if ($objArchive->protected && !$security->isGranted(ContaoCorePermissions::MEMBER_IN_GROUPS, StringUtil::deserialize($objArchive->groups, true)))
  41. {
  42. continue;
  43. }
  44. $arrArchives[] = $objArchive->id;
  45. }
  46. }
  47. return $arrArchives;
  48. }
  49. /**
  50. * Parse an item and return it as string
  51. *
  52. * @param NewsModel $objArticle
  53. * @param boolean $blnAddArchive
  54. * @param string $strClass
  55. * @param integer $intCount
  56. *
  57. * @return string
  58. */
  59. protected function parseArticle($objArticle, $blnAddArchive=false, $strClass='', $intCount=0)
  60. {
  61. $objTemplate = new FrontendTemplate($this->news_template ?: 'news_latest');
  62. $objTemplate->setData($objArticle->row());
  63. if ($objArticle->cssClass)
  64. {
  65. $strClass = ' ' . $objArticle->cssClass . $strClass;
  66. }
  67. if ($objArticle->featured)
  68. {
  69. $strClass = ' featured' . $strClass;
  70. }
  71. $objTemplate->class = $strClass;
  72. $objTemplate->newsHeadline = $objArticle->headline;
  73. $objTemplate->subHeadline = $objArticle->subheadline;
  74. $objTemplate->hasSubHeadline = $objArticle->subheadline ? true : false;
  75. $objTemplate->linkHeadline = $this->generateLink($objArticle->headline, $objArticle, $blnAddArchive);
  76. $objTemplate->more = $this->generateLink($GLOBALS['TL_LANG']['MSC']['more'], $objArticle, $blnAddArchive, true);
  77. $objTemplate->link = News::generateNewsUrl($objArticle, $blnAddArchive);
  78. $objTemplate->archive = $objArticle->getRelated('pid');
  79. $objTemplate->count = $intCount; // see #5708
  80. $objTemplate->text = '';
  81. $objTemplate->hasText = false;
  82. $objTemplate->hasTeaser = false;
  83. $objTemplate->hasReader = true;
  84. // Clean the RTE output
  85. if ($objArticle->teaser)
  86. {
  87. $objTemplate->hasTeaser = true;
  88. $objTemplate->teaser = $objArticle->teaser;
  89. $objTemplate->teaser = StringUtil::encodeEmail($objTemplate->teaser);
  90. }
  91. // Display the "read more" button for external/article links
  92. if ($objArticle->source != 'default')
  93. {
  94. $objTemplate->text = true;
  95. $objTemplate->hasText = true;
  96. $objTemplate->hasReader = false;
  97. }
  98. // Compile the news text
  99. else
  100. {
  101. $id = $objArticle->id;
  102. $objTemplate->text = Template::once(function () use ($id)
  103. {
  104. $strText = '';
  105. $objElement = ContentModel::findPublishedByPidAndTable($id, 'tl_news');
  106. if ($objElement !== null)
  107. {
  108. while ($objElement->next())
  109. {
  110. $strText .= $this->getContentElement($objElement->current());
  111. }
  112. }
  113. return $strText;
  114. });
  115. $objTemplate->hasText = Template::once(static function () use ($objArticle)
  116. {
  117. return ContentModel::countPublishedByPidAndTable($objArticle->id, 'tl_news') > 0;
  118. });
  119. }
  120. $arrMeta = $this->getMetaFields($objArticle);
  121. // Add the meta information
  122. $objTemplate->date = $arrMeta['date'] ?? null;
  123. $objTemplate->hasMetaFields = !empty($arrMeta);
  124. $objTemplate->numberOfComments = $arrMeta['ccount'] ?? null;
  125. $objTemplate->commentCount = $arrMeta['comments'] ?? null;
  126. $objTemplate->timestamp = $objArticle->date;
  127. $objTemplate->author = $arrMeta['author'] ?? null;
  128. $objTemplate->authorModel = $arrMeta['authorModel'] ?? null;
  129. $objTemplate->datetime = date('Y-m-d\TH:i:sP', $objArticle->date);
  130. $objTemplate->addImage = false;
  131. $objTemplate->addBefore = false;
  132. // Add an image
  133. if ($objArticle->addImage)
  134. {
  135. $imgSize = $objArticle->size ?: null;
  136. // Override the default image size
  137. if ($this->imgSize)
  138. {
  139. $size = StringUtil::deserialize($this->imgSize);
  140. if ($size[0] > 0 || $size[1] > 0 || is_numeric($size[2]) || ($size[2][0] ?? null) === '_')
  141. {
  142. $imgSize = $this->imgSize;
  143. }
  144. }
  145. $figureBuilder = System::getContainer()
  146. ->get('contao.image.studio')
  147. ->createFigureBuilder()
  148. ->from($objArticle->singleSRC)
  149. ->setSize($imgSize)
  150. ->setOverwriteMetadata($objArticle->getOverwriteMetadata())
  151. ->enableLightbox((bool) $objArticle->fullsize);
  152. // If the external link is opened in a new window, open the image link in a new window as well (see #210)
  153. if ('external' === $objTemplate->source && $objTemplate->target)
  154. {
  155. $figureBuilder->setLinkAttribute('target', '_blank');
  156. }
  157. if (null !== ($figure = $figureBuilder->buildIfResourceExists()))
  158. {
  159. // Rebuild with link to the news article if we are not in a
  160. // newsreader and there is no link yet. $intCount will only be
  161. // set by the news list and news archive modules (see #5851).
  162. if ($intCount > 0 && !$figure->getLinkHref())
  163. {
  164. $linkTitle = StringUtil::specialchars(sprintf($GLOBALS['TL_LANG']['MSC']['readMore'], $objArticle->headline), true);
  165. $figure = $figureBuilder
  166. ->setLinkHref($objTemplate->link)
  167. ->setLinkAttribute('title', $linkTitle)
  168. ->build();
  169. }
  170. $figure->applyLegacyTemplateData($objTemplate, $objArticle->imagemargin, $objArticle->floating);
  171. }
  172. }
  173. $objTemplate->enclosure = array();
  174. // Add enclosures
  175. if ($objArticle->addEnclosure)
  176. {
  177. $this->addEnclosuresToTemplate($objTemplate, $objArticle->row());
  178. }
  179. // HOOK: add custom logic
  180. if (isset($GLOBALS['TL_HOOKS']['parseArticles']) && \is_array($GLOBALS['TL_HOOKS']['parseArticles']))
  181. {
  182. foreach ($GLOBALS['TL_HOOKS']['parseArticles'] as $callback)
  183. {
  184. $this->import($callback[0]);
  185. $this->{$callback[0]}->{$callback[1]}($objTemplate, $objArticle->row(), $this);
  186. }
  187. }
  188. // Tag the news (see #2137)
  189. if (System::getContainer()->has('fos_http_cache.http.symfony_response_tagger'))
  190. {
  191. $responseTagger = System::getContainer()->get('fos_http_cache.http.symfony_response_tagger');
  192. $responseTagger->addTags(array('contao.db.tl_news.' . $objArticle->id));
  193. }
  194. // schema.org information
  195. $objTemplate->getSchemaOrgData = static function () use ($objTemplate, $objArticle): array
  196. {
  197. $jsonLd = News::getSchemaOrgData($objArticle);
  198. if ($objTemplate->addImage && $objTemplate->figure)
  199. {
  200. $jsonLd['image'] = $objTemplate->figure->getSchemaOrgData();
  201. }
  202. return $jsonLd;
  203. };
  204. return $objTemplate->parse();
  205. }
  206. /**
  207. * Parse one or more items and return them as array
  208. *
  209. * @param Collection $objArticles
  210. * @param boolean $blnAddArchive
  211. *
  212. * @return array
  213. */
  214. protected function parseArticles($objArticles, $blnAddArchive=false)
  215. {
  216. $limit = $objArticles->count();
  217. if ($limit < 1)
  218. {
  219. return array();
  220. }
  221. $count = 0;
  222. $arrArticles = array();
  223. $uuids = array();
  224. foreach ($objArticles as $objArticle)
  225. {
  226. if ($objArticle->addImage && $objArticle->singleSRC)
  227. {
  228. $uuids[] = $objArticle->singleSRC;
  229. }
  230. }
  231. // Preload all images in one query, so they are loaded into the model registry
  232. FilesModel::findMultipleByUuids($uuids);
  233. foreach ($objArticles as $objArticle)
  234. {
  235. $arrArticles[] = $this->parseArticle($objArticle, $blnAddArchive, ((++$count == 1) ? ' first' : '') . (($count == $limit) ? ' last' : '') . ((($count % 2) == 0) ? ' odd' : ' even'), $count);
  236. }
  237. return $arrArticles;
  238. }
  239. /**
  240. * Return the meta fields of a news article as array
  241. *
  242. * @param NewsModel $objArticle
  243. *
  244. * @return array
  245. */
  246. protected function getMetaFields($objArticle)
  247. {
  248. $meta = StringUtil::deserialize($this->news_metaFields);
  249. if (!\is_array($meta))
  250. {
  251. return array();
  252. }
  253. /** @var PageModel $objPage */
  254. global $objPage;
  255. $return = array();
  256. foreach ($meta as $field)
  257. {
  258. switch ($field)
  259. {
  260. case 'date':
  261. $return['date'] = Date::parse($objPage->datimFormat, $objArticle->date);
  262. break;
  263. case 'author':
  264. /** @var UserModel $objAuthor */
  265. if (($objAuthor = $objArticle->getRelated('author')) instanceof UserModel)
  266. {
  267. $return['author'] = $GLOBALS['TL_LANG']['MSC']['by'] . ' ' . $objAuthor->name;
  268. $return['authorModel'] = $objAuthor;
  269. }
  270. break;
  271. case 'comments':
  272. if ($objArticle->noComments || $objArticle->source != 'default')
  273. {
  274. break;
  275. }
  276. $bundles = System::getContainer()->getParameter('kernel.bundles');
  277. if (!isset($bundles['ContaoCommentsBundle']))
  278. {
  279. break;
  280. }
  281. $intTotal = CommentsModel::countPublishedBySourceAndParent('tl_news', $objArticle->id);
  282. $return['ccount'] = $intTotal;
  283. $return['comments'] = sprintf($GLOBALS['TL_LANG']['MSC']['commentCount'], $intTotal);
  284. break;
  285. }
  286. }
  287. return $return;
  288. }
  289. /**
  290. * Generate a URL and return it as string
  291. *
  292. * @param NewsModel $objItem
  293. * @param boolean $blnAddArchive
  294. *
  295. * @return string
  296. *
  297. * @deprecated Deprecated since Contao 4.1, to be removed in Contao 5.
  298. * Use News::generateNewsUrl() instead.
  299. */
  300. protected function generateNewsUrl($objItem, $blnAddArchive=false)
  301. {
  302. trigger_deprecation('contao/news-bundle', '4.1', 'Using "Contao\ModuleNews::generateNewsUrl()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\News::generateNewsUrl()" instead.');
  303. return News::generateNewsUrl($objItem, $blnAddArchive);
  304. }
  305. /**
  306. * Generate a link and return it as string
  307. *
  308. * @param string $strLink
  309. * @param NewsModel $objArticle
  310. * @param boolean $blnAddArchive
  311. * @param boolean $blnIsReadMore
  312. *
  313. * @return string
  314. */
  315. protected function generateLink($strLink, $objArticle, $blnAddArchive=false, $blnIsReadMore=false)
  316. {
  317. $blnIsInternal = $objArticle->source != 'external';
  318. $strReadMore = $blnIsInternal ? $GLOBALS['TL_LANG']['MSC']['readMore'] : $GLOBALS['TL_LANG']['MSC']['open'];
  319. $strArticleUrl = News::generateNewsUrl($objArticle, $blnAddArchive);
  320. return sprintf(
  321. '<a href="%s" title="%s"%s>%s%s</a>',
  322. $strArticleUrl,
  323. StringUtil::specialchars(sprintf($strReadMore, $blnIsInternal ? $objArticle->headline : $strArticleUrl), true),
  324. ($objArticle->target && !$blnIsInternal ? ' target="_blank" rel="noreferrer noopener"' : ''),
  325. $strLink,
  326. ($blnIsReadMore && $blnIsInternal ? '<span class="invisible"> ' . $objArticle->headline . '</span>' : '')
  327. );
  328. }
  329. }
  330. class_alias(ModuleNews::class, 'ModuleNews');