Files.php 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\App\Utility;
  7. use Magento\Framework\App\ObjectManager;
  8. use Magento\Framework\Component\ComponentRegistrar;
  9. use Magento\Framework\Component\DirSearch;
  10. use Magento\Framework\Serialize\Serializer\Json;
  11. use Magento\Framework\View\Design\Theme\ThemePackageList;
  12. use Magento\Framework\Filesystem\Glob;
  13. /**
  14. * A helper to gather specific kind of files in Magento application.
  15. *
  16. * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
  17. * @SuppressWarnings(PHPMD.NPathComplexity)
  18. */
  19. class Files
  20. {
  21. /**
  22. * Include app code
  23. */
  24. const INCLUDE_APP_CODE = 1;
  25. /**
  26. * Include tests
  27. */
  28. const INCLUDE_TESTS = 2;
  29. /**
  30. * Include dev tools
  31. */
  32. const INCLUDE_DEV_TOOLS = 4;
  33. /**
  34. * Include templates
  35. */
  36. const INCLUDE_TEMPLATES = 8;
  37. /**
  38. * Include lib files
  39. */
  40. const INCLUDE_LIBS = 16;
  41. /**
  42. * Include pub code
  43. */
  44. const INCLUDE_PUB_CODE = 32;
  45. /**
  46. * Include non classes
  47. */
  48. const INCLUDE_NON_CLASSES = 64;
  49. /**
  50. * Include setup
  51. */
  52. const INCLUDE_SETUP = 128;
  53. /**
  54. * Return as data set
  55. */
  56. const AS_DATA_SET = 1024;
  57. /**
  58. * @var ComponentRegistrar
  59. */
  60. protected $componentRegistrar;
  61. /**
  62. * @var \Magento\Framework\App\Utility\Files
  63. */
  64. protected static $_instance = null;
  65. /**
  66. * @var array
  67. */
  68. protected static $_cache = [];
  69. /**
  70. * @var DirSearch
  71. */
  72. private $dirSearch;
  73. /**
  74. * @var ThemePackageList
  75. */
  76. private $themePackageList;
  77. /**
  78. * @var Json
  79. */
  80. private $serializer;
  81. /**
  82. * @var RegexIteratorFactory
  83. */
  84. private $regexIteratorFactory;
  85. /**
  86. * Constructor
  87. *
  88. * @param ComponentRegistrar $componentRegistrar
  89. * @param DirSearch $dirSearch
  90. * @param ThemePackageList $themePackageList
  91. * @param Json|null $serializer
  92. * @param RegexIteratorFactory|null $regexIteratorFactory
  93. */
  94. public function __construct(
  95. ComponentRegistrar $componentRegistrar,
  96. DirSearch $dirSearch,
  97. ThemePackageList $themePackageList,
  98. Json $serializer = null,
  99. RegexIteratorFactory $regexIteratorFactory = null
  100. ) {
  101. $this->componentRegistrar = $componentRegistrar;
  102. $this->dirSearch = $dirSearch;
  103. $this->themePackageList = $themePackageList;
  104. $this->serializer = $serializer ?: ObjectManager::getInstance()
  105. ->get(Json::class);
  106. $this->regexIteratorFactory = $regexIteratorFactory ?: ObjectManager::getInstance()
  107. ->get(RegexIteratorFactory::class);
  108. }
  109. /**
  110. * Setter for an instance of self
  111. *
  112. * Also can unset the current instance, if no arguments are specified
  113. *
  114. * @param Files|null $instance
  115. * @return void
  116. */
  117. public static function setInstance(Files $instance = null)
  118. {
  119. self::$_instance = $instance;
  120. }
  121. /**
  122. * Getter for an instance of self
  123. *
  124. * @return \Magento\Framework\App\Utility\Files
  125. * @throws \Exception when there is no instance set
  126. */
  127. public static function init()
  128. {
  129. if (!self::$_instance) {
  130. throw new \Exception('Instance is not set yet.');
  131. }
  132. return self::$_instance;
  133. }
  134. /**
  135. * Compose PHPUnit's data sets that contain each file as the first argument
  136. *
  137. * @param array $files
  138. * @return array
  139. */
  140. public static function composeDataSets(array $files)
  141. {
  142. $result = [];
  143. foreach ($files as $file) {
  144. $key = str_replace(BP . '/', '', $file);
  145. $result[$key] = [$file];
  146. }
  147. return $result;
  148. }
  149. /**
  150. * Get list of regular expressions for matching test directories in modules
  151. *
  152. * @return array
  153. */
  154. private function getModuleTestDirsRegex()
  155. {
  156. $moduleTestDirs = [];
  157. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleDir) {
  158. $moduleTestDirs[] = str_replace('\\', '/', '#' . $moduleDir . '/Test#');
  159. }
  160. return $moduleTestDirs;
  161. }
  162. /**
  163. * Get base path
  164. *
  165. * @return string
  166. */
  167. public function getPathToSource()
  168. {
  169. return BP;
  170. }
  171. /**
  172. * Returns list of files, where expected to have class declarations
  173. *
  174. * @param int $flags
  175. * @return array
  176. */
  177. public function getPhpFiles($flags = 0)
  178. {
  179. // Sets default value
  180. if ($flags === 0) {
  181. $flags = self::INCLUDE_APP_CODE
  182. | self::INCLUDE_TESTS
  183. | self::INCLUDE_DEV_TOOLS
  184. | self::INCLUDE_LIBS
  185. | self::AS_DATA_SET;
  186. }
  187. $key = __METHOD__ . BP . $flags;
  188. if (!isset(self::$_cache[$key])) {
  189. $files = array_merge(
  190. $this->getAppCodeFiles($flags),
  191. $this->getTestFiles($flags),
  192. $this->getDevToolsFiles($flags),
  193. $this->getTemplateFiles($flags),
  194. $this->getLibraryFiles($flags),
  195. $this->getPubFiles($flags),
  196. $this->getSetupPhpFiles($flags)
  197. );
  198. self::$_cache[$key] = $files;
  199. }
  200. if ($flags & self::AS_DATA_SET) {
  201. return self::composeDataSets(self::$_cache[$key]);
  202. }
  203. return self::$_cache[$key];
  204. }
  205. /**
  206. * Return array with all template files
  207. *
  208. * @param int $flags
  209. * @return array
  210. */
  211. private function getTemplateFiles($flags)
  212. {
  213. if ($flags & self::INCLUDE_TEMPLATES) {
  214. return $this->getPhtmlFiles(false, false);
  215. }
  216. return [];
  217. }
  218. /**
  219. * Return array with all php files related to library
  220. *
  221. * @param int $flags
  222. * @return array
  223. */
  224. private function getLibraryFiles($flags)
  225. {
  226. if ($flags & self::INCLUDE_LIBS) {
  227. $libraryExcludeDirs = [];
  228. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::LIBRARY) as $libraryDir) {
  229. $libraryExcludeDirs[] = str_replace('\\', '/', '#' . $libraryDir . '/Test#');
  230. $libraryExcludeDirs[] = str_replace('\\', '/', '#' . $libraryDir) . '/[\\w]+/Test#';
  231. if (!($flags & self::INCLUDE_NON_CLASSES)) {
  232. $libraryExcludeDirs[] = str_replace('\\', '/', '#' . $libraryDir . '/registration#');
  233. }
  234. }
  235. return $this->getFilesSubset(
  236. $this->componentRegistrar->getPaths(ComponentRegistrar::LIBRARY),
  237. '*.php',
  238. $libraryExcludeDirs
  239. );
  240. }
  241. return [];
  242. }
  243. /**
  244. * Return array with all php files related to pub
  245. *
  246. * @param int $flags
  247. * @return array
  248. */
  249. private function getPubFiles($flags)
  250. {
  251. if ($flags & self::INCLUDE_PUB_CODE) {
  252. return array_merge(
  253. Glob::glob(BP . '/*.php', Glob::GLOB_NOSORT),
  254. Glob::glob(BP . '/pub/*.php', Glob::GLOB_NOSORT)
  255. );
  256. }
  257. return [];
  258. }
  259. /**
  260. * Return array with all php files related to dev tools
  261. *
  262. * @param int $flags
  263. * @return array
  264. */
  265. private function getDevToolsFiles($flags)
  266. {
  267. if ($flags & self::INCLUDE_DEV_TOOLS) {
  268. return $this->getFilesSubset([BP . '/dev/tools/Magento'], '*.php', []);
  269. }
  270. return [];
  271. }
  272. /**
  273. * Return array with all php files related to modules
  274. *
  275. * @param int $flags
  276. * @return array
  277. */
  278. private function getAppCodeFiles($flags)
  279. {
  280. if ($flags & self::INCLUDE_APP_CODE) {
  281. $excludePaths = [];
  282. $paths = $this->componentRegistrar->getPaths(ComponentRegistrar::MODULE);
  283. if ($flags & self::INCLUDE_NON_CLASSES) {
  284. $paths[] = BP . '/app';
  285. } else {
  286. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleDir) {
  287. $excludePaths[] = str_replace('\\', '/', '#' . $moduleDir . '/registration.php#');
  288. $excludePaths[] = str_replace('\\', '/', '#' . $moduleDir . '/cli_commands.php#');
  289. }
  290. }
  291. return $this->getFilesSubset(
  292. $paths,
  293. '*.php',
  294. array_merge($this->getModuleTestDirsRegex(), $excludePaths)
  295. );
  296. }
  297. return [];
  298. }
  299. /**
  300. * Return array with all test files
  301. *
  302. * @param int $flags
  303. * @return array
  304. */
  305. private function getTestFiles($flags)
  306. {
  307. if ($flags & self::INCLUDE_TESTS) {
  308. $testDirs = [
  309. BP . '/dev/tests',
  310. BP . '/setup/src/Magento/Setup/Test',
  311. ];
  312. $moduleTestDir = [];
  313. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleDir) {
  314. $moduleTestDir[] = $moduleDir . '/Test';
  315. }
  316. $libraryTestDirs = [];
  317. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::LIBRARY) as $libraryDir) {
  318. $libraryTestDirs[] = $libraryDir . '/Test';
  319. $libraryTestDirs[] = $libraryDir . '/*/Test';
  320. }
  321. $testDirs = array_merge($testDirs, $moduleTestDir, $libraryTestDirs);
  322. return self::getFiles($testDirs, '*.php');
  323. }
  324. return [];
  325. }
  326. /**
  327. * Returns list of xml files, used by Magento application
  328. *
  329. * @return array
  330. */
  331. public function getXmlFiles()
  332. {
  333. return array_merge(
  334. $this->getMainConfigFiles(),
  335. $this->getLayoutFiles(),
  336. $this->getPageLayoutFiles(),
  337. $this->getConfigFiles(),
  338. $this->getDiConfigs(true),
  339. $this->getLayoutConfigFiles(),
  340. $this->getPageTypeFiles()
  341. );
  342. }
  343. /**
  344. * Retrieve all config files, that participate (or have a chance to participate) in composing main config
  345. *
  346. * @param bool $asDataSet
  347. * @return array
  348. */
  349. public function getMainConfigFiles($asDataSet = true)
  350. {
  351. $cacheKey = __METHOD__ . '|' . implode('|', [$asDataSet]);
  352. if (!isset(self::$_cache[$cacheKey])) {
  353. $configXmlPaths = [];
  354. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleDir) {
  355. $configXmlPaths[] = $moduleDir . '/etc/config.xml';
  356. // Module DB-specific configs, e.g. config.mysql4.xml
  357. $configXmlPaths[] = $moduleDir . '/etc/config.*.xml';
  358. }
  359. $globPaths = [BP . '/app/etc/config.xml', BP . '/app/etc/*/config.xml'];
  360. $configXmlPaths = array_merge($globPaths, $configXmlPaths);
  361. $files = [];
  362. foreach ($configXmlPaths as $xmlPath) {
  363. $files = array_merge($files, glob($xmlPath, GLOB_NOSORT));
  364. }
  365. self::$_cache[$cacheKey] = $files;
  366. }
  367. if ($asDataSet) {
  368. return self::composeDataSets(self::$_cache[$cacheKey]);
  369. }
  370. return self::$_cache[$cacheKey];
  371. }
  372. /**
  373. * Returns list of configuration files, used by Magento application
  374. *
  375. * @param string $fileNamePattern
  376. * @param array $excludedFileNames
  377. * @param bool $asDataSet
  378. * @return array
  379. * @codingStandardsIgnoreStart
  380. */
  381. public function getConfigFiles(
  382. $fileNamePattern = '*.xml',
  383. $excludedFileNames = ['wsdl.xml', 'wsdl2.xml', 'wsi.xml'],
  384. $asDataSet = true
  385. ) {
  386. $cacheKey = __METHOD__ . '|' . $this->serializer->serialize([$fileNamePattern, $excludedFileNames, $asDataSet]);
  387. if (!isset(self::$_cache[$cacheKey])) {
  388. $files = $this->dirSearch->collectFiles(ComponentRegistrar::MODULE, "/etc/{$fileNamePattern}");
  389. $files = array_filter(
  390. $files,
  391. function ($file) use ($excludedFileNames) {
  392. return !in_array(basename($file), $excludedFileNames);
  393. }
  394. );
  395. self::$_cache[$cacheKey] = $files;
  396. }
  397. if ($asDataSet) {
  398. return self::composeDataSets(self::$_cache[$cacheKey]);
  399. }
  400. return self::$_cache[$cacheKey];
  401. }
  402. // @codingStandardsIgnoreEnd
  403. /**
  404. * Returns list of XML related files, used by Magento application
  405. *
  406. * @param string $fileNamePattern
  407. * @param array $excludedFileNames
  408. * @param bool $asDataSet
  409. * @return array
  410. */
  411. public function getXmlCatalogFiles(
  412. $fileNamePattern = '*.xsd',
  413. $excludedFileNames = [],
  414. $asDataSet = true
  415. ) {
  416. $cacheKey = __METHOD__ . '|' . $this->serializer->serialize([$fileNamePattern, $excludedFileNames, $asDataSet]);
  417. if (!isset(self::$_cache[$cacheKey])) {
  418. $files = $this->getFilesSubset(
  419. $this->componentRegistrar->getPaths(ComponentRegistrar::MODULE),
  420. $fileNamePattern,
  421. []
  422. );
  423. $libraryExcludeDirs = [];
  424. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::LIBRARY) as $libraryDir) {
  425. $libraryExcludeDirs[] = str_replace('\\', '/', '#' . $libraryDir . '/Test#');
  426. $libraryExcludeDirs[] = str_replace('\\', '/', '#' . $libraryDir) . '/[\\w]+/Test#';
  427. }
  428. $files = array_merge(
  429. $files,
  430. $this->getFilesSubset(
  431. $this->componentRegistrar->getPaths(ComponentRegistrar::LIBRARY),
  432. $fileNamePattern,
  433. $libraryExcludeDirs
  434. )
  435. );
  436. $files = array_merge(
  437. $files,
  438. $this->getFilesSubset(
  439. $this->componentRegistrar->getPaths(ComponentRegistrar::THEME),
  440. $fileNamePattern,
  441. []
  442. )
  443. );
  444. $files = array_merge(
  445. $files,
  446. $this->getFilesSubset(
  447. $this->componentRegistrar->getPaths(ComponentRegistrar::SETUP),
  448. $fileNamePattern,
  449. []
  450. )
  451. );
  452. $files = array_filter(
  453. $files,
  454. function ($file) use ($excludedFileNames) {
  455. return !in_array(basename($file), $excludedFileNames);
  456. }
  457. );
  458. self::$_cache[$cacheKey] = $files;
  459. }
  460. if ($asDataSet) {
  461. return self::composeDataSets(self::$_cache[$cacheKey]);
  462. }
  463. return self::$_cache[$cacheKey];
  464. }
  465. /**
  466. * Returns a list of configuration files found under theme directories.
  467. *
  468. * @param string $fileNamePattern
  469. * @param bool $asDataSet
  470. * @return array
  471. */
  472. public function getLayoutConfigFiles($fileNamePattern = '*.xml', $asDataSet = true)
  473. {
  474. $cacheKey = __METHOD__ . '|' . implode('|', [$fileNamePattern, $asDataSet]);
  475. if (!isset(self::$_cache[$cacheKey])) {
  476. self::$_cache[$cacheKey] = $this->dirSearch->collectFiles(
  477. ComponentRegistrar::THEME,
  478. "/etc/{$fileNamePattern}"
  479. );
  480. }
  481. if ($asDataSet) {
  482. return self::composeDataSets(self::$_cache[$cacheKey]);
  483. }
  484. return self::$_cache[$cacheKey];
  485. }
  486. /**
  487. * Returns list of page configuration and generic layout files, used by Magento application modules
  488. *
  489. * An incoming array can contain the following items
  490. * array (
  491. * 'namespace' => 'namespace_name',
  492. * 'module' => 'module_name',
  493. * 'area' => 'area_name',
  494. * 'theme' => 'theme_name',
  495. * 'include_code' => true|false,
  496. * 'include_design' => true|false,
  497. * 'with_metainfo' => true|false,
  498. * )
  499. *
  500. * @param array $incomingParams
  501. * @param bool $asDataSet
  502. * @return array
  503. */
  504. public function getLayoutFiles($incomingParams = [], $asDataSet = true)
  505. {
  506. return $this->getLayoutXmlFiles('layout', $incomingParams, $asDataSet);
  507. }
  508. /**
  509. * Returns list of page layout files, used by Magento application modules
  510. *
  511. * An incoming array can contain the following items
  512. * array (
  513. * 'namespace' => 'namespace_name',
  514. * 'module' => 'module_name',
  515. * 'area' => 'area_name',
  516. * 'theme' => 'theme_name',
  517. * 'include_code' => true|false,
  518. * 'include_design' => true|false,
  519. * 'with_metainfo' => true|false,
  520. * )
  521. *
  522. * @param array $incomingParams
  523. * @param bool $asDataSet
  524. * @return array
  525. */
  526. public function getPageLayoutFiles($incomingParams = [], $asDataSet = true)
  527. {
  528. return $this->getLayoutXmlFiles('page_layout', $incomingParams, $asDataSet);
  529. }
  530. /**
  531. * Returns list of UI Component files, used by Magento application
  532. *
  533. * An incoming array can contain the following items
  534. * array (
  535. * 'namespace' => 'namespace_name',
  536. * 'module' => 'module_name',
  537. * 'area' => 'area_name',
  538. * 'theme' => 'theme_name',
  539. * 'include_code' => true|false,
  540. * 'include_design' => true|false,
  541. * 'with_metainfo' => true|false,
  542. * )
  543. *
  544. * @param array $incomingParams
  545. * @param bool $asDataSet
  546. * @return array
  547. */
  548. public function getUiComponentXmlFiles($incomingParams = [], $asDataSet = true)
  549. {
  550. return $this->getLayoutXmlFiles('ui_component', $incomingParams, $asDataSet);
  551. }
  552. /**
  553. * @param string $location
  554. * @param array $incomingParams
  555. * @param bool $asDataSet
  556. * @return array
  557. */
  558. protected function getLayoutXmlFiles($location, $incomingParams = [], $asDataSet = true)
  559. {
  560. $params = [
  561. 'namespace' => '*',
  562. 'module' => '*',
  563. 'area' => '*',
  564. 'theme_path' => '*/*',
  565. 'include_code' => true,
  566. 'include_design' => true,
  567. 'with_metainfo' => false
  568. ];
  569. foreach (array_keys($params) as $key) {
  570. if (isset($incomingParams[$key])) {
  571. $params[$key] = $incomingParams[$key];
  572. }
  573. }
  574. $cacheKey = md5($location . '|' . implode('|', $params));
  575. if (!isset(self::$_cache[__METHOD__][$cacheKey])) {
  576. $files = [];
  577. if ($params['include_code']) {
  578. $files = array_merge($files, $this->collectModuleLayoutFiles($params, $location));
  579. }
  580. if ($params['include_design']) {
  581. $files = array_merge($files, $this->collectThemeLayoutFiles($params, $location));
  582. }
  583. self::$_cache[__METHOD__][$cacheKey] = $files;
  584. }
  585. if ($asDataSet) {
  586. return self::composeDataSets(self::$_cache[__METHOD__][$cacheKey]);
  587. }
  588. return self::$_cache[__METHOD__][$cacheKey];
  589. }
  590. /**
  591. * Collect layout files from modules
  592. *
  593. * @param array $params
  594. * @param string $location
  595. * @return array
  596. */
  597. private function collectModuleLayoutFiles(array $params, $location)
  598. {
  599. $files = [];
  600. $area = $params['area'];
  601. $requiredModuleName = $params['namespace'] . '_' . $params['module'];
  602. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $moduleDir) {
  603. if ($requiredModuleName == '*_*' || $moduleName == $requiredModuleName) {
  604. $moduleFiles = [];
  605. $this->_accumulateFilesByPatterns(
  606. [$moduleDir . "/view/{$area}/{$location}"],
  607. '*.xml',
  608. $moduleFiles
  609. );
  610. if ($params['with_metainfo']) {
  611. foreach ($moduleFiles as $moduleFile) {
  612. $modulePath = str_replace(DIRECTORY_SEPARATOR, '/', preg_quote($moduleDir, '#'));
  613. $regex = '#^' . $modulePath . '/view/(?P<area>[a-z]+)/layout/(?P<path>.+)$#i';
  614. if (preg_match($regex, $moduleFile, $matches)) {
  615. $files[] = [
  616. $matches['area'],
  617. '',
  618. $moduleName,
  619. $matches['path'],
  620. $moduleFile,
  621. ];
  622. } else {
  623. throw new \UnexpectedValueException("Could not parse modular layout file '$moduleFile'");
  624. }
  625. }
  626. } else {
  627. $files = array_merge($files, $moduleFiles);
  628. }
  629. }
  630. }
  631. return $files;
  632. }
  633. /**
  634. * Collect layout files from themes
  635. *
  636. * @param array $params
  637. * @param string $location
  638. * @return array
  639. */
  640. private function collectThemeLayoutFiles(array $params, $location)
  641. {
  642. $files = [];
  643. $area = $params['area'];
  644. $requiredModuleName = $params['namespace'] . '_' . $params['module'];
  645. $themePath = $params['theme_path'];
  646. foreach ($this->themePackageList->getThemes() as $theme) {
  647. $currentThemePath = str_replace(DIRECTORY_SEPARATOR, '/', $theme->getPath());
  648. $currentThemeCode = $theme->getVendor() . '/' . $theme->getName();
  649. if (($area == '*' || $theme->getArea() === $area)
  650. && ($themePath == '*' || $themePath == '*/*' || $themePath == $currentThemeCode)
  651. ) {
  652. $themeFiles = [];
  653. $this->_accumulateFilesByPatterns(
  654. [$currentThemePath . "/{$requiredModuleName}/{$location}"],
  655. '*.xml',
  656. $themeFiles
  657. );
  658. if ($params['with_metainfo']) {
  659. $files = array_merge($this->parseThemeFiles($themeFiles, $currentThemePath, $theme));
  660. } else {
  661. $files = array_merge($files, $themeFiles);
  662. }
  663. }
  664. }
  665. return $files;
  666. }
  667. /**
  668. * @param array $themeFiles
  669. * @param string $currentThemePath
  670. * @param ThemePackage $theme
  671. * @return array
  672. */
  673. private function parseThemeFiles($themeFiles, $currentThemePath, $theme)
  674. {
  675. $files = [];
  676. $regex = '#^' . $currentThemePath
  677. . '/(?P<module>[a-z\d]+_[a-z\d]+)/layout/(override/((base/)|(theme/[a-z\d_]+/[a-z\d_]+/)))?'
  678. . '(?P<path>.+)$#i';
  679. foreach ($themeFiles as $themeFile) {
  680. if (preg_match($regex, $themeFile, $matches)) {
  681. $files[] = [
  682. $theme->getArea(),
  683. $theme->getVendor() . '/' . $theme->getName(),
  684. $matches['module'],
  685. $matches['path'],
  686. $themeFile,
  687. ];
  688. } else {
  689. throw new \UnexpectedValueException("Could not parse theme layout file '$themeFile'");
  690. }
  691. }
  692. return $files;
  693. }
  694. /**
  695. * Returns list of page_type files, used by Magento application modules
  696. *
  697. * An incoming array can contain the following items
  698. * array (
  699. * 'namespace' => 'namespace_name',
  700. * 'module' => 'module_name',
  701. * 'area' => 'area_name',
  702. * 'theme' => 'theme_name',
  703. * )
  704. *
  705. * @param array $incomingParams
  706. * @param bool $asDataSet
  707. * @return array
  708. */
  709. public function getPageTypeFiles($incomingParams = [], $asDataSet = true)
  710. {
  711. $params = ['namespace' => '*', 'module' => '*', 'area' => '*'];
  712. foreach (array_keys($params) as $key) {
  713. if (isset($incomingParams[$key])) {
  714. $params[$key] = $incomingParams[$key];
  715. }
  716. }
  717. $cacheKey = md5(implode('|', $params));
  718. if (!isset(self::$_cache[__METHOD__][$cacheKey])) {
  719. self::$_cache[__METHOD__][$cacheKey] = self::getFiles(
  720. $this->getEtcAreaPaths($params['namespace'], $params['module'], $params['area']),
  721. 'page_types.xml'
  722. );
  723. }
  724. if ($asDataSet) {
  725. return self::composeDataSets(self::$_cache[__METHOD__][$cacheKey]);
  726. }
  727. return self::$_cache[__METHOD__][$cacheKey];
  728. }
  729. /**
  730. * Get module etc paths for specified area
  731. *
  732. * @param string $namespace
  733. * @param string $module
  734. * @param string $area
  735. * @return array
  736. */
  737. private function getEtcAreaPaths($namespace, $module, $area)
  738. {
  739. $etcAreaPaths = [];
  740. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $moduleDir) {
  741. $keyInfo = explode('_', $moduleName);
  742. if ($keyInfo[0] == $namespace || $namespace == '*') {
  743. if ($keyInfo[1] == $module || $module == '*') {
  744. $etcAreaPaths[] = $moduleDir . "/etc/{$area}";
  745. }
  746. }
  747. }
  748. return $etcAreaPaths;
  749. }
  750. /**
  751. * Returns list of Javascript files in Magento
  752. *
  753. * @param string $area
  754. * @param string $themePath
  755. * @param string $namespace
  756. * @param string $module
  757. * @return array
  758. */
  759. public function getJsFiles($area = '*', $themePath = '*/*', $namespace = '*', $module = '*')
  760. {
  761. $key = $area . $themePath . $namespace . $module . __METHOD__ . BP;
  762. if (isset(self::$_cache[$key])) {
  763. return self::$_cache[$key];
  764. }
  765. $moduleWebPaths = [];
  766. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $moduleDir) {
  767. $keyInfo = explode('_', $moduleName);
  768. if ($keyInfo[0] == $namespace || $namespace == '*') {
  769. if ($keyInfo[1] == $module || $module == '*') {
  770. $moduleWebPaths[] = $moduleDir . "/view/{$area}/web";
  771. }
  772. }
  773. }
  774. $themePaths = $this->getThemePaths($area, $namespace . '_' . $module, '/web');
  775. $files = self::getFiles(
  776. array_merge(
  777. [
  778. BP . "/lib/web/{mage,varien}"
  779. ],
  780. $themePaths,
  781. $moduleWebPaths
  782. ),
  783. '*.js'
  784. );
  785. $result = self::composeDataSets($files);
  786. self::$_cache[$key] = $result;
  787. return $result;
  788. }
  789. /**
  790. * @param string $area
  791. * @param string $module
  792. * @param string $subFolder
  793. * @return array
  794. */
  795. private function getThemePaths($area, $module, $subFolder)
  796. {
  797. $themePaths = [];
  798. foreach ($this->themePackageList->getThemes() as $theme) {
  799. if ($area == '*' || $theme->getArea() === $area) {
  800. $themePaths[] = $theme->getPath() . $subFolder;
  801. $themePaths[] = $theme->getPath() . "/{$module}" . $subFolder;
  802. }
  803. }
  804. return $themePaths;
  805. }
  806. /**
  807. * Returns list of Static HTML files in Magento
  808. *
  809. * @param string $area
  810. * @param string $themePath
  811. * @param string $namespace
  812. * @param string $module
  813. * @return array
  814. */
  815. public function getStaticHtmlFiles($area = '*', $themePath = '*/*', $namespace = '*', $module = '*')
  816. {
  817. $key = $area . $themePath . $namespace . $module . __METHOD__;
  818. if (isset(self::$_cache[$key])) {
  819. return self::$_cache[$key];
  820. }
  821. $moduleTemplatePaths = [];
  822. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $moduleDir) {
  823. $keyInfo = explode('_', $moduleName);
  824. if ($keyInfo[0] == $namespace || $namespace == '*') {
  825. if ($keyInfo[1] == $module || $module == '*') {
  826. $moduleTemplatePaths[] = $moduleDir . "/view/{$area}/web/template";
  827. $moduleTemplatePaths[] = $moduleDir . "/view/{$area}/web/templates";
  828. }
  829. }
  830. }
  831. $themePaths = $this->getThemePaths($area, $namespace . '_' . $module, '/web/template');
  832. $files = self::getFiles(
  833. array_merge(
  834. $themePaths,
  835. $moduleTemplatePaths
  836. ),
  837. '*.html'
  838. );
  839. $result = self::composeDataSets($files);
  840. self::$_cache[$key] = $result;
  841. return $result;
  842. }
  843. /**
  844. * Get list of static view files that are subject of Magento static view files pre-processing system
  845. *
  846. * @param string $filePattern
  847. * @return array
  848. */
  849. public function getStaticPreProcessingFiles($filePattern = '*')
  850. {
  851. $key = __METHOD__ . '|' . $filePattern;
  852. if (isset(self::$_cache[$key])) {
  853. return self::$_cache[$key];
  854. }
  855. $area = '*';
  856. $locale = '*';
  857. $result = [];
  858. $moduleWebPath = [];
  859. $moduleLocalePath = [];
  860. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleDir) {
  861. $moduleWebPath[] = $moduleDir . "/view/{$area}/web";
  862. $moduleLocalePath[] = $moduleDir . "/view/{$area}/web/i18n/{$locale}";
  863. }
  864. $this->_accumulateFilesByPatterns($moduleWebPath, $filePattern, $result, '_parseModuleStatic');
  865. $this->_accumulateFilesByPatterns($moduleLocalePath, $filePattern, $result, '_parseModuleLocaleStatic');
  866. $this->accumulateThemeStaticFiles($area, $locale, $filePattern, $result);
  867. self::$_cache[$key] = $result;
  868. return $result;
  869. }
  870. /**
  871. * Accumulate files from themes
  872. *
  873. * @param string $area
  874. * @param string $locale
  875. * @param string $filePattern
  876. * @param array $result
  877. * @return void
  878. */
  879. private function accumulateThemeStaticFiles($area, $locale, $filePattern, &$result)
  880. {
  881. foreach ($this->themePackageList->getThemes() as $themePackage) {
  882. $themeArea = $themePackage->getArea();
  883. if ($area == '*' || $area == $themeArea) {
  884. $files = [];
  885. $themePath = str_replace(DIRECTORY_SEPARATOR, '/', $themePackage->getPath());
  886. $paths = [
  887. $themePath . "/web",
  888. $themePath . "/*_*/web",
  889. $themePath . "/web/i18n/{$locale}",
  890. $themePath . "/*_*/web/i18n/{$locale}"
  891. ];
  892. $this->_accumulateFilesByPatterns($paths, $filePattern, $files);
  893. $regex = '#^' . $themePath .
  894. '/((?P<module>[a-z\d]+_[a-z\d]+)/)?web/(i18n/(?P<locale>[a-z_]+)/)?(?P<path>.+)$#i';
  895. foreach ($files as $file) {
  896. if (preg_match($regex, $file, $matches)) {
  897. $result[] = [
  898. $themeArea,
  899. $themePackage->getVendor() . '/' . $themePackage->getName(),
  900. $matches['locale'],
  901. $matches['module'],
  902. $matches['path'],
  903. $file,
  904. ];
  905. } else {
  906. throw new \UnexpectedValueException("Could not parse theme static file '$file'");
  907. }
  908. }
  909. if (!$files) {
  910. $result[] = [
  911. $themeArea,
  912. $themePackage->getVendor() . '/' . $themePackage->getName(),
  913. null,
  914. null,
  915. null,
  916. null
  917. ];
  918. }
  919. }
  920. }
  921. }
  922. /**
  923. * Get all files from static library directory
  924. *
  925. * @return array
  926. */
  927. public function getStaticLibraryFiles()
  928. {
  929. $result = [];
  930. $this->_accumulateFilesByPatterns([BP . "/lib/web"], '*', $result, '_parseLibStatic');
  931. return $result;
  932. }
  933. /**
  934. * Parse file path from the absolute path of static library
  935. *
  936. * @param string $file
  937. * @param string $path
  938. * @return string
  939. */
  940. protected function _parseLibStatic($file, $path)
  941. {
  942. preg_match('/^' . preg_quote("{$path}/lib/web/", '/') . '(.+)$/i', $file, $matches);
  943. return $matches[1];
  944. }
  945. /**
  946. * Search files by the specified patterns and accumulate them, applying a callback to each found row
  947. *
  948. * @param array $patterns
  949. * @param string $filePattern
  950. * @param array $result
  951. * @param bool $subroutine
  952. * @return void
  953. */
  954. protected function _accumulateFilesByPatterns(array $patterns, $filePattern, array &$result, $subroutine = false)
  955. {
  956. $path = str_replace(DIRECTORY_SEPARATOR, '/', BP);
  957. foreach (self::getFiles($patterns, $filePattern) as $file) {
  958. $file = str_replace(DIRECTORY_SEPARATOR, '/', $file);
  959. if ($subroutine) {
  960. $result[] = $this->$subroutine($file, $path);
  961. } else {
  962. $result[] = $file;
  963. }
  964. }
  965. }
  966. /**
  967. * Parse meta-info of a static file in module
  968. *
  969. * @param string $file
  970. * @return array
  971. */
  972. protected function _parseModuleStatic($file)
  973. {
  974. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $modulePath) {
  975. if (preg_match(
  976. '/^' . preg_quote("{$modulePath}/", '/') . 'view\/([a-z]+)\/web\/(.+)$/i',
  977. $file,
  978. $matches
  979. ) === 1
  980. ) {
  981. list(, $area, $filePath) = $matches;
  982. return [$area, '', '', $moduleName, $filePath, $file];
  983. }
  984. }
  985. return [];
  986. }
  987. /**
  988. * Parse meta-info of a localized (translated) static file in module
  989. *
  990. * @param string $file
  991. * @return array
  992. */
  993. protected function _parseModuleLocaleStatic($file)
  994. {
  995. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $modulePath) {
  996. $appCode = preg_quote("{$modulePath}/", '/');
  997. if (preg_match('/^' . $appCode . 'view\/([a-z]+)\/web\/i18n\/([a-z_]+)\/(.+)$/i', $file, $matches) === 1) {
  998. list(, $area, $locale, $filePath) = $matches;
  999. return [$area, '', $locale, $moduleName, $filePath, $file];
  1000. }
  1001. }
  1002. return [];
  1003. }
  1004. /**
  1005. * Returns list of Javascript files in Magento by certain area
  1006. *
  1007. * @param string $area
  1008. * @return array
  1009. */
  1010. public function getJsFilesForArea($area)
  1011. {
  1012. $key = __METHOD__ . BP . $area;
  1013. if (isset(self::$_cache[$key])) {
  1014. return self::$_cache[$key];
  1015. }
  1016. $viewAreaPaths = [];
  1017. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleDir) {
  1018. $viewAreaPaths[] = $moduleDir . "/view/{$area}";
  1019. }
  1020. $themePaths = [];
  1021. foreach ($this->themePackageList->getThemes() as $theme) {
  1022. if ($area == '*' || $theme->getArea() === $area) {
  1023. $themePaths[] = $theme->getPath();
  1024. }
  1025. }
  1026. $paths = [
  1027. BP . "/lib/web/varien"
  1028. ];
  1029. $paths = array_merge($paths, $viewAreaPaths, $themePaths);
  1030. $files = self::getFiles($paths, '*.js');
  1031. if ($area == 'adminhtml') {
  1032. $adminhtmlPaths = [BP . "/lib/web/mage/{adminhtml,backend}"];
  1033. $files = array_merge($files, self::getFiles($adminhtmlPaths, '*.js'));
  1034. } else {
  1035. $frontendPaths = [BP . "/lib/web/mage"];
  1036. /* current structure of /lib/web/mage directory contains frontend javascript in the root,
  1037. backend javascript in subdirectories. That's why script shouldn't go recursive throught subdirectories
  1038. to get js files for frontend */
  1039. $files = array_merge($files, self::getFiles($frontendPaths, '*.js', false));
  1040. }
  1041. self::$_cache[$key] = $files;
  1042. return $files;
  1043. }
  1044. /**
  1045. * Returns list of Phtml files in Magento app directory.
  1046. *
  1047. * @param bool $withMetaInfo
  1048. * @param bool $asDataSet
  1049. * @return array
  1050. */
  1051. public function getPhtmlFiles($withMetaInfo = false, $asDataSet = true)
  1052. {
  1053. $key = __METHOD__ . (int)$withMetaInfo;
  1054. if (!isset(self::$_cache[$key])) {
  1055. $result = [];
  1056. $this->accumulateModuleTemplateFiles($withMetaInfo, $result);
  1057. $this->accumulateThemeTemplateFiles($withMetaInfo, $result);
  1058. self::$_cache[$key] = $result;
  1059. }
  1060. if ($asDataSet) {
  1061. return self::composeDataSets(self::$_cache[$key]);
  1062. }
  1063. return self::$_cache[$key];
  1064. }
  1065. /**
  1066. * Returns list of db_schema files, used by Magento application.
  1067. *
  1068. * @param string $fileNamePattern
  1069. * @param array $excludedFileNames
  1070. * @param bool $asDataSet
  1071. * @return array
  1072. * @codingStandardsIgnoreStart
  1073. */
  1074. public function getDbSchemaFiles(
  1075. $fileNamePattern = 'db_schema.xml',
  1076. $excludedFileNames = [],
  1077. $asDataSet = true
  1078. ) {
  1079. $cacheKey = __METHOD__ . '|' . $this->serializer->serialize([$fileNamePattern, $excludedFileNames, $asDataSet]);
  1080. if (!isset(self::$_cache[$cacheKey])) {
  1081. $files = $this->dirSearch->collectFiles(ComponentRegistrar::MODULE, "/etc/{$fileNamePattern}");
  1082. $files = array_filter(
  1083. $files,
  1084. function ($file) use ($excludedFileNames) {
  1085. return !in_array(basename($file), $excludedFileNames);
  1086. }
  1087. );
  1088. self::$_cache[$cacheKey] = $files;
  1089. }
  1090. if ($asDataSet) {
  1091. return self::composeDataSets(self::$_cache[$cacheKey]);
  1092. }
  1093. return self::$_cache[$cacheKey];
  1094. }
  1095. /**
  1096. * Collect templates from themes
  1097. *
  1098. * @param bool $withMetaInfo
  1099. * @param array $result
  1100. * @return void
  1101. */
  1102. private function accumulateThemeTemplateFiles($withMetaInfo, array &$result)
  1103. {
  1104. foreach ($this->themePackageList->getThemes() as $theme) {
  1105. $files = [];
  1106. $this->_accumulateFilesByPatterns(
  1107. [$theme->getPath() . '/*_*/templates'],
  1108. '*.phtml',
  1109. $files
  1110. );
  1111. if ($withMetaInfo) {
  1112. $regex = '#^' . str_replace(DIRECTORY_SEPARATOR, '/', $theme->getPath())
  1113. . '/(?P<module>[a-z\d]+_[a-z\d]+)/templates/(?P<path>.+)$#i';
  1114. foreach ($files as $file) {
  1115. if (preg_match($regex, $file, $matches)) {
  1116. $result[] = [
  1117. $theme->getArea(),
  1118. $theme->getVendor() . '/' . $theme->getName(),
  1119. $matches['module'],
  1120. $matches['path'],
  1121. $file,
  1122. ];
  1123. } else {
  1124. echo $regex . " - " . $file . "\n";
  1125. throw new \UnexpectedValueException("Could not parse theme template file '$file'");
  1126. }
  1127. }
  1128. } else {
  1129. $result = array_merge($result, $files);
  1130. }
  1131. }
  1132. }
  1133. /**
  1134. * Collect templates from modules
  1135. *
  1136. * @param bool $withMetaInfo
  1137. * @param array $result
  1138. * @return void
  1139. */
  1140. private function accumulateModuleTemplateFiles($withMetaInfo, array &$result)
  1141. {
  1142. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $moduleDir) {
  1143. $files = [];
  1144. $this->_accumulateFilesByPatterns(
  1145. [$moduleDir . "/view/*/templates"],
  1146. '*.phtml',
  1147. $files
  1148. );
  1149. if ($withMetaInfo) {
  1150. $modulePath = str_replace(DIRECTORY_SEPARATOR, '/', preg_quote($moduleDir, '#'));
  1151. $regex = '#^' . $modulePath . '/view/(?P<area>[a-z]+)/templates/(?P<path>.+)$#i';
  1152. foreach ($files as $file) {
  1153. if (preg_match($regex, $file, $matches)) {
  1154. $result[] = [
  1155. $matches['area'],
  1156. '',
  1157. $moduleName,
  1158. $matches['path'],
  1159. $file,
  1160. ];
  1161. } else {
  1162. throw new \UnexpectedValueException("Could not parse module template file '$file'");
  1163. }
  1164. }
  1165. } else {
  1166. $result = array_merge($result, $files);
  1167. }
  1168. }
  1169. }
  1170. /**
  1171. * Returns list of email template files
  1172. *
  1173. * @return array
  1174. */
  1175. public function getEmailTemplates()
  1176. {
  1177. $key = __METHOD__;
  1178. if (isset(self::$_cache[$key])) {
  1179. return self::$_cache[$key];
  1180. }
  1181. $moduleEmailPaths = [];
  1182. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleDir) {
  1183. $moduleEmailPaths[] = $moduleDir . "/view/email";
  1184. }
  1185. $files = self::getFiles($moduleEmailPaths, '*.html');
  1186. $result = self::composeDataSets($files);
  1187. self::$_cache[$key] = $result;
  1188. return $result;
  1189. }
  1190. /**
  1191. * Return list of all files. The list excludes tool-specific files
  1192. * (e.g. Git, IDE) or temp files (e.g. in "var/").
  1193. *
  1194. * @return array
  1195. */
  1196. public function getAllFiles()
  1197. {
  1198. $key = __METHOD__ . BP;
  1199. if (isset(self::$_cache[$key])) {
  1200. return self::$_cache[$key];
  1201. }
  1202. $paths = array_merge(
  1203. [BP . '/app', BP . '/dev', BP . '/lib', BP . '/pub'],
  1204. $this->componentRegistrar->getPaths(ComponentRegistrar::LANGUAGE),
  1205. $this->componentRegistrar->getPaths(ComponentRegistrar::THEME),
  1206. $this->getPaths()
  1207. );
  1208. $subFiles = self::getFiles($paths, '*');
  1209. $rootFiles = glob(BP . '/*', GLOB_NOSORT);
  1210. $rootFiles = array_filter(
  1211. $rootFiles,
  1212. function ($file) {
  1213. return is_file($file);
  1214. }
  1215. );
  1216. $result = array_merge($rootFiles, $subFiles);
  1217. $result = self::composeDataSets($result);
  1218. self::$_cache[$key] = $result;
  1219. return $result;
  1220. }
  1221. /**
  1222. * Retrieve all files in folders and sub-folders that match pattern (glob syntax)
  1223. *
  1224. * @param array $dirPatterns
  1225. * @param string $fileNamePattern
  1226. * @param bool $recursive
  1227. * @return array
  1228. */
  1229. public static function getFiles(array $dirPatterns, $fileNamePattern, $recursive = true)
  1230. {
  1231. $result = [];
  1232. foreach ($dirPatterns as $oneDirPattern) {
  1233. $oneDirPattern = str_replace('\\', '/', $oneDirPattern);
  1234. $entriesInDir = Glob::glob("{$oneDirPattern}/{$fileNamePattern}", Glob::GLOB_NOSORT | Glob::GLOB_BRACE);
  1235. $subDirs = Glob::glob("{$oneDirPattern}/*", Glob::GLOB_ONLYDIR | Glob::GLOB_NOSORT | Glob::GLOB_BRACE);
  1236. $filesInDir = array_diff($entriesInDir, $subDirs);
  1237. if ($recursive) {
  1238. $filesInSubDir = self::getFiles($subDirs, $fileNamePattern);
  1239. $result = array_merge($result, $filesInDir, $filesInSubDir);
  1240. }
  1241. }
  1242. return $result;
  1243. }
  1244. /**
  1245. * Look for DI config through the system
  1246. *
  1247. * @param bool $asDataSet
  1248. * @return array
  1249. */
  1250. public function getDiConfigs($asDataSet = false)
  1251. {
  1252. $primaryConfigs = Glob::glob(BP . '/app/etc/{di.xml,*/di.xml}', Glob::GLOB_BRACE);
  1253. $moduleConfigs = [];
  1254. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleDir) {
  1255. $moduleConfigs = array_merge(
  1256. $moduleConfigs,
  1257. Glob::glob($moduleDir . '/etc/{di,*/di}.xml', Glob::GLOB_BRACE)
  1258. );
  1259. }
  1260. $configs = array_merge($primaryConfigs, $moduleConfigs);
  1261. if ($asDataSet) {
  1262. $output = [];
  1263. foreach ($configs as $file) {
  1264. $output[$file] = [$file];
  1265. }
  1266. return $output;
  1267. }
  1268. return $configs;
  1269. }
  1270. /**
  1271. * Get module and library paths
  1272. *
  1273. * @return array
  1274. */
  1275. private function getPaths()
  1276. {
  1277. $directories = [];
  1278. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $fullModuleDir) {
  1279. $directories[] = $fullModuleDir;
  1280. }
  1281. foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::LIBRARY) as $libraryDir) {
  1282. $directories[] = $libraryDir;
  1283. }
  1284. return $directories;
  1285. }
  1286. /**
  1287. * Check if specified class exists
  1288. *
  1289. * @param string $class
  1290. * @param string &$path
  1291. * @return bool
  1292. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1293. */
  1294. public function classFileExists($class, &$path = '')
  1295. {
  1296. if ($class[0] == '\\') {
  1297. $class = substr($class, 1);
  1298. }
  1299. $classParts = explode('\\', $class);
  1300. $className = array_pop($classParts);
  1301. $namespace = implode('\\', $classParts);
  1302. $path = implode('/', explode('\\', $class)) . '.php';
  1303. $directories = [
  1304. '/dev/tools',
  1305. '/dev/tests/api-functional/framework',
  1306. '/dev/tests/setup-integration/framework',
  1307. '/dev/tests/integration/framework',
  1308. '/dev/tests/integration/framework/tests/unit/testsuite',
  1309. '/dev/tests/integration/testsuite',
  1310. '/dev/tests/integration/testsuite/Magento/Test/Integrity',
  1311. '/dev/tests/static/framework',
  1312. '/dev/tests/static/testsuite',
  1313. '/dev/tests/functional/tests/app',
  1314. '/dev/tests/functional/lib',
  1315. '/dev/tests/functional/vendor/magento/mtf',
  1316. '/setup/src'
  1317. ];
  1318. foreach ($directories as $key => $dir) {
  1319. $directories[$key] = BP . $dir;
  1320. }
  1321. $directories = array_merge($directories, $this->getPaths());
  1322. foreach ($directories as $dir) {
  1323. $fullPath = $dir . '/' . $path;
  1324. if ($this->classFileExistsCheckContent($fullPath, $namespace, $className)) {
  1325. return true;
  1326. }
  1327. $classParts = explode('/', $path, 3);
  1328. if (count($classParts) >= 3) {
  1329. // Check if it's PSR-4 class with trimmed vendor and package name parts
  1330. $trimmedFullPath = $dir . '/' . $classParts[2];
  1331. if ($this->classFileExistsCheckContent($trimmedFullPath, $namespace, $className)) {
  1332. return true;
  1333. }
  1334. }
  1335. $classParts = explode('/', $path, 4);
  1336. if (count($classParts) >= 4) {
  1337. // Check if it's a library under framework directory
  1338. $trimmedFullPath = $dir . '/' . $classParts[3];
  1339. if ($this->classFileExistsCheckContent($trimmedFullPath, $namespace, $className)) {
  1340. return true;
  1341. }
  1342. $trimmedFullPath = $dir . '/' . $classParts[2] . '/' . $classParts[3];
  1343. if ($this->classFileExistsCheckContent($trimmedFullPath, $namespace, $className)) {
  1344. return true;
  1345. }
  1346. }
  1347. }
  1348. return false;
  1349. }
  1350. /**
  1351. * Helper function for classFileExists to check file content
  1352. *
  1353. * @param string $fullPath
  1354. * @param string $namespace
  1355. * @param string $className
  1356. * @return bool
  1357. */
  1358. private function classFileExistsCheckContent($fullPath, $namespace, $className)
  1359. {
  1360. /**
  1361. * Use realpath() instead of file_exists() to avoid incorrect work on Windows
  1362. * because of case insensitivity of file names
  1363. * Note that realpath() automatically changes directory separator to the OS-native
  1364. * Since realpath won't work with symlinks we also check file_exists if realpath failed
  1365. */
  1366. if (realpath($fullPath) == str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $fullPath)
  1367. || file_exists($fullPath)
  1368. ) {
  1369. $fileContent = file_get_contents($fullPath);
  1370. if (strpos($fileContent, 'namespace ' . $namespace) !== false
  1371. && (strpos($fileContent, 'class ' . $className) !== false
  1372. || strpos($fileContent, 'interface ' . $className) !== false
  1373. || strpos($fileContent, 'trait ' . $className) !== false)
  1374. ) {
  1375. return true;
  1376. }
  1377. }
  1378. return false;
  1379. }
  1380. /**
  1381. * Return list of declared namespaces
  1382. *
  1383. * @return array
  1384. */
  1385. public function getNamespaces()
  1386. {
  1387. $key = __METHOD__;
  1388. if (isset(self::$_cache[$key])) {
  1389. return self::$_cache[$key];
  1390. }
  1391. $result = [];
  1392. foreach (array_keys($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE)) as $moduleName) {
  1393. $namespace = explode('_', $moduleName)[0];
  1394. if (!in_array($namespace, $result) && $namespace !== 'Zend') {
  1395. $result[] = $namespace;
  1396. }
  1397. }
  1398. self::$_cache[$key] = $result;
  1399. return $result;
  1400. }
  1401. /**
  1402. * @param string $namespace
  1403. * @param string $module
  1404. * @param string $file
  1405. * @return string
  1406. */
  1407. public function getModuleFile($namespace, $module, $file)
  1408. {
  1409. return $this->componentRegistrar->getPath(ComponentRegistrar::MODULE, $namespace . '_' . $module) .
  1410. '/' . $file;
  1411. }
  1412. /**
  1413. * Returns array of PHP-files for specified module
  1414. *
  1415. * @param string $module
  1416. * @param bool $asDataSet
  1417. * @return array
  1418. */
  1419. public function getModulePhpFiles($module, $asDataSet = true)
  1420. {
  1421. $key = __METHOD__ . "/{$module}";
  1422. if (!isset(self::$_cache[$key])) {
  1423. $files = self::getFiles(
  1424. [$this->componentRegistrar->getPath(ComponentRegistrar::MODULE, 'Magento_' . $module)],
  1425. '*.php'
  1426. );
  1427. self::$_cache[$key] = $files;
  1428. }
  1429. if ($asDataSet) {
  1430. return self::composeDataSets(self::$_cache[$key]);
  1431. }
  1432. return self::$_cache[$key];
  1433. }
  1434. /**
  1435. * Returns array of composer.json for components of specified type
  1436. *
  1437. * @param string $componentType
  1438. * @param bool $asDataSet
  1439. * @return array
  1440. */
  1441. public function getComposerFiles($componentType, $asDataSet = true)
  1442. {
  1443. $key = __METHOD__ . '|' . implode('|', [$componentType, $asDataSet]);
  1444. if (!isset(self::$_cache[$key])) {
  1445. $excludes = $componentType == ComponentRegistrar::MODULE ? $this->getModuleTestDirsRegex() : [];
  1446. $files = $this->getFilesSubset(
  1447. $this->componentRegistrar->getPaths($componentType),
  1448. 'composer.json',
  1449. $excludes
  1450. );
  1451. self::$_cache[$key] = $files;
  1452. }
  1453. if ($asDataSet) {
  1454. return self::composeDataSets(self::$_cache[$key]);
  1455. }
  1456. return self::$_cache[$key];
  1457. }
  1458. /**
  1459. * Read all text files by specified glob pattern and combine them into an array of valid files/directories
  1460. *
  1461. * The Magento root path is prepended to all (non-empty) entries
  1462. *
  1463. * @param string $globPattern
  1464. * @return array
  1465. * @throws \Exception if any of the patterns don't return any result
  1466. */
  1467. public function readLists($globPattern)
  1468. {
  1469. $patterns = [];
  1470. foreach (glob($globPattern) as $list) {
  1471. $patterns = array_merge($patterns, file($list, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES));
  1472. }
  1473. // Expand glob patterns
  1474. $result = [];
  1475. $incorrectPatterns = [];
  1476. foreach ($patterns as $pattern) {
  1477. if (0 === strpos($pattern, '#')) {
  1478. continue;
  1479. }
  1480. $patternParts = explode(' ', $pattern);
  1481. if (count($patternParts) == 3) {
  1482. list($componentType, $componentName, $pathPattern) = $patternParts;
  1483. $files = $this->getPathByComponentPattern($componentType, $componentName, $pathPattern);
  1484. } elseif (count($patternParts) == 1) {
  1485. /**
  1486. * Note that glob() for directories will be returned as is,
  1487. * but passing directory is supported by the tools (phpcpd, phpmd, phpcs)
  1488. */
  1489. $files = Glob::glob(BP . '/' . $pattern, Glob::GLOB_BRACE);
  1490. } else {
  1491. throw new \UnexpectedValueException(
  1492. "Incorrect pattern record '$pattern'. Supported formats: "
  1493. . "'<componentType> <componentName> <glob_pattern>' or '<glob_pattern>'"
  1494. );
  1495. }
  1496. if (empty($files)) {
  1497. $incorrectPatterns[] = $pattern;
  1498. }
  1499. $result = array_merge($result, $files);
  1500. }
  1501. if (!empty($incorrectPatterns)) {
  1502. throw new \Exception("The following patterns didn't return any result:\n" . join("\n", $incorrectPatterns));
  1503. }
  1504. return $result;
  1505. }
  1506. /**
  1507. * Get paths by pattern for specified component component
  1508. *
  1509. * @param string $componentType
  1510. * @param string $componentName
  1511. * @param string $pathPattern
  1512. * @return array
  1513. */
  1514. private function getPathByComponentPattern($componentType, $componentName, $pathPattern)
  1515. {
  1516. $files = [];
  1517. if ($componentType == '*') {
  1518. $componentTypes = [
  1519. ComponentRegistrar::MODULE,
  1520. ComponentRegistrar::LIBRARY,
  1521. ComponentRegistrar::THEME,
  1522. ComponentRegistrar::LANGUAGE,
  1523. ];
  1524. } else {
  1525. $componentTypes = [$componentType];
  1526. }
  1527. foreach ($componentTypes as $type) {
  1528. if ($componentName == '*') {
  1529. $files = array_merge($files, $this->dirSearch->collectFiles($type, $pathPattern));
  1530. } else {
  1531. $componentDir = $this->componentRegistrar->getPath($type, $componentName);
  1532. if (!empty($componentDir)) {
  1533. $files = array_merge($files, Glob::glob($componentDir . '/' . $pathPattern, Glob::GLOB_BRACE));
  1534. }
  1535. }
  1536. }
  1537. return $files;
  1538. }
  1539. /**
  1540. * Check module existence
  1541. *
  1542. * @param string $moduleName
  1543. * @return bool
  1544. */
  1545. public function isModuleExists($moduleName)
  1546. {
  1547. $key = __METHOD__ . "/{$moduleName}";
  1548. if (!isset(self::$_cache[$key])) {
  1549. self::$_cache[$key] = file_exists(
  1550. $this->componentRegistrar->getPath(ComponentRegistrar::MODULE, $moduleName)
  1551. );
  1552. }
  1553. return self::$_cache[$key];
  1554. }
  1555. /**
  1556. * Returns list of files in a given directory, minus files in specifically excluded directories.
  1557. *
  1558. * @param array $dirPatterns Directories to search in
  1559. * @param string $fileNamePattern Pattern for filename
  1560. * @param string|array $excludes Subdirectories to exlude, represented as regex
  1561. * @return array Files in $dirPatterns but not in $excludes
  1562. */
  1563. protected function getFilesSubset(array $dirPatterns, $fileNamePattern, $excludes)
  1564. {
  1565. if (!is_array($excludes)) {
  1566. $excludes = [$excludes];
  1567. }
  1568. $fileSet = self::getFiles($dirPatterns, $fileNamePattern);
  1569. foreach ($excludes as $excludeRegex) {
  1570. $fileSet = preg_grep($excludeRegex, $fileSet, PREG_GREP_INVERT);
  1571. }
  1572. return $fileSet;
  1573. }
  1574. /**
  1575. * Get list of PHP files in setup application
  1576. *
  1577. * @param int $flags
  1578. * @return array
  1579. */
  1580. private function getSetupPhpFiles($flags = null)
  1581. {
  1582. $files = [];
  1583. $setupAppPath = BP . '/setup';
  1584. if ($flags & self::INCLUDE_SETUP && file_exists($setupAppPath)) {
  1585. $regexIterator = $this->regexIteratorFactory->create(
  1586. $setupAppPath,
  1587. '/.*php$/'
  1588. );
  1589. foreach ($regexIterator as $file) {
  1590. $files[] = $file[0];
  1591. }
  1592. }
  1593. return $files;
  1594. }
  1595. }