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