Renderer.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\View\Page\Config;
  7. use Magento\Framework\Exception\LocalizedException;
  8. use Magento\Framework\View\Asset\GroupedCollection;
  9. use Magento\Framework\View\Page\Config;
  10. /**
  11. * Page config Renderer model
  12. *
  13. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  14. */
  15. class Renderer implements RendererInterface
  16. {
  17. /**
  18. * @var array
  19. */
  20. protected $assetTypeOrder = ['css', 'ico', 'js'];
  21. /**
  22. * @var Config
  23. */
  24. protected $pageConfig;
  25. /**
  26. * @var \Magento\Framework\View\Asset\MergeService
  27. */
  28. protected $assetMergeService;
  29. /**
  30. * @var \Magento\Framework\Escaper
  31. */
  32. protected $escaper;
  33. /**
  34. * @var \Magento\Framework\Stdlib\StringUtils
  35. */
  36. protected $string;
  37. /**
  38. * @var \Psr\Log\LoggerInterface
  39. */
  40. protected $logger;
  41. /**
  42. * @var \Magento\Framework\UrlInterface
  43. */
  44. protected $urlBuilder;
  45. /**
  46. * @param Config $pageConfig
  47. * @param \Magento\Framework\View\Asset\MergeService $assetMergeService
  48. * @param \Magento\Framework\UrlInterface $urlBuilder
  49. * @param \Magento\Framework\Escaper $escaper
  50. * @param \Magento\Framework\Stdlib\StringUtils $string
  51. * @param \Psr\Log\LoggerInterface $logger
  52. */
  53. public function __construct(
  54. Config $pageConfig,
  55. \Magento\Framework\View\Asset\MergeService $assetMergeService,
  56. \Magento\Framework\UrlInterface $urlBuilder,
  57. \Magento\Framework\Escaper $escaper,
  58. \Magento\Framework\Stdlib\StringUtils $string,
  59. \Psr\Log\LoggerInterface $logger
  60. ) {
  61. $this->pageConfig = $pageConfig;
  62. $this->assetMergeService = $assetMergeService;
  63. $this->urlBuilder = $urlBuilder;
  64. $this->escaper = $escaper;
  65. $this->string = $string;
  66. $this->logger = $logger;
  67. }
  68. /**
  69. * Render element attributes
  70. *
  71. * @param string $elementType
  72. * @return string
  73. */
  74. public function renderElementAttributes($elementType)
  75. {
  76. $resultAttributes = [];
  77. foreach ($this->pageConfig->getElementAttributes($elementType) as $name => $value) {
  78. $resultAttributes[] = sprintf('%s="%s"', $name, $value);
  79. }
  80. return implode(' ', $resultAttributes);
  81. }
  82. /**
  83. * Render head content
  84. *
  85. * @return string
  86. */
  87. public function renderHeadContent()
  88. {
  89. $result = '';
  90. $result .= $this->renderMetadata();
  91. $result .= $this->renderTitle();
  92. $this->prepareFavicon();
  93. $result .= $this->renderAssets($this->getAvailableResultGroups());
  94. $result .= $this->pageConfig->getIncludes();
  95. return $result;
  96. }
  97. /**
  98. * Render title
  99. *
  100. * @return string
  101. */
  102. public function renderTitle()
  103. {
  104. return '<title>' . $this->escaper->escapeHtml($this->pageConfig->getTitle()->get()) . '</title>' . "\n";
  105. }
  106. /**
  107. * Render metadata
  108. *
  109. * @return string
  110. */
  111. public function renderMetadata()
  112. {
  113. $result = '';
  114. foreach ($this->pageConfig->getMetadata() as $name => $content) {
  115. $metadataTemplate = $this->getMetadataTemplate($name);
  116. if (!$metadataTemplate) {
  117. continue;
  118. }
  119. $content = $this->processMetadataContent($name, $content);
  120. if ($content) {
  121. $result .= str_replace(['%name', '%content'], [$name, $content], $metadataTemplate);
  122. }
  123. }
  124. return $result;
  125. }
  126. /**
  127. * Process metadata content
  128. *
  129. * @param string $name
  130. * @param string $content
  131. * @return mixed
  132. */
  133. protected function processMetadataContent($name, $content)
  134. {
  135. $method = 'get' . $this->string->upperCaseWords($name, '_', '');
  136. if ($name === 'title') {
  137. if (!$content) {
  138. $content = $this->escaper->escapeHtml($this->pageConfig->$method()->get());
  139. }
  140. return $content;
  141. }
  142. if (method_exists($this->pageConfig, $method)) {
  143. $content = $this->pageConfig->$method();
  144. }
  145. return $content;
  146. }
  147. /**
  148. * Returns metadata template
  149. *
  150. * @param string $name
  151. * @return bool|string
  152. */
  153. protected function getMetadataTemplate($name)
  154. {
  155. if (strpos($name, 'og:') === 0) {
  156. return '<meta property="' . $name . '" content="%content"/>' . "\n";
  157. }
  158. switch ($name) {
  159. case Config::META_CHARSET:
  160. $metadataTemplate = '<meta charset="%content"/>' . "\n";
  161. break;
  162. case Config::META_CONTENT_TYPE:
  163. $metadataTemplate = '<meta http-equiv="Content-Type" content="%content"/>' . "\n";
  164. break;
  165. case Config::META_X_UI_COMPATIBLE:
  166. $metadataTemplate = '<meta http-equiv="X-UA-Compatible" content="%content"/>' . "\n";
  167. break;
  168. case Config::META_MEDIA_TYPE:
  169. $metadataTemplate = false;
  170. break;
  171. default:
  172. $metadataTemplate = '<meta name="%name" content="%content"/>' . "\n";
  173. break;
  174. }
  175. return $metadataTemplate;
  176. }
  177. /**
  178. * Favicon preparation
  179. *
  180. * @return void
  181. */
  182. public function prepareFavicon()
  183. {
  184. if ($this->pageConfig->getFaviconFile()) {
  185. $this->pageConfig->addRemotePageAsset(
  186. $this->pageConfig->getFaviconFile(),
  187. Generator\Head::VIRTUAL_CONTENT_TYPE_LINK,
  188. ['attributes' => ['rel' => 'icon', 'type' => 'image/x-icon']],
  189. 'icon'
  190. );
  191. $this->pageConfig->addRemotePageAsset(
  192. $this->pageConfig->getFaviconFile(),
  193. Generator\Head::VIRTUAL_CONTENT_TYPE_LINK,
  194. ['attributes' => ['rel' => 'shortcut icon', 'type' => 'image/x-icon']],
  195. 'shortcut-icon'
  196. );
  197. } else {
  198. $this->pageConfig->addPageAsset(
  199. $this->pageConfig->getDefaultFavicon(),
  200. ['attributes' => ['rel' => 'icon', 'type' => 'image/x-icon']],
  201. 'icon'
  202. );
  203. $this->pageConfig->addPageAsset(
  204. $this->pageConfig->getDefaultFavicon(),
  205. ['attributes' => ['rel' => 'shortcut icon', 'type' => 'image/x-icon']],
  206. 'shortcut-icon'
  207. );
  208. }
  209. }
  210. /**
  211. * Returns rendered HTML for all Assets (CSS before)
  212. *
  213. * @param array $resultGroups
  214. *
  215. * @return string
  216. */
  217. public function renderAssets($resultGroups = [])
  218. {
  219. /** @var $group \Magento\Framework\View\Asset\PropertyGroup */
  220. foreach ($this->pageConfig->getAssetCollection()->getGroups() as $group) {
  221. $type = $group->getProperty(GroupedCollection::PROPERTY_CONTENT_TYPE);
  222. if (!isset($resultGroups[$type])) {
  223. $resultGroups[$type] = '';
  224. }
  225. $resultGroups[$type] .= $this->renderAssetGroup($group);
  226. }
  227. return implode('', $resultGroups);
  228. }
  229. /**
  230. * Returns rendered HTML for an Asset Group
  231. *
  232. * @param \Magento\Framework\View\Asset\PropertyGroup $group
  233. * @return string
  234. */
  235. protected function renderAssetGroup(\Magento\Framework\View\Asset\PropertyGroup $group)
  236. {
  237. $groupHtml = $this->renderAssetHtml($group);
  238. $groupHtml = $this->processIeCondition($groupHtml, $group);
  239. return $groupHtml;
  240. }
  241. /**
  242. * Process assets merge
  243. *
  244. * @param array $groupAssets
  245. * @param \Magento\Framework\View\Asset\PropertyGroup $group
  246. * @return array
  247. */
  248. protected function processMerge($groupAssets, $group)
  249. {
  250. if ($group->getProperty(GroupedCollection::PROPERTY_CAN_MERGE) && count($groupAssets) > 1) {
  251. $groupAssets = $this->assetMergeService->getMergedAssets(
  252. $groupAssets,
  253. $group->getProperty(GroupedCollection::PROPERTY_CONTENT_TYPE)
  254. );
  255. }
  256. return $groupAssets;
  257. }
  258. /**
  259. * Returns group attributes
  260. *
  261. * @param \Magento\Framework\View\Asset\PropertyGroup $group
  262. * @return string|null
  263. */
  264. protected function getGroupAttributes($group)
  265. {
  266. $attributes = $group->getProperty('attributes');
  267. if (!empty($attributes)) {
  268. if (is_array($attributes)) {
  269. $attributesString = '';
  270. foreach ($attributes as $name => $value) {
  271. $attributesString .= ' ' . $name . '="' . $this->escaper->escapeHtml($value) . '"';
  272. }
  273. $attributes = $attributesString;
  274. } else {
  275. $attributes = ' ' . $attributes;
  276. }
  277. }
  278. return $attributes;
  279. }
  280. /**
  281. * Add default attributes
  282. *
  283. * @param string $contentType
  284. * @param string $attributes
  285. * @return string
  286. */
  287. protected function addDefaultAttributes($contentType, $attributes)
  288. {
  289. switch ($contentType) {
  290. case 'js':
  291. $attributes = ' type="text/javascript" ' . $attributes;
  292. break;
  293. case 'css':
  294. $attributes = ' rel="stylesheet" type="text/css" ' . ($attributes ?: ' media="all"');
  295. break;
  296. }
  297. return $attributes;
  298. }
  299. /**
  300. * Returns assets template
  301. *
  302. * @param string $contentType
  303. * @param string|null $attributes
  304. * @return string
  305. */
  306. protected function getAssetTemplate($contentType, $attributes)
  307. {
  308. switch ($contentType) {
  309. case 'js':
  310. $groupTemplate = '<script ' . $attributes . ' src="%s"></script>' . "\n";
  311. break;
  312. case 'css':
  313. default:
  314. $groupTemplate = '<link ' . $attributes . ' href="%s" />' . "\n";
  315. break;
  316. }
  317. return $groupTemplate;
  318. }
  319. /**
  320. * Process IE condition
  321. *
  322. * @param string $groupHtml
  323. * @param \Magento\Framework\View\Asset\PropertyGroup $group
  324. * @return string
  325. */
  326. protected function processIeCondition($groupHtml, $group)
  327. {
  328. $ieCondition = $group->getProperty('ie_condition');
  329. if (!empty($ieCondition)) {
  330. $groupHtml = '<!--[if ' . $ieCondition . ']>' . "\n" . $groupHtml . '<![endif]-->' . "\n";
  331. }
  332. return $groupHtml;
  333. }
  334. /**
  335. * Render HTML tags referencing corresponding URLs
  336. *
  337. * @param \Magento\Framework\View\Asset\PropertyGroup $group
  338. * @return string
  339. */
  340. protected function renderAssetHtml(\Magento\Framework\View\Asset\PropertyGroup $group)
  341. {
  342. $assets = $this->processMerge($group->getAll(), $group);
  343. $attributes = $this->getGroupAttributes($group);
  344. $result = '';
  345. try {
  346. /** @var $asset \Magento\Framework\View\Asset\AssetInterface */
  347. foreach ($assets as $asset) {
  348. $template = $this->getAssetTemplate(
  349. $group->getProperty(GroupedCollection::PROPERTY_CONTENT_TYPE),
  350. $this->addDefaultAttributes($this->getAssetContentType($asset), $attributes)
  351. );
  352. $result .= sprintf($template, $asset->getUrl());
  353. }
  354. } catch (LocalizedException $e) {
  355. $this->logger->critical($e);
  356. $result .= sprintf($template, $this->urlBuilder->getUrl('', ['_direct' => 'core/index/notFound']));
  357. }
  358. return $result;
  359. }
  360. /**
  361. * Get asset content type
  362. *
  363. * @param \Magento\Framework\View\Asset\AssetInterface $asset
  364. * @return string
  365. */
  366. protected function getAssetContentType(\Magento\Framework\View\Asset\AssetInterface $asset)
  367. {
  368. return $asset->getContentType();
  369. }
  370. /**
  371. * Returns available groups.
  372. *
  373. * @return array
  374. */
  375. public function getAvailableResultGroups()
  376. {
  377. return array_fill_keys($this->assetTypeOrder, '');
  378. }
  379. }