FilePermissions.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Setup;
  7. use Magento\Framework\App\Filesystem\DirectoryList;
  8. use Magento\Framework\Backup\Filesystem\Iterator\Filter;
  9. use Magento\Framework\Filesystem;
  10. use Magento\Framework\Filesystem\Filter\ExcludeFilter;
  11. use Magento\Framework\App\State;
  12. use Magento\Framework\App\ObjectManager;
  13. /**
  14. * Checks permissions to files and folders.
  15. */
  16. class FilePermissions
  17. {
  18. /**
  19. * @var Filesystem
  20. */
  21. protected $filesystem;
  22. /**
  23. * @var DirectoryList
  24. */
  25. protected $directoryList;
  26. /**
  27. * @var State
  28. */
  29. private $state;
  30. /**
  31. * List of required writable directories for installation
  32. *
  33. * @var array
  34. */
  35. protected $installationWritableDirectories = [];
  36. /**
  37. * List of recommended non-writable directories for application
  38. *
  39. * @var array
  40. */
  41. protected $applicationNonWritableDirectories = [];
  42. /**
  43. * List of current writable directories for installation
  44. *
  45. * @var array
  46. */
  47. protected $installationCurrentWritableDirectories = [];
  48. /**
  49. * List of current non-writable directories for application
  50. *
  51. * @var array
  52. */
  53. protected $applicationCurrentNonWritableDirectories = [];
  54. /**
  55. * List of non-writable paths in a specified directory
  56. *
  57. * @var array
  58. */
  59. protected $nonWritablePathsInDirectories = [];
  60. /**
  61. * @param Filesystem $filesystem
  62. * @param DirectoryList $directoryList
  63. * @param State $state
  64. */
  65. public function __construct(
  66. Filesystem $filesystem,
  67. DirectoryList $directoryList,
  68. State $state = null
  69. ) {
  70. $this->filesystem = $filesystem;
  71. $this->directoryList = $directoryList;
  72. $this->state = $state ?: ObjectManager::getInstance()->get(State::class);
  73. }
  74. /**
  75. * Retrieve list of required writable directories for installation
  76. *
  77. * @return array
  78. */
  79. public function getInstallationWritableDirectories()
  80. {
  81. if (!$this->installationWritableDirectories) {
  82. $data = [
  83. DirectoryList::CONFIG,
  84. DirectoryList::VAR_DIR,
  85. DirectoryList::MEDIA,
  86. DirectoryList::STATIC_VIEW,
  87. ];
  88. if ($this->state->getMode() !== State::MODE_PRODUCTION) {
  89. $data[] = DirectoryList::GENERATED;
  90. }
  91. foreach ($data as $code) {
  92. $this->installationWritableDirectories[$code] = $this->directoryList->getPath($code);
  93. }
  94. }
  95. return array_values($this->installationWritableDirectories);
  96. }
  97. /**
  98. * Retrieve list of recommended non-writable directories for application
  99. *
  100. * @return array
  101. */
  102. public function getApplicationNonWritableDirectories()
  103. {
  104. if (!$this->applicationNonWritableDirectories) {
  105. $data = [
  106. DirectoryList::CONFIG,
  107. ];
  108. foreach ($data as $code) {
  109. $this->applicationNonWritableDirectories[$code] = $this->directoryList->getPath($code);
  110. }
  111. }
  112. return array_values($this->applicationNonWritableDirectories);
  113. }
  114. /**
  115. * Retrieve list of currently writable directories for installation
  116. *
  117. * @return array
  118. */
  119. public function getInstallationCurrentWritableDirectories()
  120. {
  121. if (!$this->installationCurrentWritableDirectories) {
  122. foreach ($this->installationWritableDirectories as $code => $path) {
  123. if ($this->isWritable($code)) {
  124. if ($this->checkRecursiveDirectories($path)) {
  125. $this->installationCurrentWritableDirectories[] = $path;
  126. }
  127. } else {
  128. $this->nonWritablePathsInDirectories[$path] = [$path];
  129. }
  130. }
  131. }
  132. return $this->installationCurrentWritableDirectories;
  133. }
  134. /**
  135. * Check all sub-directories and files except for generated/code and generated/metadata
  136. *
  137. * @param string $directory
  138. * @return bool
  139. */
  140. private function checkRecursiveDirectories($directory)
  141. {
  142. /** @var $directoryIterator \RecursiveIteratorIterator */
  143. $directoryIterator = new \RecursiveIteratorIterator(
  144. new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::SKIP_DOTS),
  145. \RecursiveIteratorIterator::CHILD_FIRST
  146. );
  147. $noWritableFilesFolders = [
  148. $this->directoryList->getPath(DirectoryList::GENERATED_CODE) . '/',
  149. $this->directoryList->getPath(DirectoryList::GENERATED_METADATA) . '/',
  150. ];
  151. $directoryIterator = new Filter($directoryIterator, $noWritableFilesFolders);
  152. $directoryIterator = new ExcludeFilter(
  153. $directoryIterator,
  154. [
  155. $this->directoryList->getPath(DirectoryList::SESSION) . '/',
  156. ]
  157. );
  158. $directoryIterator->setMaxDepth(1);
  159. $foundNonWritable = false;
  160. try {
  161. foreach ($directoryIterator as $subDirectory) {
  162. if (!$subDirectory->isWritable() && !$subDirectory->isLink()) {
  163. $this->nonWritablePathsInDirectories[$directory][] = $subDirectory->getPathname();
  164. $foundNonWritable = true;
  165. }
  166. }
  167. } catch (\UnexpectedValueException $e) {
  168. return false;
  169. }
  170. return !$foundNonWritable;
  171. }
  172. /**
  173. * Retrieve list of currently non-writable directories for application
  174. *
  175. * @return array
  176. */
  177. public function getApplicationCurrentNonWritableDirectories()
  178. {
  179. if (!$this->applicationCurrentNonWritableDirectories) {
  180. foreach ($this->applicationNonWritableDirectories as $code => $path) {
  181. if ($this->isNonWritable($code)) {
  182. $this->applicationCurrentNonWritableDirectories[] = $path;
  183. }
  184. }
  185. }
  186. return $this->applicationCurrentNonWritableDirectories;
  187. }
  188. /**
  189. * Checks if directory is writable by given directory code
  190. *
  191. * @param string $code
  192. * @return bool
  193. */
  194. protected function isWritable($code)
  195. {
  196. $directory = $this->filesystem->getDirectoryWrite($code);
  197. return $this->isReadableDirectory($directory) && $directory->isWritable();
  198. }
  199. /**
  200. * Checks if directory is non-writable by given directory code
  201. *
  202. * @param string $code
  203. * @return bool
  204. */
  205. protected function isNonWritable($code)
  206. {
  207. $directory = $this->filesystem->getDirectoryWrite($code);
  208. return $this->isReadableDirectory($directory) && !$directory->isWritable();
  209. }
  210. /**
  211. * Checks if directory exists and is readable
  212. *
  213. * @param \Magento\Framework\Filesystem\Directory\WriteInterface $directory
  214. * @return bool
  215. */
  216. protected function isReadableDirectory($directory)
  217. {
  218. if (!$directory->isExist() || !$directory->isDirectory() || !$directory->isReadable()) {
  219. return false;
  220. }
  221. return true;
  222. }
  223. /**
  224. * Checks writable paths for installation, returns associative array if input is true, else returns simple array
  225. *
  226. * @param bool $associative
  227. * @return array
  228. */
  229. public function getMissingWritablePathsForInstallation($associative = false)
  230. {
  231. $required = $this->getInstallationWritableDirectories();
  232. $current = $this->getInstallationCurrentWritableDirectories();
  233. $missingPaths = [];
  234. foreach (array_diff($required, $current) as $missingPath) {
  235. if (isset($this->nonWritablePathsInDirectories[$missingPath])) {
  236. if ($associative) {
  237. $missingPaths[$missingPath] = $this->nonWritablePathsInDirectories[$missingPath];
  238. } else {
  239. $missingPaths = array_merge(
  240. $missingPaths,
  241. $this->nonWritablePathsInDirectories[$missingPath]
  242. );
  243. }
  244. }
  245. }
  246. if ($associative) {
  247. $required = array_flip($required);
  248. $missingPaths = array_merge($required, $missingPaths);
  249. }
  250. return $missingPaths;
  251. }
  252. /**
  253. * Checks writable paths for database upgrade, returns array of directory paths that requires write permission
  254. *
  255. * @return array List of directories that requires write permission for database upgrade
  256. */
  257. public function getMissingWritableDirectoriesForDbUpgrade()
  258. {
  259. $writableDirectories = [
  260. DirectoryList::CONFIG,
  261. DirectoryList::VAR_DIR
  262. ];
  263. $requireWritePermission = [];
  264. foreach ($writableDirectories as $code) {
  265. if (!$this->isWritable($code)) {
  266. $path = $this->directoryList->getPath($code);
  267. if (!$this->checkRecursiveDirectories($path)) {
  268. $requireWritePermission[] = $path;
  269. }
  270. }
  271. }
  272. return $requireWritePermission;
  273. }
  274. /**
  275. * Checks writable directories for installation
  276. *
  277. * @deprecated 100.1.0 Use getMissingWritablePathsForInstallation()
  278. * to get all missing writable paths required for install.
  279. * @return array
  280. */
  281. public function getMissingWritableDirectoriesForInstallation()
  282. {
  283. $required = $this->getInstallationWritableDirectories();
  284. $current = $this->getInstallationCurrentWritableDirectories();
  285. return array_diff($required, $current);
  286. }
  287. /**
  288. * Checks non-writable directories for application
  289. *
  290. * @return array
  291. */
  292. public function getUnnecessaryWritableDirectoriesForApplication()
  293. {
  294. $required = $this->getApplicationNonWritableDirectories();
  295. $current = $this->getApplicationCurrentNonWritableDirectories();
  296. return array_diff($required, $current);
  297. }
  298. }