1<?php 2namespace 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 17use TYPO3\CMS\Core\Messaging\FlashMessage; 18use 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 */ 24class 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 (strstr($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} 310