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\Core\Environment; 19use TYPO3\CMS\Core\Messaging\FlashMessage; 20use TYPO3\CMS\Install\FolderStructure\Exception\InvalidArgumentException; 21 22/** 23 * Abstract node implements common methods 24 * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. 25 */ 26abstract class AbstractNode 27{ 28 /** 29 * @var string Name 30 */ 31 protected $name = ''; 32 33 /** 34 * @var string|null Target permissions for unix, eg. '2775' or '0664' (4 characters string) 35 */ 36 protected $targetPermission; 37 38 /** 39 * @var NodeInterface|null Parent object of this structure node 40 */ 41 protected $parent; 42 43 /** 44 * @var array Directories and root may have children, files and link always empty array 45 */ 46 protected $children = []; 47 48 /** 49 * Get name 50 * 51 * @return string Name 52 */ 53 public function getName() 54 { 55 return $this->name; 56 } 57 58 /** 59 * Get target permission 60 * 61 * Make sure to call octdec on the value when passing this to chmod 62 * 63 * @return string Permissions as a 4 character octal string, i.e. 2775 or 0644 64 */ 65 protected function getTargetPermission() 66 { 67 return $this->targetPermission ?? ''; 68 } 69 70 /** 71 * Set target permission 72 * 73 * @param string $permission Permissions as a 4 character octal string, i.e. 2775 or 0644 74 */ 75 protected function setTargetPermission($permission) 76 { 77 // Normalize the permission string to "4 characters", padding with leading "0" if necessary: 78 $permission = substr($permission, 0, 4); 79 $permission = str_pad($permission, 4, '0', STR_PAD_LEFT); 80 $this->targetPermission = $permission; 81 } 82 83 /** 84 * Get children 85 * 86 * @return array 87 */ 88 protected function getChildren() 89 { 90 return $this->children; 91 } 92 93 /** 94 * Get parent 95 * 96 * @return NodeInterface|null 97 */ 98 protected function getParent() 99 { 100 return $this->parent; 101 } 102 103 /** 104 * Get absolute path of node 105 * 106 * @return string 107 */ 108 public function getAbsolutePath() 109 { 110 return $this->getParent()->getAbsolutePath() . '/' . $this->name; 111 } 112 113 /** 114 * Current node is writable if parent is writable 115 * 116 * @return bool TRUE if parent is writable 117 */ 118 public function isWritable() 119 { 120 return $this->getParent()->isWritable(); 121 } 122 123 /** 124 * Checks if node exists. 125 * Returns TRUE if it is there, even if it is only a link. 126 * Does not check the type! 127 * 128 * @return bool 129 */ 130 protected function exists() 131 { 132 if (@is_link($this->getAbsolutePath())) { 133 return true; 134 } 135 return @file_exists($this->getAbsolutePath()); 136 } 137 138 /** 139 * Fix permission if they are not equal to target permission 140 * 141 * @throws Exception 142 * @return FlashMessage 143 */ 144 protected function fixPermission(): FlashMessage 145 { 146 if ($this->isPermissionCorrect()) { 147 throw new Exception( 148 'Permission on ' . $this->getAbsolutePath() . ' are already ok', 149 1366744035 150 ); 151 } 152 $result = @chmod($this->getAbsolutePath(), (int)octdec($this->getTargetPermission())); 153 if ($result === true) { 154 return new FlashMessage( 155 '', 156 'Fixed permission on ' . $this->getRelativePathBelowSiteRoot() . '.' 157 ); 158 } 159 return new FlashMessage( 160 'Permissions could not be changed to ' . $this->getTargetPermission() 161 . '. This only is a problem if files and folders within this node cannot be written.', 162 'Permission change on ' . $this->getRelativePathBelowSiteRoot() . ' not successful', 163 FlashMessage::NOTICE 164 ); 165 } 166 167 /** 168 * Checks if current permission are identical to target permission 169 * 170 * @return bool 171 */ 172 protected function isPermissionCorrect() 173 { 174 if ($this->isWindowsOs()) { 175 return true; 176 } 177 if ($this->getCurrentPermission() === $this->getTargetPermission()) { 178 return true; 179 } 180 return false; 181 } 182 183 /** 184 * Get current permission of node 185 * 186 * @return string eg. 2775 for dirs, 0664 for files 187 */ 188 protected function getCurrentPermission() 189 { 190 $permissions = decoct((int)fileperms($this->getAbsolutePath())); 191 return substr($permissions, -4); 192 } 193 194 /** 195 * Returns TRUE if OS is windows 196 * 197 * @return bool TRUE on windows 198 */ 199 protected function isWindowsOs() 200 { 201 return Environment::isWindows(); 202 } 203 204 /** 205 * Cut off project path from given path 206 * 207 * @param string $path Given path 208 * @return string Relative path, but beginning with / 209 * @throws Exception\InvalidArgumentException 210 */ 211 protected function getRelativePathBelowSiteRoot($path = null) 212 { 213 if ($path === null) { 214 $path = $this->getAbsolutePath(); 215 } 216 $projectPath = Environment::getProjectPath(); 217 if (strpos($path, $projectPath, 0) !== 0) { 218 throw new InvalidArgumentException( 219 'Public path is not first part of given path', 220 1366398198 221 ); 222 } 223 $relativePath = substr($path, strlen($projectPath), strlen($path)); 224 // Add a forward slash again, so we don't end up with an empty string 225 if ($relativePath === '') { 226 $relativePath = '/'; 227 } 228 return $relativePath; 229 } 230} 231