vendor/contao/core-bundle/src/Resources/contao/elements/ContentGallery.php line 39

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\Exception\PageNotFoundException;
  11. use Contao\Model\Collection;
  12. use Symfony\Component\Filesystem\Path;
  13. /**
  14. * Front end content element "gallery".
  15. */
  16. class ContentGallery extends ContentElement
  17. {
  18. /**
  19. * Files object
  20. * @var Collection|FilesModel
  21. */
  22. protected $objFiles;
  23. /**
  24. * Template
  25. * @var string
  26. */
  27. protected $strTemplate = 'ce_gallery';
  28. /**
  29. * Return if there are no files
  30. *
  31. * @return string
  32. */
  33. public function generate()
  34. {
  35. // Use the home directory of the current user as file source
  36. if ($this->useHomeDir && System::getContainer()->get('contao.security.token_checker')->hasFrontendUser())
  37. {
  38. $this->import(FrontendUser::class, 'User');
  39. if ($this->User->assignDir && $this->User->homeDir)
  40. {
  41. $this->multiSRC = array($this->User->homeDir);
  42. }
  43. }
  44. else
  45. {
  46. $this->multiSRC = StringUtil::deserialize($this->multiSRC);
  47. }
  48. // Return if there are no files
  49. if (empty($this->multiSRC) || !\is_array($this->multiSRC))
  50. {
  51. return '';
  52. }
  53. // Get the file entries from the database
  54. $this->objFiles = FilesModel::findMultipleByUuids($this->multiSRC);
  55. if ($this->objFiles === null)
  56. {
  57. return '';
  58. }
  59. // Make sure we have at least one item per row to prevent division by zero
  60. if ($this->perRow < 1)
  61. {
  62. $this->perRow = 1;
  63. }
  64. return parent::generate();
  65. }
  66. /**
  67. * Generate the content element
  68. */
  69. protected function compile()
  70. {
  71. $images = array();
  72. $projectDir = System::getContainer()->getParameter('kernel.project_dir');
  73. $objFiles = $this->objFiles;
  74. // Get all images
  75. while ($objFiles->next())
  76. {
  77. // Continue if the files has been processed or does not exist
  78. if (isset($images[$objFiles->path]) || !file_exists(Path::join($projectDir, $objFiles->path)))
  79. {
  80. continue;
  81. }
  82. // Single files
  83. if ($objFiles->type == 'file')
  84. {
  85. $objFile = new File($objFiles->path);
  86. if (!$objFile->isImage)
  87. {
  88. continue;
  89. }
  90. $row = $objFiles->row();
  91. $row['mtime'] = $objFile->mtime;
  92. // Add the image
  93. $images[$objFiles->path] = $row;
  94. }
  95. // Folders
  96. else
  97. {
  98. $objSubfiles = FilesModel::findByPid($objFiles->uuid, array('order' => 'name'));
  99. if ($objSubfiles === null)
  100. {
  101. continue;
  102. }
  103. while ($objSubfiles->next())
  104. {
  105. // Skip subfolders and files that do not exist
  106. if ($objSubfiles->type == 'folder' || !file_exists(Path::join($projectDir, $objSubfiles->path)))
  107. {
  108. continue;
  109. }
  110. $objFile = new File($objSubfiles->path);
  111. if (!$objFile->isImage)
  112. {
  113. continue;
  114. }
  115. $row = $objSubfiles->row();
  116. $row['mtime'] = $objFile->mtime;
  117. // Add the image
  118. $images[$objSubfiles->path] = $row;
  119. }
  120. }
  121. }
  122. // Sort array
  123. switch ($this->sortBy)
  124. {
  125. default:
  126. case 'name_asc':
  127. uksort($images, static function ($a, $b): int
  128. {
  129. return strnatcasecmp(basename($a), basename($b));
  130. });
  131. break;
  132. case 'name_desc':
  133. uksort($images, static function ($a, $b): int
  134. {
  135. return -strnatcasecmp(basename($a), basename($b));
  136. });
  137. break;
  138. case 'date_asc':
  139. uasort($images, static function (array $a, array $b)
  140. {
  141. return $a['mtime'] <=> $b['mtime'];
  142. });
  143. break;
  144. case 'date_desc':
  145. uasort($images, static function (array $a, array $b)
  146. {
  147. return $b['mtime'] <=> $a['mtime'];
  148. });
  149. break;
  150. // Deprecated since Contao 4.0, to be removed in Contao 5.0
  151. case 'meta':
  152. trigger_deprecation('contao/core-bundle', '4.0', 'The "meta" key in "Contao\ContentGallery::compile()" has been deprecated and will no longer work in Contao 5.0.');
  153. // no break
  154. case 'custom':
  155. $images = ArrayUtil::sortByOrderField($images, $this->orderSRC);
  156. break;
  157. case 'random':
  158. shuffle($images);
  159. $this->Template->isRandomOrder = true;
  160. break;
  161. }
  162. $images = array_values($images);
  163. // Limit the total number of items (see #2652)
  164. if ($this->numberOfItems > 0)
  165. {
  166. $images = \array_slice($images, 0, $this->numberOfItems);
  167. }
  168. $offset = 0;
  169. $total = \count($images);
  170. $limit = $total;
  171. // Paginate the result of not randomly sorted (see #8033)
  172. if ($this->perPage > 0 && $this->sortBy != 'random')
  173. {
  174. // Get the current page
  175. $id = 'page_g' . $this->id;
  176. $page = (int) (Input::get($id) ?? 1);
  177. // Do not index or cache the page if the page number is outside the range
  178. if ($page < 1 || $page > max(ceil($total/$this->perPage), 1))
  179. {
  180. throw new PageNotFoundException('Page not found: ' . Environment::get('uri'));
  181. }
  182. // Set limit and offset
  183. $offset = ($page - 1) * $this->perPage;
  184. $limit = min($this->perPage + $offset, $total);
  185. $objPagination = new Pagination($total, $this->perPage, Config::get('maxPaginationLinks'), $id);
  186. $this->Template->pagination = $objPagination->generate("\n ");
  187. }
  188. $rowcount = 0;
  189. $colwidth = floor(100/$this->perRow);
  190. $body = array();
  191. $figureBuilder = System::getContainer()
  192. ->get('contao.image.studio')
  193. ->createFigureBuilder()
  194. ->setSize($this->size)
  195. ->setLightboxGroupIdentifier('lb' . $this->id)
  196. ->enableLightbox((bool) $this->fullsize);
  197. // Rows
  198. for ($i=$offset; $i<$limit; $i+=$this->perRow)
  199. {
  200. $class_tr = '';
  201. if ($rowcount == 0)
  202. {
  203. $class_tr .= ' row_first';
  204. }
  205. if (($i + $this->perRow) >= $limit)
  206. {
  207. $class_tr .= ' row_last';
  208. }
  209. $class_eo = (($rowcount % 2) == 0) ? ' even' : ' odd';
  210. // Columns
  211. for ($j=0; $j<$this->perRow; $j++)
  212. {
  213. $class_td = '';
  214. if ($j == 0)
  215. {
  216. $class_td .= ' col_first';
  217. }
  218. if ($j == ($this->perRow - 1))
  219. {
  220. $class_td .= ' col_last';
  221. }
  222. // Image / empty cell
  223. if (($j + $i) < $limit && null !== ($image = $images[$i + $j] ?? null))
  224. {
  225. $figure = $figureBuilder
  226. ->fromId($image['id'])
  227. ->build();
  228. $cellData = $figure->getLegacyTemplateData($this->imagemargin);
  229. $cellData['figure'] = $figure;
  230. }
  231. else
  232. {
  233. $cellData = array('addImage' => false);
  234. }
  235. // Add column width and class
  236. $cellData['colWidth'] = $colwidth . '%';
  237. $cellData['class'] = 'col_' . $j . $class_td;
  238. $body['row_' . $rowcount . $class_tr . $class_eo][$j] = (object) $cellData;
  239. }
  240. ++$rowcount;
  241. }
  242. $request = System::getContainer()->get('request_stack')->getCurrentRequest();
  243. // Always use the default template in the back end
  244. if ($request && System::getContainer()->get('contao.routing.scope_matcher')->isBackendRequest($request))
  245. {
  246. $this->galleryTpl = '';
  247. }
  248. $objTemplate = new FrontendTemplate($this->galleryTpl ?: 'gallery_default');
  249. $objTemplate->setData($this->arrData);
  250. $objTemplate->body = $body;
  251. $objTemplate->headline = $this->headline; // see #1603
  252. $this->Template->images = $objTemplate->parse();
  253. }
  254. }
  255. class_alias(ContentGallery::class, 'ContentGallery');