1<?php 2 3 4namespace ComboStrap; 5 6/** 7 * Class LocalPath 8 * @package ComboStrap 9 * A local file system path 10 */ 11class LocalPath extends PathAbs 12{ 13 14 /** 15 * For whatever reason, it seems that php uses always the / separator on windows also 16 * but not always (ie https://www.php.net/manual/en/function.realpath.php output \ on windows) 17 * 18 * Because we want to be able to copy the path and to be able to use 19 * it directly, we {@link LocalPath::normalizedToOs() normalize} it to the OS separator 20 * at build time 21 */ 22 public const PHP_SYSTEM_DIRECTORY_SEPARATOR = DIRECTORY_SEPARATOR; 23 24 /** 25 * The characters that cannot be in the path for windows 26 * @var string[] 27 */ 28 public const RESERVED_WINDOWS_CHARACTERS = ["\\", "/", ":", "*", "?", "\"", "<", ">", "|"]; 29 30 private $path; 31 32 /** 33 * LocalPath constructor. 34 * @param $path - relative or absolute 35 */ 36 public function __construct($path) 37 { 38 $this->path = $path; 39 } 40 41 42 /** 43 * @param string $filePath 44 * @return LocalPath 45 * @deprecated for {@link LocalPath::createFromPath()} 46 */ 47 public static function create(string $filePath): LocalPath 48 { 49 return new LocalPath($filePath); 50 } 51 52 public static function createFromPath(string $string): LocalPath 53 { 54 return new LocalPath($string); 55 } 56 57 function getScheme(): string 58 { 59 return LocalFs::SCHEME; 60 } 61 62 function getLastName() 63 { 64 $names = $this->getNames(); 65 $sizeof = sizeof($names); 66 if ($sizeof === 0) { 67 return null; 68 } 69 return $names[$sizeof - 1]; 70 71 } 72 73 public function getExtension() 74 { 75 return pathinfo($this->path, PATHINFO_EXTENSION); 76 } 77 78 function getNames() 79 { 80 $directorySeparator = $this->getDirectorySeparator(); 81 return explode($directorySeparator, $this->path); 82 } 83 84 function getDokuwikiId() 85 { 86 throw new ExceptionComboRuntime("Not implemented"); 87 } 88 89 90 function toString(): string 91 { 92 return $this->path; 93 } 94 95 public function getParent(): ?Path 96 { 97 $absolutePath = pathinfo($this->path, PATHINFO_DIRNAME); 98 if (empty($absolutePath)) { 99 return null; 100 } 101 return new LocalPath($absolutePath); 102 } 103 104 function toAbsolutePath(): Path 105 { 106 107 if ($this->isAbsolute()) { 108 return $this; 109 } 110 111 return $this->toCanonicalPath(); 112 113 } 114 115 /** 116 * @return string 117 */ 118 private function getDirectorySeparator(): string 119 { 120 $directorySeparator = self::PHP_SYSTEM_DIRECTORY_SEPARATOR; 121 if ( 122 $directorySeparator === '\\' 123 && 124 strpos($this->path, "/") !== false 125 ) { 126 $directorySeparator = "/"; 127 } 128 return $directorySeparator; 129 } 130 131 132 /** 133 * @throws ExceptionCombo 134 */ 135 public function toDokuPath(): DokuPath 136 { 137 $driveRoots = DokuPath::getDriveRoots(); 138 foreach ($driveRoots as $driveRoot => $drivePath) { 139 try { 140 $relativePath = $this->relativize($drivePath); 141 return DokuPath::createDokuPath($relativePath->toString(), $driveRoot); 142 } catch (ExceptionCombo $e) { 143 // not a relative path 144 } 145 146 } 147 throw new ExceptionCombo("The local path ($this) is not inside a doku path drive"); 148 149 150 } 151 152 public function resolve(string $name): LocalPath 153 { 154 155 $newPath = $this->toCanonicalPath()->toString() . self::PHP_SYSTEM_DIRECTORY_SEPARATOR . $name; 156 return self::createFromPath($newPath); 157 158 } 159 160 /** 161 * @throws ExceptionCombo 162 */ 163 public function relativize(LocalPath $localPath): LocalPath 164 { 165 $actualPath = $this->toCanonicalPath(); 166 $localPath = $localPath->toCanonicalPath(); 167 168 if (!(strpos($actualPath->toString(), $localPath->toString()) === 0)) { 169 throw new ExceptionCombo("The path ($localPath) is not a parent path of the actual path ($actualPath)"); 170 } 171 $sepCharacter = 1; // delete the sep characters 172 $relativePath = substr($actualPath->toString(), strlen($localPath->toString()) + $sepCharacter); 173 $relativePath = str_replace(self::PHP_SYSTEM_DIRECTORY_SEPARATOR, DokuPath::PATH_SEPARATOR, $relativePath); 174 return LocalPath::createFromPath($relativePath); 175 176 } 177 178 public function isAbsolute(): bool 179 { 180 /** 181 * / 182 * \ 183 * or a:/ 184 * or z:\ 185 */ 186 if (preg_match("/^\/|[a-z]:[\\\\\/]|\\\\/i", $this->path)) { 187 return true; 188 } 189 return false; 190 191 } 192 193 /** 194 * An absolute path may not be canonical 195 * (ie windows short name or the path separator is not consistent (ie / in place of \ on windows) 196 * 197 * This function makes the path canonical meaning that two canonical path can be compared. 198 */ 199 public function toCanonicalPath(): LocalPath 200 { 201 202 /** 203 * realpath() is just a system/library call to actual realpath() function supported by OS. 204 * real path handle also the windows name ie USERNAME~ 205 */ 206 $realPath = realpath($this->path); 207 if ($realPath !== false) { 208 return LocalPath::createFromPath($realPath); 209 } 210 211 /** 212 * It returns false on on file that does not exists. 213 * The suggestion on the realpath man page 214 * is to look for an existing parent directory. 215 * https://man7.org/linux/man-pages/man3/realpath.3.html 216 */ 217 $parts = null; 218 $isRoot = false; 219 $counter = 0; // breaker 220 $workingPath = $this->path; 221 while ($realPath === false) { 222 $counter++; 223 $parent = dirname($workingPath); 224 /** 225 * From the doc: https://www.php.net/manual/en/function.dirname.php 226 * dirname('.'); // Will return '.'. 227 * dirname('/'); // Will return `\` on Windows and '/' on *nix systems. 228 * dirname('\\'); // Will return `\` on Windows and '.' on *nix systems. 229 * dirname('C:\\'); // Will return 'C:\' on Windows and '.' on *nix systems. 230 * dirname('\'); // Will return `C:\` on Windows and ??? on *nix systems. 231 */ 232 if (preg_match("/^(\.|\/|\\\\|[a-z]:\\\\)$/i", $parent) 233 || $parent === $workingPath 234 || $parent === "\\" // bug on regexp 235 ) { 236 $isRoot = true; 237 } 238 // root, no need to delete the last sep 239 $lastSep = 1; 240 if ($isRoot) { 241 $lastSep = 0; 242 } 243 $parts[] = substr($workingPath, strlen($parent) + $lastSep); 244 245 $realPath = realpath($parent); 246 if ($isRoot) { 247 break; 248 } 249 if ($counter > 200) { 250 $message = "Bad absolute local path file ($this->path)"; 251 if (PluginUtility::isDevOrTest()) { 252 throw new ExceptionComboRuntime($message); 253 } else { 254 LogUtility::msg($message); 255 } 256 return $this; 257 } 258 if ($realPath === false) { 259 // loop 260 $workingPath = $parent; 261 } 262 } 263 if ($parts !== null) { 264 if (!$isRoot) { 265 $realPath .= self::PHP_SYSTEM_DIRECTORY_SEPARATOR; 266 } 267 $parts = array_reverse($parts); 268 $realPath .= implode(self::PHP_SYSTEM_DIRECTORY_SEPARATOR, $parts); 269 } 270 return LocalPath::createFromPath($realPath); 271 } 272 273 274} 275