vendor/contao/core-bundle/src/Resources/contao/modules/ModuleBreadcrumb.php line 53

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\Routing\Exception\ExceptionInterface;
  11. /**
  12. * Front end module "breadcrumb".
  13. */
  14. class ModuleBreadcrumb extends Module
  15. {
  16. /**
  17. * Template
  18. * @var string
  19. */
  20. protected $strTemplate = 'mod_breadcrumb';
  21. /**
  22. * Display a wildcard in the back end
  23. *
  24. * @return string
  25. */
  26. public function generate()
  27. {
  28. $request = System::getContainer()->get('request_stack')->getCurrentRequest();
  29. if ($request && System::getContainer()->get('contao.routing.scope_matcher')->isBackendRequest($request))
  30. {
  31. $objTemplate = new BackendTemplate('be_wildcard');
  32. $objTemplate->wildcard = '### ' . $GLOBALS['TL_LANG']['FMD']['breadcrumb'][0] . ' ###';
  33. $objTemplate->title = $this->headline;
  34. $objTemplate->id = $this->id;
  35. $objTemplate->link = $this->name;
  36. $objTemplate->href = StringUtil::specialcharsUrl(System::getContainer()->get('router')->generate('contao_backend', array('do'=>'themes', 'table'=>'tl_module', 'act'=>'edit', 'id'=>$this->id)));
  37. return $objTemplate->parse();
  38. }
  39. return parent::generate();
  40. }
  41. /**
  42. * Generate the module
  43. */
  44. protected function compile()
  45. {
  46. /** @var PageModel $objPage */
  47. global $objPage;
  48. $type = null;
  49. $pageId = $objPage->id;
  50. $pages = array($objPage);
  51. $items = array();
  52. $blnShowUnpublished = System::getContainer()->get('contao.security.token_checker')->isPreviewMode();
  53. // Get all pages up to the root page
  54. $objPages = PageModel::findParentsById($objPage->pid);
  55. if ($objPages !== null)
  56. {
  57. while ($pageId > 0 && $type != 'root' && $objPages->next())
  58. {
  59. $type = $objPages->type;
  60. $pageId = $objPages->pid;
  61. $pages[] = $objPages->current();
  62. }
  63. }
  64. // Get the first active regular page and display it instead of the root page
  65. if ($type == 'root')
  66. {
  67. $objFirstPage = PageModel::findFirstPublishedByPid($objPages->id);
  68. $items[] = array
  69. (
  70. 'isRoot' => true,
  71. 'isActive' => false,
  72. 'href' => (($objFirstPage !== null) ? $this->getPageFrontendUrl($objFirstPage) : Environment::get('base')),
  73. 'title' => StringUtil::specialchars($objPages->pageTitle ?: $objPages->title, true),
  74. 'link' => $objPages->title,
  75. 'data' => (($objFirstPage !== null) ? $objFirstPage->row() : array()),
  76. 'class' => ''
  77. );
  78. array_pop($pages);
  79. }
  80. for ($i=(\count($pages)-1); $i>0; $i--)
  81. {
  82. // Skip pages that require an item (see #3450) and hidden or unpublished pages
  83. if ($pages[$i]->requireItem || ($pages[$i]->hide && !$this->showHidden) || (!$pages[$i]->published && !$blnShowUnpublished))
  84. {
  85. continue;
  86. }
  87. // Get href
  88. switch ($pages[$i]->type)
  89. {
  90. case 'redirect':
  91. $href = $pages[$i]->url;
  92. if (strncasecmp($href, 'mailto:', 7) === 0)
  93. {
  94. $href = StringUtil::encodeEmail($href);
  95. }
  96. break;
  97. case 'forward':
  98. if ($pages[$i]->jumpTo)
  99. {
  100. $objNext = PageModel::findPublishedById($pages[$i]->jumpTo);
  101. }
  102. else
  103. {
  104. $objNext = PageModel::findFirstPublishedRegularByPid($pages[$i]->id);
  105. }
  106. if ($objNext instanceof PageModel)
  107. {
  108. $href = $this->getPageFrontendUrl($objNext);
  109. break;
  110. }
  111. // no break
  112. default:
  113. $href = $this->getPageFrontendUrl($pages[$i]);
  114. break;
  115. }
  116. // Do not add non-root pages with an empty URL to the breadcrumbs
  117. if ($href)
  118. {
  119. $items[] = array
  120. (
  121. 'isRoot' => false,
  122. 'isActive' => false,
  123. 'href' => $href,
  124. 'title' => StringUtil::specialchars($pages[$i]->pageTitle ?: $pages[$i]->title, true),
  125. 'link' => $pages[$i]->title,
  126. 'data' => $pages[$i]->row(),
  127. 'class' => ''
  128. );
  129. }
  130. }
  131. // Only add active article(s) to the breadcrumbs if the current page does not require an item (see #3450)
  132. if (isset($_GET['articles']) && !$pages[0]->requireItem)
  133. {
  134. $items[] = array
  135. (
  136. 'isRoot' => false,
  137. 'isActive' => false,
  138. 'href' => $this->getPageFrontendUrl($pages[0]),
  139. 'title' => StringUtil::specialchars($pages[0]->pageTitle ?: $pages[0]->title, true),
  140. 'link' => $pages[0]->title,
  141. 'data' => $pages[0]->row(),
  142. 'class' => ''
  143. );
  144. list($strSection, $strArticle) = explode(':', Input::get('articles')) + array(null, null);
  145. if ($strArticle === null)
  146. {
  147. $strArticle = $strSection;
  148. }
  149. $objArticle = ArticleModel::findByIdOrAlias($strArticle);
  150. $strAlias = $objArticle->alias ?: $objArticle->id;
  151. if ($objArticle->inColumn != 'main')
  152. {
  153. $strAlias = $objArticle->inColumn . ':' . $strAlias;
  154. }
  155. if ($objArticle !== null)
  156. {
  157. $items[] = array
  158. (
  159. 'isRoot' => false,
  160. 'isActive' => true,
  161. 'href' => $this->getPageFrontendUrl($pages[0], '/articles/' . $strAlias),
  162. 'title' => StringUtil::specialchars($objArticle->title, true),
  163. 'link' => $objArticle->title,
  164. 'data' => $objArticle->row(),
  165. 'class' => ''
  166. );
  167. }
  168. }
  169. // Active page
  170. else
  171. {
  172. $items[] = array
  173. (
  174. 'isRoot' => false,
  175. 'isActive' => true,
  176. // Use the current request without query string for the current page (see #3450)
  177. 'href' => strtok(Environment::get('request'), '?'),
  178. 'title' => StringUtil::specialchars($pages[0]->pageTitle ?: $pages[0]->title),
  179. 'link' => $pages[0]->title,
  180. 'data' => $pages[0]->row(),
  181. 'class' => ''
  182. );
  183. }
  184. // Mark the first element (see #4833)
  185. $items[0]['class'] = 'first';
  186. // HOOK: add custom logic
  187. if (isset($GLOBALS['TL_HOOKS']['generateBreadcrumb']) && \is_array($GLOBALS['TL_HOOKS']['generateBreadcrumb']))
  188. {
  189. foreach ($GLOBALS['TL_HOOKS']['generateBreadcrumb'] as $callback)
  190. {
  191. $this->import($callback[0]);
  192. $items = $this->{$callback[0]}->{$callback[1]}($items, $this);
  193. }
  194. }
  195. $this->Template->getSchemaOrgData = static function () use ($items): array
  196. {
  197. $jsonLd = array(
  198. '@type' => 'BreadcrumbList',
  199. 'itemListElement' => array()
  200. );
  201. $position = 0;
  202. $htmlDecoder = System::getContainer()->get('contao.string.html_decoder');
  203. foreach ($items as $item)
  204. {
  205. $jsonLd['itemListElement'][] = array(
  206. '@type' => 'ListItem',
  207. 'position' => ++$position,
  208. 'item' => array(
  209. '@id' => $item['href'] ?: './',
  210. 'name' => $htmlDecoder->inputEncodedToPlainText($item['link'])
  211. )
  212. );
  213. }
  214. return $jsonLd;
  215. };
  216. $this->Template->items = $items;
  217. // Tag the pages
  218. if (!System::getContainer()->has('fos_http_cache.http.symfony_response_tagger'))
  219. {
  220. return;
  221. }
  222. $tags = array();
  223. foreach ($items as $item)
  224. {
  225. if (isset($item['data']['id']))
  226. {
  227. $tags[] = 'contao.db.tl_page.' . $item['data']['id'];
  228. }
  229. }
  230. if (!empty($tags))
  231. {
  232. $responseTagger = System::getContainer()->get('fos_http_cache.http.symfony_response_tagger');
  233. $responseTagger->addTags($tags);
  234. }
  235. }
  236. private function getPageFrontendUrl(PageModel $pageModel, $strParams=null)
  237. {
  238. try
  239. {
  240. return $pageModel->getFrontendUrl($strParams);
  241. }
  242. catch (ExceptionInterface $exception)
  243. {
  244. return '';
  245. }
  246. }
  247. }
  248. class_alias(ModuleBreadcrumb::class, 'ModuleBreadcrumb');