vendor/contao/core-bundle/src/Resources/contao/modules/ModuleArticle.php line 70

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\Routing\ResponseContext\HtmlHeadBag\HtmlHeadBag;
  11. /**
  12. * Provides methods to handle articles.
  13. *
  14. * @property integer $tstamp
  15. * @property string $title
  16. * @property string $alias
  17. * @property string $inColumn
  18. * @property boolean $showTeaser
  19. * @property boolean $multiMode
  20. * @property string $teaser
  21. * @property string $teaserCssID
  22. * @property string $classes
  23. * @property string $keywords
  24. * @property boolean $printable
  25. * @property boolean $published
  26. * @property integer $start
  27. * @property integer $stop
  28. */
  29. class ModuleArticle extends Module
  30. {
  31. /**
  32. * Template
  33. * @var string
  34. */
  35. protected $strTemplate = 'mod_article';
  36. /**
  37. * No markup
  38. * @var boolean
  39. */
  40. protected $blnNoMarkup = false;
  41. /**
  42. * Check whether the article is published
  43. *
  44. * @param boolean $blnNoMarkup
  45. *
  46. * @return string
  47. */
  48. public function generate($blnNoMarkup=false)
  49. {
  50. if ($this->isHidden())
  51. {
  52. return '';
  53. }
  54. $this->type = 'article';
  55. $this->blnNoMarkup = $blnNoMarkup;
  56. // Tag the article (see #2137)
  57. if ($this->objModel !== null)
  58. {
  59. System::getContainer()->get('contao.cache.entity_tags')->tagWithModelInstance($this->objModel);
  60. }
  61. return parent::generate();
  62. }
  63. protected function isHidden()
  64. {
  65. $isUnpublished = !$this->published || ($this->start && $this->start > time()) || ($this->stop && $this->stop <= time());
  66. // The article is published, so show it
  67. if (!$isUnpublished)
  68. {
  69. return false;
  70. }
  71. $tokenChecker = System::getContainer()->get('contao.security.token_checker');
  72. // Preview mode is enabled, so show the article
  73. if ($tokenChecker->isPreviewMode())
  74. {
  75. return false;
  76. }
  77. $request = System::getContainer()->get('request_stack')->getCurrentRequest();
  78. // We are in the back end, so show the article
  79. if ($request && System::getContainer()->get('contao.routing.scope_matcher')->isBackendRequest($request))
  80. {
  81. return false;
  82. }
  83. return true;
  84. }
  85. /**
  86. * Generate the module
  87. */
  88. protected function compile()
  89. {
  90. /** @var PageModel $objPage */
  91. global $objPage;
  92. $id = 'article-' . $this->id;
  93. // Generate the CSS ID if it is not set
  94. if (empty($this->cssID[0]))
  95. {
  96. $this->cssID = array($id, $this->cssID[1] ?? null);
  97. }
  98. $this->Template->column = $this->inColumn;
  99. $this->Template->noMarkup = $this->blnNoMarkup;
  100. // Add the modification date
  101. $this->Template->timestamp = $this->tstamp;
  102. $this->Template->date = Date::parse($objPage->datimFormat ?? Config::get('datimFormat'), $this->tstamp);
  103. // Show the teaser only
  104. if ($this->multiMode && $this->showTeaser)
  105. {
  106. $this->cssID = array($id, '');
  107. $arrCss = StringUtil::deserialize($this->teaserCssID);
  108. // Override the CSS ID and class
  109. if (\is_array($arrCss) && \count($arrCss) == 2)
  110. {
  111. if (!$arrCss[0])
  112. {
  113. $arrCss[0] = $id;
  114. }
  115. $this->cssID = $arrCss;
  116. }
  117. $article = $this->alias ?: $this->id;
  118. $href = '/articles/' . (($this->inColumn != 'main') ? $this->inColumn . ':' : '') . $article;
  119. $this->Template->teaserOnly = true;
  120. $this->Template->headline = $this->headline;
  121. $this->Template->href = $objPage->getFrontendUrl($href);
  122. $this->Template->teaser = $this->teaser ?? '';
  123. $this->Template->readMore = StringUtil::specialchars(sprintf($GLOBALS['TL_LANG']['MSC']['readMore'], $this->headline), true);
  124. $this->Template->more = $GLOBALS['TL_LANG']['MSC']['more'];
  125. return;
  126. }
  127. // Get section and article alias
  128. $chunks = explode(':', Input::get('articles') ?? '');
  129. $strSection = $chunks[0] ?? null;
  130. $strArticle = $chunks[1] ?? $strSection;
  131. // Overwrite the page metadata (see #2853, #4955 and #87)
  132. if (!$this->blnNoMarkup && $strArticle && ($strArticle == $this->id || $strArticle == $this->alias) && $this->title)
  133. {
  134. $responseContext = System::getContainer()->get('contao.routing.response_context_accessor')->getResponseContext();
  135. if ($responseContext && $responseContext->has(HtmlHeadBag::class))
  136. {
  137. $htmlDecoder = System::getContainer()->get('contao.string.html_decoder');
  138. /** @var HtmlHeadBag $htmlHeadBag */
  139. $htmlHeadBag = $responseContext->get(HtmlHeadBag::class);
  140. $htmlHeadBag->setTitle($htmlDecoder->inputEncodedToPlainText($this->title ?? ''));
  141. if ($this->teaser)
  142. {
  143. $htmlHeadBag->setMetaDescription($htmlDecoder->htmlToPlainText($this->teaser));
  144. }
  145. }
  146. }
  147. $this->Template->printable = false;
  148. $this->Template->backlink = false;
  149. // Back link
  150. if (!$this->multiMode && $strArticle && ($strArticle == $this->id || $strArticle == $this->alias))
  151. {
  152. $this->Template->backlink = $objPage->getFrontendUrl();
  153. $this->Template->back = StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['goBack']);
  154. }
  155. $arrElements = array();
  156. $objCte = ContentModel::findPublishedByPidAndTable($this->id, 'tl_article');
  157. if ($objCte !== null)
  158. {
  159. while ($objCte->next())
  160. {
  161. $arrElements[] = $this->getContentElement($objCte->current(), $this->strColumn);
  162. }
  163. }
  164. $this->Template->teaser = $this->teaser;
  165. $this->Template->elements = $arrElements;
  166. // Backwards compatibility
  167. if ($this->keywords && isset($GLOBALS['TL_KEYWORDS']))
  168. {
  169. $GLOBALS['TL_KEYWORDS'] .= ($GLOBALS['TL_KEYWORDS'] ? ', ' : '') . $this->keywords;
  170. }
  171. // Deprecated since Contao 4.0, to be removed in Contao 5.0
  172. if ($this->printable == 1)
  173. {
  174. trigger_deprecation('contao/core-bundle', '4.0', 'Setting tl_article.printable to "1" has been deprecated and will no longer work in Contao 5.0.');
  175. $this->Template->printable = !empty($GLOBALS['TL_HOOKS']['printArticleAsPdf']);
  176. $this->Template->pdfButton = $this->Template->printable;
  177. }
  178. // New structure
  179. elseif ($this->printable)
  180. {
  181. $options = StringUtil::deserialize($this->printable);
  182. if (!empty($options) && \is_array($options))
  183. {
  184. // Remove the PDF option if there is no PDF handler (see #417)
  185. if (empty($GLOBALS['TL_HOOKS']['printArticleAsPdf']) && ($key = array_search('pdf', $options)) !== false)
  186. {
  187. unset($options[$key]);
  188. }
  189. if (!empty($options))
  190. {
  191. $this->Template->printable = true;
  192. $this->Template->printButton = \in_array('print', $options);
  193. $this->Template->pdfButton = \in_array('pdf', $options);
  194. $this->Template->facebookButton = \in_array('facebook', $options);
  195. $this->Template->twitterButton = \in_array('twitter', $options);
  196. }
  197. }
  198. }
  199. // Add syndication variables
  200. if ($this->Template->printable)
  201. {
  202. $request = Environment::get('indexFreeRequest');
  203. // URL encoding will be handled by the Symfony router, so do not apply rawurlencode() here anymore
  204. $this->Template->print = '#';
  205. $this->Template->encUrl = Environment::get('base') . Environment::get('request');
  206. $this->Template->encTitle = $objPage->pageTitle;
  207. $this->Template->href = $request . ((strpos($request, '?') !== false) ? '&amp;' : '?') . 'pdf=' . $this->id;
  208. $this->Template->printTitle = StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['printPage']);
  209. $this->Template->pdfTitle = StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['printAsPdf']);
  210. $this->Template->facebookTitle = StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['facebookShare']);
  211. $this->Template->twitterTitle = StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['twitterShare']);
  212. }
  213. // HOOK: add custom logic
  214. if (isset($GLOBALS['TL_HOOKS']['compileArticle']) && \is_array($GLOBALS['TL_HOOKS']['compileArticle']))
  215. {
  216. foreach ($GLOBALS['TL_HOOKS']['compileArticle'] as $callback)
  217. {
  218. $this->import($callback[0]);
  219. $this->{$callback[0]}->{$callback[1]}($this->Template, $this->arrData, $this);
  220. }
  221. }
  222. }
  223. /**
  224. * Print an article as PDF and stream it to the browser
  225. */
  226. public function generatePdf()
  227. {
  228. $this->headline = $this->title;
  229. $this->printable = false;
  230. $container = System::getContainer();
  231. // Generate article
  232. $strArticle = $container->get('contao.insert_tag.parser')->replaceInline($this->generate());
  233. $strArticle = html_entity_decode($strArticle, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, $container->getParameter('kernel.charset'));
  234. $strArticle = $this->convertRelativeUrls($strArticle, '', true);
  235. if (empty($GLOBALS['TL_HOOKS']['printArticleAsPdf']))
  236. {
  237. throw new \Exception('No PDF extension found. Did you forget to install contao/tcpdf-bundle?');
  238. }
  239. trigger_deprecation('contao/core-bundle', '4.13', 'Printing an article as PDF has been deprecated in Contao 4.13 and will be removed in Contao 5.0');
  240. // HOOK: allow individual PDF routines
  241. if (isset($GLOBALS['TL_HOOKS']['printArticleAsPdf']) && \is_array($GLOBALS['TL_HOOKS']['printArticleAsPdf']))
  242. {
  243. foreach ($GLOBALS['TL_HOOKS']['printArticleAsPdf'] as $callback)
  244. {
  245. $this->import($callback[0]);
  246. $this->{$callback[0]}->{$callback[1]}($strArticle, $this);
  247. }
  248. }
  249. }
  250. protected function getResponseCacheTags(): array
  251. {
  252. // Do not tag with 'contao.db.tl_module.<id>' when rendering articles (see #2814)
  253. return array();
  254. }
  255. }
  256. class_alias(ModuleArticle::class, 'ModuleArticle');