PageRenderTime 48ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/dev/tests/integration/testsuite/Magento/Test/Integrity/ViewFileReferenceTest.php

https://gitlab.com/axeltizon/magento-demopoweraccess
PHP | 273 lines | 160 code | 30 blank | 83 comment | 12 complexity | f2dfc5636fe7a715b1484d51eb4b26f1 MD5 | raw file
  1. <?php
  2. /**
  3. * Test constructions of layout files
  4. *
  5. * Copyright © 2016 Magento. All rights reserved.
  6. * See COPYING.txt for license details.
  7. */
  8. /**
  9. * This test finds usages of modular view files, searched in non-modular context - it is obsolete and buggy
  10. * functionality, initially introduced in Magento 2.
  11. *
  12. * The test goes through modular calls of view files, and finds out, whether there are theme non-modular files
  13. * with the same path. Before fixing the bug, such call return theme files instead of modular files, which is
  14. * incorrect. After fixing the bug, such calls will start returning modular files, which is not a file we got used
  15. * to see, so such cases are probably should be fixed. The test finds such suspicious places.
  16. *
  17. * The test is intended to be deleted before Magento 2 release. With the release, having non-modular files with the
  18. * same paths as modular ones, is legitimate.
  19. */
  20. namespace Magento\Test\Integrity;
  21. use Magento\Framework\Component\ComponentRegistrar;
  22. class ViewFileReferenceTest extends \PHPUnit_Framework_TestCase
  23. {
  24. /**
  25. * @var \Magento\Framework\View\Design\Fallback\Rule\RuleInterface
  26. */
  27. protected static $_fallbackRule;
  28. /**
  29. * @var \Magento\Framework\View\Design\FileResolution\Fallback\StaticFile
  30. */
  31. protected static $_viewFilesFallback;
  32. /**
  33. * @var \Magento\Framework\View\Design\FileResolution\Fallback\File
  34. */
  35. protected static $_filesFallback;
  36. /**
  37. * @var array
  38. */
  39. protected static $_checkThemeLocales = [];
  40. /**
  41. * @var \Magento\Theme\Model\Theme\Collection
  42. */
  43. protected static $_themeCollection;
  44. /**
  45. * @var \Magento\Framework\Component\ComponentRegistrar
  46. */
  47. protected static $_componentRegistrar;
  48. public static function setUpBeforeClass()
  49. {
  50. $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
  51. $objectManager->configure(
  52. ['preferences' => ['Magento\Theme\Model\Theme' => 'Magento\Theme\Model\Theme\Data']]
  53. );
  54. self::$_componentRegistrar = $objectManager->get('Magento\Framework\Component\ComponentRegistrar');
  55. /** @var $fallbackPool \Magento\Framework\View\Design\Fallback\RulePool */
  56. $fallbackPool = $objectManager->get('Magento\Framework\View\Design\Fallback\RulePool');
  57. self::$_fallbackRule = $fallbackPool->getRule(
  58. $fallbackPool::TYPE_STATIC_FILE
  59. );
  60. self::$_viewFilesFallback = $objectManager->get(
  61. 'Magento\Framework\View\Design\FileResolution\Fallback\StaticFile'
  62. );
  63. self::$_filesFallback = $objectManager->get('Magento\Framework\View\Design\FileResolution\Fallback\File');
  64. // Themes to be checked
  65. self::$_themeCollection = $objectManager->get('Magento\Theme\Model\Theme\Collection');
  66. // Compose list of locales, needed to be checked for themes
  67. self::$_checkThemeLocales = [];
  68. foreach (self::$_themeCollection as $theme) {
  69. $themeLocales = self::_getThemeLocales($theme);
  70. $themeLocales[] = null;
  71. // Default non-localized file will need to be checked as well
  72. self::$_checkThemeLocales[$theme->getFullPath()] = $themeLocales;
  73. }
  74. }
  75. /**
  76. * Return array of locales, supported by the theme
  77. *
  78. * @param \Magento\Framework\View\Design\ThemeInterface $theme
  79. * @return array
  80. */
  81. protected static function _getThemeLocales(\Magento\Framework\View\Design\ThemeInterface $theme)
  82. {
  83. $result = [];
  84. $patternDir = self::_getLocalePatternDir($theme);
  85. foreach (\ResourceBundle::getLocales('') as $locale) {
  86. $dir = str_replace('<locale_placeholder>', $locale, $patternDir);
  87. if (is_dir($dir)) {
  88. $result[] = $locale;
  89. }
  90. }
  91. return $result;
  92. }
  93. /**
  94. * Return pattern for theme locale directories, where <locale_placeholder> is placed to mark a locale's location.
  95. *
  96. * @param \Magento\Framework\View\Design\ThemeInterface $theme
  97. * @return string
  98. * @throws \Exception
  99. */
  100. protected static function _getLocalePatternDir(\Magento\Framework\View\Design\ThemeInterface $theme)
  101. {
  102. $localePlaceholder = '<locale_placeholder>';
  103. $params = ['area' => $theme->getArea(), 'theme' => $theme, 'locale' => $localePlaceholder];
  104. $patternDirs = self::$_fallbackRule->getPatternDirs($params);
  105. $themePath = self::$_componentRegistrar->getPath(
  106. \Magento\Framework\Component\ComponentRegistrar::THEME,
  107. $theme->getFullPath()
  108. );
  109. foreach ($patternDirs as $patternDir) {
  110. $patternPath = $patternDir . '/';
  111. if ((strpos($patternPath, $themePath) !== false) // It is theme's directory
  112. && (strpos($patternPath, $localePlaceholder) !== false) // It is localized directory
  113. ) {
  114. return $patternDir;
  115. }
  116. }
  117. throw new \Exception('Unable to determine theme locale path');
  118. }
  119. /**
  120. * @param string $modularCall
  121. * @param array $usages
  122. * @param null|string $area
  123. * @dataProvider modularFallbackDataProvider
  124. */
  125. public function testModularFallback($modularCall, array $usages, $area)
  126. {
  127. list(, $file) = explode(\Magento\Framework\View\Asset\Repository::FILE_ID_SEPARATOR, $modularCall);
  128. $wrongResolutions = [];
  129. foreach (self::$_themeCollection as $theme) {
  130. if ($area && $theme->getArea() != $area) {
  131. continue;
  132. }
  133. $found = $this->_getFileResolutions($theme, $file);
  134. $wrongResolutions = array_merge($wrongResolutions, $found);
  135. }
  136. if ($wrongResolutions) {
  137. // If file is found, then old functionality (find modular files in non-modular locations) is used
  138. $message = sprintf(
  139. "Found modular call:\n %s in\n %s\n which may resolve to non-modular location(s):\n %s",
  140. $modularCall,
  141. implode(', ', $usages),
  142. implode(', ', $wrongResolutions)
  143. );
  144. $this->fail($message);
  145. }
  146. }
  147. /**
  148. * Resolves file to find its fallback'ed paths
  149. *
  150. * @param \Magento\Framework\View\Design\ThemeInterface $theme
  151. * @param string $file
  152. * @return array
  153. */
  154. protected function _getFileResolutions(\Magento\Framework\View\Design\ThemeInterface $theme, $file)
  155. {
  156. $found = [];
  157. $fileResolved = self::$_filesFallback->getFile($theme->getArea(), $theme, $file);
  158. if (file_exists($fileResolved)) {
  159. $found[$fileResolved] = $fileResolved;
  160. }
  161. foreach (self::$_checkThemeLocales[$theme->getFullPath()] as $locale) {
  162. $fileResolved = self::$_viewFilesFallback->getFile($theme->getArea(), $theme, $locale, $file);
  163. if (file_exists($fileResolved)) {
  164. $found[$fileResolved] = $fileResolved;
  165. }
  166. }
  167. return $found;
  168. }
  169. /**
  170. * @return array
  171. */
  172. public static function modularFallbackDataProvider()
  173. {
  174. $result = [];
  175. foreach (self::_getFilesToProcess() as $file) {
  176. $file = (string)$file;
  177. $modulePattern = '[A-Z][a-z]+_[A-Z][a-z]+';
  178. $filePattern = '[[:alnum:]_/-]+\\.[[:alnum:]_./-]+';
  179. $pattern = '#' . $modulePattern
  180. . preg_quote(\Magento\Framework\View\Asset\Repository::FILE_ID_SEPARATOR)
  181. . $filePattern . '#S';
  182. if (!preg_match_all($pattern, file_get_contents($file), $matches)) {
  183. continue;
  184. }
  185. $area = self::_getArea($file);
  186. foreach ($matches[0] as $modularCall) {
  187. $dataSetKey = $modularCall . ' @ ' . ($area ?: 'any area');
  188. if (!isset($result[$dataSetKey])) {
  189. $result[$dataSetKey] = ['modularCall' => $modularCall, 'usages' => [], 'area' => $area];
  190. }
  191. $result[$dataSetKey]['usages'][$file] = $file;
  192. }
  193. }
  194. return $result;
  195. }
  196. /**
  197. * Return list of files, that must be processed, searching for modular calls to view files
  198. *
  199. * @return array
  200. */
  201. protected static function _getFilesToProcess()
  202. {
  203. $result = [];
  204. $componentRegistrar = new \Magento\Framework\Component\ComponentRegistrar();
  205. $dirs = array_merge(
  206. $componentRegistrar->getPaths(ComponentRegistrar::MODULE),
  207. $componentRegistrar->getPaths(ComponentRegistrar::THEME)
  208. );
  209. foreach ($dirs as $dir) {
  210. $iterator = new \RecursiveIteratorIterator(
  211. new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS)
  212. );
  213. $result = array_merge($result, iterator_to_array($iterator));
  214. }
  215. return $result;
  216. }
  217. /**
  218. * Get the area, where file is located.
  219. *
  220. * Null is returned, if the file is not within an area, e.g. it is a model/block/helper php-file.
  221. *
  222. * @param string $file
  223. * @return string|null
  224. */
  225. protected static function _getArea($file)
  226. {
  227. $file = str_replace('\\', '/', $file);
  228. $areaPatterns = [];
  229. $componentRegistrar = new ComponentRegistrar();
  230. foreach ($componentRegistrar->getPaths(ComponentRegistrar::THEME) as $themeDir) {
  231. $areaPatterns[] = '#' . $themeDir . '/([^/]+)/#S';
  232. }
  233. foreach ($componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleDir) {
  234. $areaPatterns[] = '#' . $moduleDir . '/view/([^/]+)/#S';
  235. }
  236. foreach ($areaPatterns as $pattern) {
  237. if (preg_match($pattern, $file, $matches)) {
  238. return $matches[1];
  239. }
  240. }
  241. return null;
  242. }
  243. }