PageRenderTime 57ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Renderer/StylesheetRendererSubscriber.php

https://github.com/bit3/contao-theme-plus
PHP | 362 lines | 246 code | 61 blank | 55 comment | 50 complexity | 660a9731445ee14047f61b4002e4a426 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of bit3/contao-theme-plus.
  4. *
  5. * (c) Tristan Lins <tristan.lins@bit3.de>
  6. *
  7. * This project is provided in good faith and hope to be usable by anyone.
  8. *
  9. * @package bit3/contao-theme-plus
  10. * @author Tristan Lins <tristan.lins@bit3.de>
  11. * @copyright bit3 UG <https://bit3.de>
  12. * @link https://github.com/bit3/contao-theme-plus
  13. * @license http://opensource.org/licenses/LGPL-3.0 LGPL-3.0+
  14. * @filesource
  15. */
  16. namespace Bit3\Contao\ThemePlus\Renderer;
  17. use Assetic\Asset\FileAsset;
  18. use Assetic\Asset\HttpAsset;
  19. use Bit3\Contao\ThemePlus\Asset\DelegatorAssetInterface;
  20. use Bit3\Contao\ThemePlus\Asset\ExtendedAssetInterface;
  21. use Bit3\Contao\ThemePlus\DeveloperTool\DeveloperTool;
  22. use Bit3\Contao\ThemePlus\Event\AddStaticDomainEvent;
  23. use Bit3\Contao\ThemePlus\Event\CompileAssetEvent;
  24. use Bit3\Contao\ThemePlus\Event\GenerateAssetPathEvent;
  25. use Bit3\Contao\ThemePlus\Event\RenderAssetHtmlEvent;
  26. use Bit3\Contao\ThemePlus\ThemePlusEvents;
  27. use Bit3\Contao\ThemePlus\ThemePlusUtils;
  28. use DependencyInjection\Container\PageProvider;
  29. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  30. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  31. class StylesheetRendererSubscriber implements EventSubscriberInterface
  32. {
  33. /**
  34. * @var PageProvider
  35. */
  36. private $pageProvider;
  37. /**
  38. * @var DeveloperTool
  39. */
  40. private $developerTool;
  41. public function __construct(PageProvider $pageProvider, DeveloperTool $developerTool)
  42. {
  43. $this->pageProvider = $pageProvider;
  44. $this->developerTool = $developerTool;
  45. }
  46. /**
  47. * {@inheritdoc}
  48. */
  49. public static function getSubscribedEvents()
  50. {
  51. return [
  52. ThemePlusEvents::COMPILE_STYLESHEET => [
  53. ['compileAsset']
  54. ],
  55. ThemePlusEvents::RENDER_STYLESHEET_HTML => [
  56. ['renderDesignerModeHtml'],
  57. ['renderDesignerModeInlineHtml'],
  58. ['renderLinkHtml'],
  59. ['renderInlineHtml'],
  60. ],
  61. ];
  62. }
  63. public function compileAsset(
  64. CompileAssetEvent $event,
  65. $eventName,
  66. EventDispatcherInterface $eventDispatcher
  67. ) {
  68. if (!$event->getTargetPath()) {
  69. $asset = $event->getAsset();
  70. $generateAssetPathEvent = new GenerateAssetPathEvent(
  71. $event->getRenderMode(),
  72. $event->getPage(),
  73. $event->getLayout(),
  74. $asset,
  75. $event->getDefaultFilters(),
  76. 'css'
  77. );
  78. $eventDispatcher->dispatch(ThemePlusEvents::GENERATE_ASSET_PATH, $generateAssetPathEvent);
  79. $targetPath = $generateAssetPathEvent->getPath();
  80. if ($event->isOverwrite() || !file_exists(TL_ROOT . DIRECTORY_SEPARATOR . $targetPath)) {
  81. // overwrite the target path
  82. $asset->setTargetPath($targetPath);
  83. // load and dump the collection
  84. $asset->load($event->getDefaultFilters());
  85. $css = $asset->dump($event->getDefaultFilters());
  86. // write the asset
  87. file_put_contents($targetPath, $css);
  88. }
  89. $event->setTargetPath($targetPath);
  90. }
  91. }
  92. public function renderDesignerModeHtml(
  93. RenderAssetHtmlEvent $event,
  94. $eventName,
  95. EventDispatcherInterface $eventDispatcher
  96. ) {
  97. if (!$event->getHtml() && $event->isDesignerMode()) {
  98. $asset = $event->getAsset();
  99. if (!$asset instanceof ExtendedAssetInterface || !$asset->isInline()) {
  100. $page = $this->pageProvider->getPage();
  101. $html = '';
  102. // html mode
  103. $xhtml = ($page->outputFormat == 'xhtml');
  104. $tagEnding = $xhtml ? ' />' : '>';
  105. // session id
  106. $id = substr(md5($asset->getSourceRoot() . '/' . $asset->getSourcePath()), 0, 8);
  107. // get the session object
  108. $session = unserialize($_SESSION['THEME_PLUS_ASSETS'][$id]);
  109. if (!$session || $asset->getLastModified() > $session->asset->getLastModified()) {
  110. $session = new \stdClass;
  111. $session->page = $page->id;
  112. $session->asset = $asset;
  113. $_SESSION['THEME_PLUS_ASSETS'][$id] = serialize($session);
  114. }
  115. $realAssets = $asset;
  116. while ($realAssets instanceof DelegatorAssetInterface) {
  117. $realAssets = $realAssets->getAsset();
  118. }
  119. if ($realAssets instanceof FileAsset) {
  120. $name = basename($realAssets->getSourcePath());
  121. } else {
  122. if ($realAssets instanceof HttpAsset) {
  123. $class = new \ReflectionClass($realAssets);
  124. $property = $class->getProperty('sourceUrl');
  125. $property->setAccessible(true);
  126. $url = $property->getValue($realAssets);
  127. $name = 'url_' . basename(parse_url($url, PHP_URL_PATH));
  128. } else {
  129. $name = 'asset_' . $id;
  130. }
  131. }
  132. // generate the proxy url
  133. $url = sprintf(
  134. 'assets/theme-plus/proxy.php/css/%s/%s',
  135. $id,
  136. $name
  137. );
  138. // overwrite the target path
  139. $asset->setTargetPath($url);
  140. // remember asset for debug tool
  141. $this->developerTool->registerFile(
  142. $id,
  143. (object) [
  144. 'asset' => $realAssets,
  145. 'type' => 'css',
  146. 'url' => $url,
  147. ]
  148. );
  149. // generate html
  150. $linkHtml = '<link';
  151. $linkHtml .= sprintf(' id="%s"', $id);
  152. $linkHtml .= sprintf(' href="%s"', $url);
  153. if ($xhtml) {
  154. $linkHtml .= ' type="text/css"';
  155. }
  156. $linkHtml .= ' rel="stylesheet"';
  157. if ($asset instanceof ExtendedAssetInterface && $asset->getMediaQuery()) {
  158. $linkHtml .= sprintf(' media="%s"', $asset->getMediaQuery());
  159. }
  160. $linkHtml .= $tagEnding;
  161. // wrap cc around
  162. if ($asset instanceof ExtendedAssetInterface && $asset->getConditionalComment()) {
  163. $linkHtml = ThemePlusUtils::wrapCc($linkHtml, $asset->getConditionalComment());
  164. }
  165. // add debug information
  166. $html .= $this->developerTool->getDebugComment($asset);
  167. $html .= $linkHtml . PHP_EOL;
  168. $event->setHtml($html);
  169. }
  170. }
  171. }
  172. public function renderDesignerModeInlineHtml(
  173. RenderAssetHtmlEvent $event,
  174. $eventName,
  175. EventDispatcherInterface $eventDispatcher
  176. ) {
  177. if (!$event->getHtml() && $event->isDesignerMode()) {
  178. $asset = $event->getAsset();
  179. if ($asset instanceof ExtendedAssetInterface && $asset->isInline()) {
  180. $page = $this->pageProvider->getPage();
  181. $html = '';
  182. // html mode
  183. $xhtml = ($page->outputFormat == 'xhtml');
  184. // retrieve page path
  185. $targetPath = \Environment::get('requestUri');
  186. // remove query string
  187. $targetPath = preg_replace('~\?\.*~', '', $targetPath);
  188. // remove leading /
  189. $targetPath = ltrim($targetPath, '/');
  190. // overwrite the target path
  191. $asset->setTargetPath($targetPath);
  192. // load and dump the collection
  193. $asset->load($event->getDefaultFilters());
  194. $css = $asset->dump($event->getDefaultFilters());
  195. // generate html
  196. $styleHtml = '<style';
  197. if ($xhtml) {
  198. $styleHtml .= ' type="text/css"';
  199. }
  200. if ($asset instanceof ExtendedAssetInterface && $asset->getMediaQuery()) {
  201. $styleHtml .= sprintf(' media="%s"', $asset->getMediaQuery());
  202. }
  203. $styleHtml .= '>';
  204. $styleHtml .= $css;
  205. $styleHtml .= '</style>';
  206. // wrap cc around
  207. if ($asset instanceof ExtendedAssetInterface && $asset->getConditionalComment()) {
  208. $styleHtml = ThemePlusUtils::wrapCc($styleHtml, $asset->getConditionalComment());
  209. }
  210. // add debug information
  211. $html .= $this->developerTools->getDebugComment($asset);
  212. $html .= $styleHtml . PHP_EOL;
  213. $event->setHtml($html);
  214. }
  215. }
  216. }
  217. public function renderLinkHtml(RenderAssetHtmlEvent $event, $eventName, EventDispatcherInterface $eventDispatcher)
  218. {
  219. if (!$event->getHtml() && !$event->isDesignerMode()) {
  220. $asset = $event->getAsset();
  221. if (!$asset instanceof ExtendedAssetInterface || !$asset->isInline()) {
  222. $compileEvent = new CompileAssetEvent(
  223. $event->getRenderMode(),
  224. $event->getPage(),
  225. $event->getLayout(),
  226. $asset,
  227. $event->getDefaultFilters()
  228. );
  229. $eventDispatcher->dispatch(ThemePlusEvents::COMPILE_STYLESHEET, $compileEvent);
  230. $targetPath = $compileEvent->getTargetPath();
  231. $addStaticDomainEvent = new AddStaticDomainEvent(
  232. $event->getRenderMode(),
  233. $event->getPage(),
  234. $event->getLayout(),
  235. $targetPath
  236. );
  237. $eventDispatcher->dispatch(ThemePlusEvents::ADD_STATIC_DOMAIN, $addStaticDomainEvent);
  238. $targetUrl = $addStaticDomainEvent->getUrl();
  239. // html mode
  240. $xhtml = ($event->getPage()->outputFormat == 'xhtml');
  241. $tagEnding = $xhtml ? ' />' : '>';
  242. // generate html
  243. $linkHtml = '<link';
  244. $linkHtml .= sprintf(' href="%s"', $targetUrl);
  245. if ($xhtml) {
  246. $linkHtml .= ' type="text/css"';
  247. }
  248. $linkHtml .= ' rel="stylesheet"';
  249. if ($asset instanceof ExtendedAssetInterface && $asset->getMediaQuery()) {
  250. $linkHtml .= sprintf(' media="%s"', $asset->getMediaQuery());
  251. }
  252. $linkHtml .= $tagEnding;
  253. // wrap cc around
  254. if ($asset instanceof ExtendedAssetInterface && $asset->getConditionalComment()) {
  255. $linkHtml = ThemePlusUtils::wrapCc($linkHtml, $asset->getConditionalComment());
  256. }
  257. $linkHtml .= PHP_EOL;
  258. $event->setHtml($linkHtml);
  259. }
  260. }
  261. }
  262. public function renderInlineHtml(RenderAssetHtmlEvent $event)
  263. {
  264. if (!$event->getHtml() && !$event->isDesignerMode()) {
  265. $asset = $event->getAsset();
  266. if ($asset instanceof ExtendedAssetInterface && $asset->isInline()) {
  267. $page = $this->pageProvider->getPage();
  268. // retrieve page path
  269. $targetPath = \Environment::get('requestUri');
  270. // remove query string
  271. $targetPath = preg_replace('~\?\.*~', '', $targetPath);
  272. // remove leading /
  273. $targetPath = ltrim($targetPath, '/');
  274. // overwrite the target path
  275. $asset->setTargetPath($targetPath);
  276. // load and dump the collection
  277. $asset->load($event->getDefaultFilters());
  278. $css = $asset->dump($event->getDefaultFilters());
  279. // html mode
  280. $xhtml = ($page->outputFormat == 'xhtml');
  281. // generate html
  282. $styleHtml = '<style';
  283. if ($xhtml) {
  284. $styleHtml .= ' type="text/css"';
  285. }
  286. if ($asset instanceof ExtendedAssetInterface && $asset->getMediaQuery()) {
  287. $styleHtml .= sprintf(' media="%s"', $asset->getMediaQuery());
  288. }
  289. $styleHtml .= '>';
  290. $styleHtml .= $css;
  291. $styleHtml .= '</style>';
  292. // wrap cc around
  293. if ($asset instanceof ExtendedAssetInterface && $asset->getConditionalComment()) {
  294. $styleHtml = ThemePlusUtils::wrapCc($styleHtml, $asset->getConditionalComment());
  295. }
  296. $styleHtml .= PHP_EOL;
  297. $event->setHtml($styleHtml);
  298. }
  299. }
  300. }
  301. }