Repository.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\View\Asset;
  7. use Magento\Framework\UrlInterface;
  8. use Magento\Framework\App\Filesystem\DirectoryList;
  9. use Magento\Framework\App\ObjectManager;
  10. use Magento\Framework\View\Design\Theme\ThemeProviderInterface;
  11. /**
  12. * A repository service for view assets
  13. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  14. *
  15. * @api
  16. * @since 100.0.2
  17. */
  18. class Repository
  19. {
  20. /**
  21. * Scope separator for module notation of file ID
  22. */
  23. const FILE_ID_SEPARATOR = '::';
  24. /**
  25. * @var \Magento\Framework\UrlInterface
  26. */
  27. private $baseUrl;
  28. /**
  29. * @var \Magento\Framework\View\DesignInterface
  30. */
  31. private $design;
  32. /**
  33. * @var \Magento\Framework\View\Design\Theme\ListInterface
  34. * @deprecated 100.0.2
  35. */
  36. private $themeList;
  37. /**
  38. * @var \Magento\Framework\View\Asset\Source
  39. */
  40. private $assetSource;
  41. /**
  42. * @var \Magento\Framework\View\Asset\ContextInterface[]
  43. */
  44. private $fallbackContext;
  45. /**
  46. * @var \Magento\Framework\View\Asset\ContextInterface[]
  47. */
  48. private $fileContext;
  49. /**
  50. * @var null|array
  51. */
  52. private $defaults = null;
  53. /**
  54. * @var FileFactory
  55. */
  56. private $fileFactory;
  57. /**
  58. * @var File\FallbackContextFactory
  59. */
  60. private $fallbackContextFactory;
  61. /**
  62. * @var File\ContextFactory
  63. */
  64. private $contextFactory;
  65. /**
  66. * @var RemoteFactory
  67. */
  68. private $remoteFactory;
  69. /**
  70. * @var ThemeProviderInterface
  71. */
  72. private $themeProvider;
  73. /**
  74. * @param \Magento\Framework\UrlInterface $baseUrl
  75. * @param \Magento\Framework\View\DesignInterface $design
  76. * @param \Magento\Framework\View\Design\Theme\ListInterface $themeList
  77. * @param \Magento\Framework\View\Asset\Source $assetSource
  78. * @param \Magento\Framework\App\Request\Http $request
  79. * @param FileFactory $fileFactory
  80. * @param File\FallbackContextFactory $fallbackContextFactory
  81. * @param File\ContextFactory $contextFactory
  82. * @param RemoteFactory $remoteFactory
  83. */
  84. public function __construct(
  85. \Magento\Framework\UrlInterface $baseUrl,
  86. \Magento\Framework\View\DesignInterface $design,
  87. \Magento\Framework\View\Design\Theme\ListInterface $themeList,
  88. \Magento\Framework\View\Asset\Source $assetSource,
  89. \Magento\Framework\App\Request\Http $request,
  90. FileFactory $fileFactory,
  91. File\FallbackContextFactory $fallbackContextFactory,
  92. File\ContextFactory $contextFactory,
  93. RemoteFactory $remoteFactory
  94. ) {
  95. $this->baseUrl = $baseUrl;
  96. $this->design = $design;
  97. $this->themeList = $themeList;
  98. $this->assetSource = $assetSource;
  99. $this->request = $request;
  100. $this->fileFactory = $fileFactory;
  101. $this->fallbackContextFactory = $fallbackContextFactory;
  102. $this->contextFactory = $contextFactory;
  103. $this->remoteFactory = $remoteFactory;
  104. }
  105. /**
  106. * Update required parameters with default values if custom not specified
  107. *
  108. * @param array &$params
  109. * @throws \UnexpectedValueException
  110. * @return $this
  111. *
  112. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  113. * @SuppressWarnings(PHPMD.NPathComplexity)
  114. */
  115. public function updateDesignParams(array &$params)
  116. {
  117. // Set area
  118. if (empty($params['area'])) {
  119. $params['area'] = $this->getDefaultParameter('area');
  120. }
  121. // Set themeModel
  122. $theme = null;
  123. $area = $params['area'];
  124. if (!empty($params['themeId'])) {
  125. $theme = $params['themeId'];
  126. } elseif (isset($params['theme'])) {
  127. $theme = $params['theme'];
  128. } elseif (empty($params['themeModel']) && $area !== $this->getDefaultParameter('area')) {
  129. $theme = $this->design->getConfigurationDesignTheme($area);
  130. }
  131. if ($theme) {
  132. if (is_numeric($theme)) {
  133. $params['themeModel'] = $this->getThemeProvider()->getThemeById($theme);
  134. } else {
  135. $params['themeModel'] = $this->getThemeProvider()->getThemeByFullPath($area . '/' . $theme);
  136. }
  137. if (!$params['themeModel']) {
  138. throw new \UnexpectedValueException("Could not find theme '$theme' for area '$area'");
  139. }
  140. } elseif (empty($params['themeModel'])) {
  141. $params['themeModel'] = $this->getDefaultParameter('themeModel');
  142. }
  143. // Set module
  144. if (!array_key_exists('module', $params)) {
  145. $params['module'] = false;
  146. }
  147. // Set locale
  148. if (empty($params['locale'])) {
  149. $params['locale'] = $this->getDefaultParameter('locale');
  150. }
  151. return $this;
  152. }
  153. /**
  154. * Get theme provider
  155. *
  156. * @return ThemeProviderInterface
  157. */
  158. private function getThemeProvider()
  159. {
  160. if (null === $this->themeProvider) {
  161. $this->themeProvider = ObjectManager::getInstance()->get(ThemeProviderInterface::class);
  162. }
  163. return $this->themeProvider;
  164. }
  165. /**
  166. * Get default design parameter
  167. *
  168. * @param string $name
  169. * @return mixed
  170. */
  171. private function getDefaultParameter($name)
  172. {
  173. $this->defaults = $this->design->getDesignParams();
  174. return $this->defaults[$name];
  175. }
  176. /**
  177. * Create a file asset that's subject of fallback system
  178. *
  179. * @param string $fileId
  180. * @param array $params
  181. * @return File
  182. */
  183. public function createAsset($fileId, array $params = [])
  184. {
  185. $this->updateDesignParams($params);
  186. list($module, $filePath) = self::extractModule($fileId);
  187. if (!$module && $params['module']) {
  188. $module = $params['module'];
  189. }
  190. if (!isset($params['publish'])) {
  191. $map = $this->getRepositoryFilesMap($fileId, $params);
  192. if ($map) {
  193. $params = array_replace($params, $map);
  194. }
  195. }
  196. $isSecure = isset($params['_secure']) ? (bool) $params['_secure'] : null;
  197. $themePath = isset($params['theme']) ? $params['theme'] : $this->design->getThemePath($params['themeModel']);
  198. $context = $this->getFallbackContext(
  199. UrlInterface::URL_TYPE_STATIC,
  200. $isSecure,
  201. $params['area'],
  202. $themePath,
  203. $params['locale']
  204. );
  205. return $this->fileFactory->create(
  206. [
  207. 'source' => $this->assetSource,
  208. 'context' => $context,
  209. 'filePath' => $filePath,
  210. 'module' => $module,
  211. 'contentType' => $this->assetSource->getContentType($filePath)
  212. ]
  213. );
  214. }
  215. /**
  216. * Get current context for static view files
  217. *
  218. * @return \Magento\Framework\View\Asset\File\FallbackContext
  219. */
  220. public function getStaticViewFileContext()
  221. {
  222. $params = [];
  223. $this->updateDesignParams($params);
  224. $themePath = $this->design->getThemePath($params['themeModel']);
  225. $isSecure = $this->request->isSecure();
  226. return $this->getFallbackContext(
  227. UrlInterface::URL_TYPE_STATIC,
  228. $isSecure,
  229. $params['area'],
  230. $themePath,
  231. $params['locale']
  232. );
  233. }
  234. /**
  235. * Get a fallback context value object
  236. *
  237. * Create only one instance per combination of parameters
  238. *
  239. * @param string $urlType
  240. * @param bool|null $isSecure
  241. * @param string $area
  242. * @param string $themePath
  243. * @param string $locale
  244. * @return \Magento\Framework\View\Asset\File\FallbackContext
  245. */
  246. private function getFallbackContext($urlType, $isSecure, $area, $themePath, $locale)
  247. {
  248. $secureKey = null === $isSecure ? 'null' : (int)$isSecure;
  249. $baseDirType = DirectoryList::STATIC_VIEW;
  250. $id = implode('|', [$baseDirType, $urlType, $secureKey, $area, $themePath, $locale]);
  251. if (!isset($this->fallbackContext[$id])) {
  252. $url = $this->baseUrl->getBaseUrl(['_type' => $urlType, '_secure' => $isSecure]);
  253. $this->fallbackContext[$id] = $this->fallbackContextFactory->create(
  254. [
  255. 'baseUrl' => $url,
  256. 'areaType' => $area,
  257. 'themePath' => $themePath,
  258. 'localeCode' => $locale
  259. ]
  260. );
  261. }
  262. return $this->fallbackContext[$id];
  263. }
  264. /**
  265. * Create a file asset similar to an existing local asset by using its context
  266. *
  267. * @param string $fileId
  268. * @param LocalInterface $similarTo
  269. * @return File
  270. */
  271. public function createSimilar($fileId, LocalInterface $similarTo)
  272. {
  273. list($module, $filePath) = self::extractModule($fileId);
  274. if (!$module) {
  275. $module = $similarTo->getModule();
  276. }
  277. return $this->fileFactory->create(
  278. [
  279. 'source' => $this->assetSource,
  280. 'context' => $similarTo->getContext(),
  281. 'filePath' => $filePath,
  282. 'module' => $module,
  283. 'contentType' => $this->assetSource->getContentType($filePath)
  284. ]
  285. );
  286. }
  287. /**
  288. * Create a file asset with an arbitrary path
  289. *
  290. * This kind of file is not subject of fallback system
  291. * Client code is responsible for ensuring that the file is in specified directory
  292. *
  293. * @param string $filePath
  294. * @param string $dirPath
  295. * @param string $baseDirType
  296. * @param string $baseUrlType
  297. * @return File
  298. */
  299. public function createArbitrary(
  300. $filePath,
  301. $dirPath,
  302. $baseDirType = DirectoryList::STATIC_VIEW,
  303. $baseUrlType = UrlInterface::URL_TYPE_STATIC
  304. ) {
  305. $context = $this->getFileContext($baseDirType, $baseUrlType, $dirPath);
  306. $contentType = $this->assetSource->getContentType($filePath);
  307. return $this->fileFactory->create(
  308. [
  309. 'source' => $this->assetSource,
  310. 'context' => $context,
  311. 'filePath' => $filePath,
  312. 'module' => '',
  313. 'contentType' => $contentType
  314. ]
  315. );
  316. }
  317. /**
  318. * Get a file context value object
  319. *
  320. * Same instance per set of parameters
  321. *
  322. * @param string $baseDirType
  323. * @param string $urlType
  324. * @param string $dirPath
  325. * @return \Magento\Framework\View\Asset\File\Context
  326. */
  327. private function getFileContext($baseDirType, $urlType, $dirPath)
  328. {
  329. $id = implode('|', [$baseDirType, $urlType, $dirPath]);
  330. if (!isset($this->fileContext[$id])) {
  331. $url = $this->baseUrl->getBaseUrl(['_type' => $urlType]);
  332. $this->fileContext[$id] = $this->contextFactory->create(
  333. ['baseUrl' => $url, 'baseDirType' => $baseDirType, 'contextPath' => $dirPath]
  334. );
  335. }
  336. return $this->fileContext[$id];
  337. }
  338. /**
  339. * Create a file asset with path relative to specified local asset
  340. *
  341. * @param string $fileId
  342. * @param LocalInterface $relativeTo
  343. * @return File
  344. */
  345. public function createRelated($fileId, LocalInterface $relativeTo)
  346. {
  347. list($module, $filePath) = self::extractModule($fileId);
  348. if ($module) {
  349. return $this->createSimilar($fileId, $relativeTo);
  350. }
  351. $filePath = \Magento\Framework\View\FileSystem::getRelatedPath($relativeTo->getFilePath(), $filePath);
  352. return $this->createSimilar($filePath, $relativeTo);
  353. }
  354. /**
  355. * Create a remote asset value object
  356. *
  357. * @param string $url
  358. * @param string $contentType
  359. * @return Remote
  360. * @codeCoverageIgnore
  361. */
  362. public function createRemoteAsset($url, $contentType)
  363. {
  364. return $this->remoteFactory->create(['url' => $url, 'contentType' => $contentType]);
  365. }
  366. /**
  367. * Getter for static view file URL
  368. *
  369. * @param string $fileId
  370. * @return string
  371. */
  372. public function getUrl($fileId)
  373. {
  374. $asset = $this->createAsset($fileId);
  375. return $asset->getUrl();
  376. }
  377. /**
  378. * A getter for static view file URL with special parameters
  379. *
  380. * To omit parameters and have them automatically determined from application state, use getUrl()
  381. *
  382. * @param string $fileId
  383. * @param array $params
  384. * @return string
  385. * @see getUrl()
  386. */
  387. public function getUrlWithParams($fileId, array $params)
  388. {
  389. $asset = $this->createAsset($fileId, $params);
  390. return $asset->getUrl();
  391. }
  392. /**
  393. * Extract module name from specified file ID
  394. *
  395. * @param string $fileId
  396. * @return array
  397. * @throws \Magento\Framework\Exception\LocalizedException
  398. */
  399. public static function extractModule($fileId)
  400. {
  401. if (strpos($fileId, self::FILE_ID_SEPARATOR) === false) {
  402. return ['', $fileId];
  403. }
  404. $result = explode(self::FILE_ID_SEPARATOR, $fileId, 2);
  405. if (empty($result[0])) {
  406. throw new \Magento\Framework\Exception\LocalizedException(
  407. new \Magento\Framework\Phrase('Scope separator "::" cannot be used without scope identifier.')
  408. );
  409. }
  410. return [$result[0], $result[1]];
  411. }
  412. /**
  413. * Get repository files map
  414. *
  415. * @param string $fileId
  416. * @param array $params
  417. * @return RepositoryMap
  418. */
  419. private function getRepositoryFilesMap($fileId, array $params)
  420. {
  421. $repositoryMap = ObjectManager::getInstance()->get(RepositoryMap::class);
  422. return $repositoryMap->getMap($fileId, $params);
  423. }
  424. }