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