Filesystem.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Deploy\Model;
  7. use Symfony\Component\Console\Output\OutputInterface;
  8. use Magento\Framework\App\State;
  9. use Magento\Framework\App\DeploymentConfig\Writer;
  10. use Magento\Framework\App\Filesystem\DirectoryList;
  11. use Magento\Framework\Exception\LocalizedException;
  12. use Magento\Framework\Validator\Locale;
  13. use Magento\User\Model\ResourceModel\User\Collection as UserCollection;
  14. /**
  15. * Generate static files, compile
  16. *
  17. * Сlear generated/code, generated/metadata/, var/view_preprocessed and pub/static directories
  18. *
  19. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  20. */
  21. class Filesystem
  22. {
  23. /**
  24. * File access permissions
  25. *
  26. * @deprecated As magento2 doesn't control indirectly the access permissions to the files anymore.
  27. * Access permissions to the files are set during deploy Magento 2, directly after
  28. * uploading code of Magento. Also it is possible to specify the value
  29. * of inverse mask for setting access permissions to files generated by Magento.
  30. * @link https://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html
  31. * @link https://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html
  32. */
  33. const PERMISSIONS_FILE = 0640;
  34. /**
  35. * Directory access permissions
  36. *
  37. * @deprecated As magento2 doesn't control indirectly the access permissions to the directories anymore.
  38. * Access permissions to the directories are set during deploy Magento 2, directly after
  39. * uploading code of Magento. Also it is possible to specify the value
  40. * of inverse mask for setting access permissions to directories generated by Magento.
  41. * @link https://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html
  42. * @link https://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html
  43. */
  44. const PERMISSIONS_DIR = 0750;
  45. /**
  46. * Default theme when no theme is stored in configuration
  47. */
  48. const DEFAULT_THEME = 'Magento/blank';
  49. /**
  50. * @var \Magento\Framework\App\DeploymentConfig\Writer
  51. */
  52. private $writer;
  53. /**
  54. * @var \Magento\Framework\App\DeploymentConfig\Reader
  55. */
  56. private $reader;
  57. /**
  58. * @var \Magento\Framework\ObjectManagerInterface
  59. */
  60. private $objectManager;
  61. /**
  62. * @var \Magento\Framework\Filesystem
  63. */
  64. private $filesystem;
  65. /**
  66. * @var \Magento\Framework\App\Filesystem\DirectoryList
  67. */
  68. private $directoryList;
  69. /**
  70. * @var \Magento\Framework\Filesystem\Driver\File
  71. */
  72. private $driverFile;
  73. /**
  74. * @var \Magento\Store\Model\Config\StoreView
  75. */
  76. private $storeView;
  77. /**
  78. * @var \Magento\Framework\ShellInterface
  79. */
  80. private $shell;
  81. /**
  82. * @var string
  83. */
  84. private $functionCallPath;
  85. /**
  86. * @var UserCollection
  87. */
  88. private $userCollection;
  89. /**
  90. * @var Locale
  91. */
  92. private $locale;
  93. /**
  94. * @param \Magento\Framework\App\DeploymentConfig\Writer $writer
  95. * @param \Magento\Framework\App\DeploymentConfig\Reader $reader
  96. * @param \Magento\Framework\ObjectManagerInterface $objectManager
  97. * @param \Magento\Framework\Filesystem $filesystem
  98. * @param \Magento\Framework\App\Filesystem\DirectoryList $directoryList
  99. * @param \Magento\Framework\Filesystem\Driver\File $driverFile
  100. * @param \Magento\Store\Model\Config\StoreView $storeView
  101. * @param \Magento\Framework\ShellInterface $shell
  102. * @param UserCollection|null $userCollection
  103. * @param Locale|null $locale
  104. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  105. */
  106. public function __construct(
  107. \Magento\Framework\App\DeploymentConfig\Writer $writer,
  108. \Magento\Framework\App\DeploymentConfig\Reader $reader,
  109. \Magento\Framework\ObjectManagerInterface $objectManager,
  110. \Magento\Framework\Filesystem $filesystem,
  111. \Magento\Framework\App\Filesystem\DirectoryList $directoryList,
  112. \Magento\Framework\Filesystem\Driver\File $driverFile,
  113. \Magento\Store\Model\Config\StoreView $storeView,
  114. \Magento\Framework\ShellInterface $shell,
  115. UserCollection $userCollection = null,
  116. Locale $locale = null
  117. ) {
  118. $this->writer = $writer;
  119. $this->reader = $reader;
  120. $this->objectManager = $objectManager;
  121. $this->filesystem = $filesystem;
  122. $this->directoryList = $directoryList;
  123. $this->driverFile = $driverFile;
  124. $this->storeView = $storeView;
  125. $this->shell = $shell;
  126. $this->userCollection = $userCollection ?: $this->objectManager->get(UserCollection::class);
  127. $this->locale = $locale ?: $this->objectManager->get(Locale::class);
  128. $this->functionCallPath =
  129. PHP_BINARY . ' -f ' . BP . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'magento ';
  130. }
  131. /**
  132. * Regenerate static
  133. *
  134. * @param OutputInterface $output
  135. * @return void
  136. * @throws LocalizedException
  137. * @throws \Exception
  138. */
  139. public function regenerateStatic(
  140. OutputInterface $output
  141. ) {
  142. // Сlear generated/code, generated/metadata/, var/view_preprocessed and pub/static directories
  143. $this->cleanupFilesystem(
  144. [
  145. DirectoryList::CACHE,
  146. DirectoryList::GENERATED_CODE,
  147. DirectoryList::GENERATED_METADATA,
  148. DirectoryList::TMP_MATERIALIZATION_DIR,
  149. DirectoryList::STATIC_VIEW
  150. ]
  151. );
  152. $this->reinitCacheDirectories();
  153. // Trigger code generation
  154. $this->compile($output);
  155. $this->reinitCacheDirectories();
  156. // Trigger static assets compilation and deployment
  157. $this->deployStaticContent($output);
  158. }
  159. /**
  160. * Deploy static content
  161. *
  162. * @param OutputInterface $output
  163. * @return void
  164. * @throws \Exception
  165. */
  166. protected function deployStaticContent(
  167. OutputInterface $output
  168. ) {
  169. $output->writeln('Starting deployment of static content');
  170. $cmd = $this->functionCallPath . 'setup:static-content:deploy -f '
  171. . implode(' ', $this->getUsedLocales());
  172. /**
  173. * @todo eliminate exec
  174. */
  175. try {
  176. $execOutput = $this->shell->execute($cmd);
  177. } catch (LocalizedException $e) {
  178. $output->writeln('Something went wrong while deploying static content. See the error log for details.');
  179. throw $e;
  180. }
  181. $output->writeln($execOutput);
  182. $output->writeln('Deployment of static content complete');
  183. }
  184. /**
  185. * Get admin user locales
  186. *
  187. * @return array
  188. */
  189. private function getAdminUserInterfaceLocales()
  190. {
  191. $locales = [];
  192. foreach ($this->userCollection as $user) {
  193. $locales[] = $user->getInterfaceLocale();
  194. }
  195. return $locales;
  196. }
  197. /**
  198. * Get used store and admin user locales.
  199. *
  200. * @return array
  201. * @throws \InvalidArgumentException if unknown locale is provided by the store configuration
  202. * @throws \Magento\Framework\Exception\FileSystemException
  203. */
  204. private function getUsedLocales()
  205. {
  206. $usedLocales = array_merge(
  207. $this->storeView->retrieveLocales(),
  208. $this->getAdminUserInterfaceLocales()
  209. );
  210. return array_map(
  211. function ($locale) {
  212. if (!$this->locale->isValid($locale)) {
  213. throw new \InvalidArgumentException(
  214. $locale . ' argument has invalid value, run info:language:list for list of available locales'
  215. );
  216. }
  217. return $locale;
  218. },
  219. array_unique($usedLocales)
  220. );
  221. }
  222. /**
  223. * Runs compiler
  224. *
  225. * @param OutputInterface $output
  226. * @return void
  227. * @throws LocalizedException
  228. */
  229. protected function compile(OutputInterface $output)
  230. {
  231. $output->writeln('Starting compilation');
  232. $cmd = $this->functionCallPath . 'setup:di:compile';
  233. /**
  234. * exec command is necessary for now to isolate the autoloaders in the compiler from the memory state
  235. * of this process, which would prevent some classes from being generated
  236. *
  237. * @todo eliminate exec
  238. */
  239. try {
  240. $execOutput = $this->shell->execute($cmd);
  241. } catch (LocalizedException $e) {
  242. $output->writeln('Something went wrong while compiling generated code. See the error log for details.');
  243. throw $e;
  244. }
  245. $output->writeln($execOutput);
  246. $output->writeln('Compilation complete');
  247. }
  248. /**
  249. * Deletes specified directories by code
  250. *
  251. * @param array $directoryCodeList
  252. * @return void
  253. * @throws \Magento\Framework\Exception\FileSystemException
  254. */
  255. public function cleanupFilesystem($directoryCodeList)
  256. {
  257. $excludePatterns = ['#.htaccess#', '#deployed_version.txt#'];
  258. foreach ($directoryCodeList as $code) {
  259. if ($code == DirectoryList::STATIC_VIEW) {
  260. $directoryPath = $this->directoryList->getPath(DirectoryList::STATIC_VIEW);
  261. if ($this->driverFile->isExists($directoryPath)) {
  262. $files = $this->driverFile->readDirectory($directoryPath);
  263. foreach ($files as $file) {
  264. foreach ($excludePatterns as $pattern) {
  265. if (preg_match($pattern, $file)) {
  266. continue 2;
  267. }
  268. }
  269. if ($this->driverFile->isFile($file)) {
  270. $this->driverFile->deleteFile($file);
  271. } else {
  272. $this->driverFile->deleteDirectory($file);
  273. }
  274. }
  275. }
  276. } else {
  277. $this->filesystem->getDirectoryWrite($code)
  278. ->delete();
  279. }
  280. }
  281. }
  282. /**
  283. * Changes permissions for directories by their code.
  284. *
  285. * @param array $directoryCodeList
  286. * @param int $dirPermissions
  287. * @param int $filePermissions
  288. * @return void
  289. * @deprecated 100.0.6 As magento2 doesn't control indirectly
  290. * the access permissions to the files and directories anymore.
  291. * Access permissions to the files and directories are set during deploy Magento 2, directly after
  292. * uploading code of Magento. Also it is possible to specify the value
  293. * of inverse mask for setting access permissions to files and directories generated by Magento.
  294. * @link https://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html
  295. * @link https://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html
  296. * @throws \Magento\Framework\Exception\FileSystemException
  297. */
  298. protected function changePermissions($directoryCodeList, $dirPermissions, $filePermissions)
  299. {
  300. foreach ($directoryCodeList as $code) {
  301. $directoryPath = $this->directoryList->getPath($code);
  302. if ($this->driverFile->isExists($directoryPath)) {
  303. $this->filesystem->getDirectoryWrite($code)
  304. ->changePermissionsRecursively('', $dirPermissions, $filePermissions);
  305. } else {
  306. $this->driverFile->createDirectory($directoryPath, $dirPermissions);
  307. }
  308. }
  309. }
  310. /**
  311. * Change permissions on static resources
  312. *
  313. * @return void
  314. * @deprecated 100.0.6 As magento2 doesn't control indirectly the access permissions to the files
  315. * and directories anymore.
  316. * Access permissions to the files and directories are set during deploy Magento 2, directly after
  317. * uploading code of Magento. Also it is possible to specify the value
  318. * of inverse mask for setting access permissions to files and directories generated by Magento.
  319. * @link https://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html
  320. * @link https://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html
  321. * @throws \Magento\Framework\Exception\FileSystemException
  322. */
  323. public function lockStaticResources()
  324. {
  325. // Lock /generated/code, /generated/metadata/ and /var/view_preprocessed directories
  326. $this->changePermissions(
  327. [
  328. DirectoryList::GENERATED_CODE,
  329. DirectoryList::GENERATED_METADATA,
  330. DirectoryList::TMP_MATERIALIZATION_DIR,
  331. ],
  332. self::PERMISSIONS_DIR,
  333. self::PERMISSIONS_FILE
  334. );
  335. }
  336. /**
  337. * Flush cache and restore the basic cache directories.
  338. *
  339. * @throws LocalizedException
  340. */
  341. private function reinitCacheDirectories()
  342. {
  343. $command = $this->functionCallPath . 'cache:flush';
  344. $this->shell->execute($command);
  345. }
  346. }