Revert "[TASK] Avoid slow array functions in loops"
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / FolderStructure / DirectoryNode.php
1 <?php
2 namespace TYPO3\CMS\Install\FolderStructure;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Messaging\FlashMessage;
18 use TYPO3\CMS\Core\Utility\StringUtility;
19
20 /**
21 * A directory
22 * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
23 */
24 class DirectoryNode extends AbstractNode implements NodeInterface
25 {
26 /**
27 * @var int|null Default for directories is octal 02775 == decimal 1533
28 */
29 protected $targetPermission = '2775';
30
31 /**
32 * Implement constructor
33 *
34 * @param array $structure Structure array
35 * @param NodeInterface $parent Parent object
36 * @throws Exception\InvalidArgumentException
37 */
38 public function __construct(array $structure, NodeInterface $parent = null)
39 {
40 if ($parent === null) {
41 throw new Exception\InvalidArgumentException(
42 'Node must have parent',
43 1366222203
44 );
45 }
46 $this->parent = $parent;
47
48 // Ensure name is a single segment, but not a path like foo/bar or an absolute path /foo
49 if (strpos($structure['name'], '/') !== false) {
50 throw new Exception\InvalidArgumentException(
51 'Directory name must not contain forward slash',
52 1366226639
53 );
54 }
55 $this->name = $structure['name'];
56
57 if (isset($structure['targetPermission'])) {
58 $this->setTargetPermission($structure['targetPermission']);
59 }
60
61 if (array_key_exists('children', $structure)) {
62 $this->createChildren($structure['children']);
63 }
64 }
65
66 /**
67 * Get own status and status of child objects
68 *
69 * @return FlashMessage[]
70 */
71 public function getStatus(): array
72 {
73 $result = [];
74 if (!$this->exists()) {
75 $status = new FlashMessage(
76 'The Install Tool can try to create it',
77 'Directory ' . $this->getRelativePathBelowSiteRoot() . ' does not exist',
78 FlashMessage::WARNING
79 );
80 $result[] = $status;
81 } else {
82 $result = $this->getSelfStatus();
83 }
84 $result = array_merge($result, $this->getChildrenStatus());
85 return $result;
86 }
87
88 /**
89 * Create a test file and delete again if directory exists
90 *
91 * @return bool TRUE if test file creation was successful
92 */
93 public function isWritable()
94 {
95 $result = true;
96 if (!$this->exists()) {
97 $result = false;
98 } elseif (!$this->canFileBeCreated()) {
99 $result = false;
100 }
101 return $result;
102 }
103
104 /**
105 * Fix structure
106 *
107 * If there is nothing to fix, returns an empty array
108 *
109 * @return FlashMessage[]
110 */
111 public function fix(): array
112 {
113 $result = $this->fixSelf();
114 foreach ($this->children as $child) {
115 /** @var NodeInterface $child */
116 $result = array_merge($result, $child->fix());
117 }
118 return $result;
119 }
120
121 /**
122 * Fix this directory:
123 *
124 * - create with correct permissions if it was not existing
125 * - if there is no "write" permissions, try to fix it
126 * - leave it alone otherwise
127 *
128 * @return FlashMessage[]
129 */
130 protected function fixSelf()
131 {
132 $result = [];
133 if (!$this->exists()) {
134 $resultCreateDirectory = $this->createDirectory();
135 $result[] = $resultCreateDirectory;
136 if ($resultCreateDirectory->getSeverity() === FlashMessage::OK &&
137 !$this->isPermissionCorrect()
138 ) {
139 $result[] = $this->fixPermission();
140 }
141 } elseif (!$this->isWritable()) {
142 // If directory is not writable, we might have permissions to fix that
143 // Try it:
144 $result[] = $this->fixPermission();
145 } elseif (!$this->isDirectory()) {
146 $fileType = @filetype($this->getAbsolutePath());
147 if ($fileType) {
148 $messageBody =
149 'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a directory,' .
150 ' but is of type ' . $fileType . '. This cannot be fixed automatically. Please investigate.'
151 ;
152 } else {
153 $messageBody =
154 'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a directory,' .
155 ' but is of unknown type, probably because an upper level directory does not exist. Please investigate.'
156 ;
157 }
158 $result[] = new FlashMessage(
159 $messageBody,
160 'Path ' . $this->getRelativePathBelowSiteRoot() . ' is not a directory',
161 FlashMessage::ERROR
162 );
163 }
164 return $result;
165 }
166
167 /**
168 * Create directory if not exists
169 *
170 * @throws Exception
171 * @return FlashMessage
172 */
173 protected function createDirectory(): FlashMessage
174 {
175 if ($this->exists()) {
176 throw new Exception(
177 'Directory ' . $this->getAbsolutePath() . ' already exists',
178 1366740091
179 );
180 }
181 $result = @mkdir($this->getAbsolutePath());
182 if ($result === true) {
183 return new FlashMessage(
184 '',
185 'Directory ' . $this->getRelativePathBelowSiteRoot() . ' successfully created.'
186 );
187 }
188 return new FlashMessage(
189 'The target directory could not be created. There is probably a'
190 . ' group or owner permission problem on the parent directory.',
191 'Directory ' . $this->getRelativePathBelowSiteRoot() . ' not created!',
192 FlashMessage::ERROR
193 );
194 }
195
196 /**
197 * Get status of directory - used in root and directory node
198 *
199 * @return FlashMessage[]
200 */
201 protected function getSelfStatus(): array
202 {
203 $result = [];
204 if (!$this->isDirectory()) {
205 $result[] = new FlashMessage(
206 'Directory ' . $this->getRelativePathBelowSiteRoot() . ' should be a directory,'
207 . ' but is of type ' . filetype($this->getAbsolutePath()),
208 $this->getRelativePathBelowSiteRoot() . ' is not a directory',
209 FlashMessage::ERROR
210 );
211 } elseif (!$this->isWritable()) {
212 $result[] = new FlashMessage(
213 'Path ' . $this->getAbsolutePath() . ' exists, but no file underneath it'
214 . ' can be created.',
215 'Directory ' . $this->getRelativePathBelowSiteRoot() . ' is not writable',
216 FlashMessage::ERROR
217 );
218 } elseif (!$this->isPermissionCorrect()) {
219 $result[] = new FlashMessage(
220 'Default configured permissions are ' . $this->getTargetPermission()
221 . ' but current permissions are ' . $this->getCurrentPermission(),
222 'Directory ' . $this->getRelativePathBelowSiteRoot() . ' permissions mismatch',
223 FlashMessage::NOTICE
224 );
225 } else {
226 $result[] = new FlashMessage(
227 'Is a directory with the configured permissions of ' . $this->getTargetPermission(),
228 'Directory ' . $this->getRelativePathBelowSiteRoot()
229 );
230 }
231 return $result;
232 }
233
234 /**
235 * Get status of children
236 *
237 * @return FlashMessage[]
238 */
239 protected function getChildrenStatus(): array
240 {
241 $result = [];
242 foreach ($this->children as $child) {
243 /** @var NodeInterface $child */
244 $result = array_merge($result, $child->getStatus());
245 }
246 return $result;
247 }
248
249 /**
250 * Create a test file and delete again - helper for isWritable
251 *
252 * @return bool TRUE if test file creation was successful
253 */
254 protected function canFileBeCreated()
255 {
256 $testFileName = StringUtility::getUniqueId('installToolTest_');
257 $result = @touch($this->getAbsolutePath() . '/' . $testFileName);
258 if ($result === true) {
259 unlink($this->getAbsolutePath() . '/' . $testFileName);
260 }
261 return $result;
262 }
263
264 /**
265 * Checks if not is a directory
266 *
267 * @return bool True if node is a directory
268 */
269 protected function isDirectory()
270 {
271 $path = $this->getAbsolutePath();
272 return !@is_link($path) && @is_dir($path);
273 }
274
275 /**
276 * Create children nodes - done in directory and root node
277 *
278 * @param array $structure Array of children
279 * @throws Exception\InvalidArgumentException
280 */
281 protected function createChildren(array $structure)
282 {
283 foreach ($structure as $child) {
284 if (!array_key_exists('type', $child)) {
285 throw new Exception\InvalidArgumentException(
286 'Child must have type',
287 1366222204
288 );
289 }
290 if (!array_key_exists('name', $child)) {
291 throw new Exception\InvalidArgumentException(
292 'Child must have name',
293 1366222205
294 );
295 }
296 $name = $child['name'];
297 foreach ($this->children as $existingChild) {
298 /** @var NodeInterface $existingChild */
299 if ($existingChild->getName() === $name) {
300 throw new Exception\InvalidArgumentException(
301 'Child name must be unique',
302 1366222206
303 );
304 }
305 }
306 $this->children[] = new $child['type']($child, $this);
307 }
308 }
309 }