vendor/madeyourday/contao-rocksolid-custom-elements/src/Element/CustomElement.php line 240

Open in your IDE?
  1. <?php
  2. /*
  3. * Copyright MADE/YOUR/DAY OG <mail@madeyourday.net>
  4. *
  5. * For the full copyright and license information, please view the LICENSE
  6. * file that was distributed with this source code.
  7. */
  8. namespace MadeYourDay\RockSolidCustomElements\Element;
  9. use Contao\ContentElement;
  10. use Contao\ContentModel;
  11. use Contao\Image\PictureConfiguration;
  12. use Contao\Input;
  13. use Contao\ModuleModel;
  14. use Contao\StringUtil;
  15. use Contao\System;
  16. use Contao\Validator;
  17. use MadeYourDay\RockSolidColumns\Element\ColumnsStart;
  18. use MadeYourDay\RockSolidCustomElements\Template\CustomTemplate;
  19. use MadeYourDay\RockSolidCustomElements\CustomElements;
  20. use Symfony\Component\HttpFoundation\Request;
  21. /**
  22. * Custom content element and frontend module
  23. *
  24. * @author Martin Auswöger <martin@madeyourday.net>
  25. */
  26. class CustomElement extends ContentElement
  27. {
  28. /**
  29. * @var string Template
  30. */
  31. protected $strTemplate = 'rsce_default';
  32. /**
  33. * Find the correct template and parse it
  34. *
  35. * @return string Parsed template
  36. */
  37. public function generate()
  38. {
  39. $this->strTemplate = $this->customTpl ?: $this->type;
  40. // Return output for the backend if in BE mode
  41. if (($output = $this->rsceGetBackendOutput()) !== null) {
  42. return $output;
  43. }
  44. try {
  45. return parent::generate();
  46. }
  47. catch (\Exception $exception) {
  48. if (System::getContainer()->get('contao.routing.scope_matcher')->isBackendRequest(System::getContainer()->get('request_stack')->getCurrentRequest() ?? Request::create(''))) {
  49. $template = new CustomTemplate($this->strTemplate);
  50. $template->setData($this->Template->getData());
  51. $this->Template = $template;
  52. return $this->Template->parse();
  53. }
  54. throw $exception;
  55. }
  56. }
  57. /**
  58. * Generate backend output if TL_MODE is set to BE
  59. *
  60. * @return string|null Backend output or null
  61. */
  62. public function rsceGetBackendOutput()
  63. {
  64. if (!System::getContainer()->get('contao.routing.scope_matcher')->isBackendRequest(System::getContainer()->get('request_stack')->getCurrentRequest() ?? Request::create(''))) {
  65. return null;
  66. }
  67. $config = CustomElements::getConfigByType($this->type) ?: array();
  68. // Handle newsletter output the same way as the frontend
  69. if (!empty($config['isNewsletter'])) {
  70. if (Input::get('do') === 'newsletter') {
  71. return null;
  72. }
  73. foreach(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $entry) {
  74. $method = $entry['class'] . '::' . $entry['function'];
  75. if (
  76. $entry['file'] === System::getContainer()->getParameter('kernel.project_dir') . '/system/modules/newsletter/classes/Newsletter.php'
  77. || $entry['file'] === System::getContainer()->getParameter('kernel.project_dir') . '/vendor/contao/newsletter-bundle/src/Resources/contao/classes/Newsletter.php'
  78. || $entry['file'] === System::getContainer()->getParameter('kernel.project_dir') . '/vendor/contao/newsletter-bundle/contao/classes/Newsletter.php'
  79. || $method === 'Contao\\Newsletter::send'
  80. || $method === 'tl_newsletter::listNewsletters'
  81. ) {
  82. return null;
  83. }
  84. }
  85. }
  86. if (!empty($config['beTemplate'])) {
  87. if (!isset($this->arrData['wildcard'])) {
  88. $label = CustomElements::getLabelTranslated($config['label']);
  89. $this->arrData['wildcard'] = '### ' . mb_strtoupper(is_array($label) ? $label[0] : $label) . ' ###';
  90. }
  91. if (!isset($this->arrData['title'])) {
  92. $this->arrData['title'] = $this->headline;
  93. }
  94. if (
  95. !isset($this->arrData['link'])
  96. && !isset($this->arrData['href'])
  97. && $this->objModel instanceof ModuleModel
  98. ) {
  99. $this->arrData['link'] = $this->name;
  100. $this->arrData['href'] = StringUtil::specialcharsUrl(System::getContainer()->get('router')->generate('contao_backend', ['do' => 'themes', 'table' => 'tl_module', 'act' => 'edit', 'id' => $this->id]));
  101. }
  102. $this->strTemplate = $config['beTemplate'];
  103. return null;
  104. }
  105. if (
  106. in_array($this->type, $GLOBALS['TL_WRAPPERS']['start'])
  107. || in_array($this->type, $GLOBALS['TL_WRAPPERS']['stop'])
  108. || in_array($this->type, $GLOBALS['TL_WRAPPERS']['separator'])
  109. ) {
  110. return '';
  111. }
  112. return null;
  113. }
  114. /**
  115. * Parse the json data and pass it to the template
  116. *
  117. * @return void
  118. */
  119. public function compile()
  120. {
  121. // Add an image
  122. if ($this->addImage && trim($this->singleSRC)) {
  123. $figure = System::getContainer()
  124. ->get('contao.image.studio')
  125. ->createFigureBuilder()
  126. ->from($this->singleSRC)
  127. ->setSize(StringUtil::deserialize($this->arrData['size'] ?? null) ?: null)
  128. ->enableLightbox((bool) ($this->arrData['fullsize'] ?? false))
  129. ->setLightboxSize(StringUtil::deserialize($this->arrData['lightboxSize'] ?? null) ?: null)
  130. ->setMetadata((new ContentModel())->setRow($this->arrData)->getOverwriteMetadata())
  131. ->buildIfResourceExists();
  132. if ($figure) {
  133. $figure->applyLegacyTemplateData($this->Template, null, $this->arrData['floating'] ?? null);
  134. }
  135. }
  136. $data = array();
  137. if ($this->rsce_data && substr($this->rsce_data, 0, 1) === '{') {
  138. $data = json_decode($this->rsce_data);
  139. }
  140. $data = $this->deserializeDataRecursive($data);
  141. foreach ($data as $key => $value) {
  142. $this->Template->$key = $value;
  143. }
  144. $self = $this;
  145. $this->Template->getImageObject = function() use($self) {
  146. return call_user_func_array(array($self, 'getImageObject'), func_get_args());
  147. };
  148. $this->Template->getColumnClassName = function() use($self) {
  149. return call_user_func_array(array($self, 'getColumnClassName'), func_get_args());
  150. };
  151. $this->addFragmentControllerDefaults();
  152. }
  153. /**
  154. * Deserialize all data recursively
  155. *
  156. * @param array|object $data data array or object
  157. * @return array|object data passed in with deserialized values
  158. */
  159. protected function deserializeDataRecursive($data)
  160. {
  161. foreach ($data as $key => $value) {
  162. if (is_string($value) && trim($value)) {
  163. if (is_object($data)) {
  164. $data->$key = StringUtil::deserialize($value);
  165. }
  166. else {
  167. $data[$key] = StringUtil::deserialize($value);
  168. }
  169. }
  170. else if (is_array($value) || is_object($value)) {
  171. if (is_object($data)) {
  172. $data->$key = $this->deserializeDataRecursive($value);
  173. }
  174. else {
  175. $data[$key] = $this->deserializeDataRecursive($value);
  176. }
  177. }
  178. }
  179. if ($data instanceof \stdClass) {
  180. $return = new class extends \stdClass{
  181. public function __get($name) {
  182. return null;
  183. }
  184. };
  185. foreach ($data as $key => $value) {
  186. $return->$key = $value;
  187. }
  188. $data = $return;
  189. }
  190. return $data;
  191. }
  192. /**
  193. * Get an image object from id/uuid and an optional size configuration
  194. *
  195. * @param int|string $id ID, UUID string or binary
  196. * @param string|array|PictureConfiguration $size [width, height, mode] optionally serialized or a config object
  197. * @param int $maxSize Gets passed to addImageToTemplate as $intMaxWidth
  198. * @param string $lightboxId Gets passed to addImageToTemplate as $strLightboxId
  199. * @param array $item Gets merged and passed to addImageToTemplate as $arrItem
  200. * @return object Image object (similar as addImageToTemplate)
  201. */
  202. public function getImageObject($id, $size = null, $deprecated = null, $lightboxId = null, $item = array())
  203. {
  204. if (!$id) {
  205. return null;
  206. }
  207. $figure = System::getContainer()
  208. ->get('contao.image.studio')
  209. ->createFigureBuilder()
  210. ->from($id)
  211. ->setSize($size)
  212. ->enableLightbox((bool) ($item['fullsize'] ?? false))
  213. ->setLightboxGroupIdentifier($lightboxId)
  214. ->setLightboxSize(StringUtil::deserialize($item['lightboxSize'] ?? null) ?: null)
  215. ->setMetadata((new ContentModel())->setRow($item)->getOverwriteMetadata())
  216. ->buildIfResourceExists();
  217. if (null === $figure) {
  218. return null;
  219. }
  220. return (object) array_merge($figure->getLegacyTemplateData(), ['figure' => $figure]);
  221. }
  222. /**
  223. * Get the column class name for the specified index
  224. *
  225. * @param int $index Index of the column
  226. * @return string Class name(s)
  227. */
  228. public function getColumnClassName($index)
  229. {
  230. if (!class_exists(ColumnsStart::class)) {
  231. return '';
  232. }
  233. $config = ColumnsStart::getColumnsConfiguration($this->arrData);
  234. $classes = array('rs-column');
  235. foreach ($config as $name => $media) {
  236. $classes = array_merge($classes, $media[$index % count($media)]);
  237. if ($index < count($media)) {
  238. $classes[] = '-' . $name . '-first-row';
  239. }
  240. }
  241. return implode(' ', $classes);
  242. }
  243. private function addFragmentControllerDefaults()
  244. {
  245. $this->Template->template ??= $this->Template->getName();
  246. $this->Template->as_editor_view ??= System::getContainer()->get('contao.routing.scope_matcher')->isBackendRequest(System::getContainer()->get('request_stack')->getCurrentRequest() ?? Request::create(''));
  247. $this->Template->data ??= $this->objModel ? $this->objModel->row() : $this->arrData;
  248. $this->Template->nested_fragments ??= [];
  249. $this->Template->section ??= $this->strColumn;
  250. $this->Template->properties ??= [];
  251. $this->Template->element_html_id ??= $this->Template->cssID[0] ?? null;
  252. $this->Template->element_css_classes ??= trim(($this->Template->cssID[1] ?? '') . ' ' . implode(' ', $this->objModel ? (array) $this->objModel->classes : []));
  253. if (
  254. (!\is_string($this->Template->headline) && $this->Template->headline !== null)
  255. || (!\is_string($this->Template->hl) && $this->Template->hl !== null)
  256. ) {
  257. return;
  258. }
  259. // Legacy templates access the text using `$this->headline`, twig templates use `headline.text`
  260. $this->Template->headline = new class($this->Template->headline, $this->Template->hl) implements \Stringable
  261. {
  262. public ?string $text;
  263. public ?string $tag_name;
  264. public function __construct(?string $text, ?string $tag_name)
  265. {
  266. $this->text = $text;
  267. $this->tag_name = $tag_name;
  268. }
  269. public function __toString(): string
  270. {
  271. return $this->text ?? '';
  272. }
  273. public function __invoke(): string
  274. {
  275. return $this->text ?? '';
  276. }
  277. };
  278. // The parent::generate() method overwrites the template headline with $this->headline
  279. // so we need to set it to the same callable object here
  280. $this->headline = $this->Template->getData()['headline'];
  281. }
  282. }