1c3437056SNickeau<?php 2c3437056SNickeau 3c3437056SNickeau 4c3437056SNickeaunamespace ComboStrap; 5c3437056SNickeau 604fd306cSNickeauuse ComboStrap\Web\Url; 704fd306cSNickeau 8c3437056SNickeau/** 9c3437056SNickeau * Class LocalPath 10c3437056SNickeau * @package ComboStrap 11c3437056SNickeau * A local file system path 1204fd306cSNickeau * 1304fd306cSNickeau * File protocol Uri: 1404fd306cSNickeau * 1504fd306cSNickeau * file://[HOST]/[PATH] 16c3437056SNickeau */ 17c3437056SNickeauclass LocalPath extends PathAbs 18c3437056SNickeau{ 19c3437056SNickeau 20c3437056SNickeau 21e4026cd1Sgerardnico /** 22e4026cd1Sgerardnico * The characters that cannot be in the path for windows 23e4026cd1Sgerardnico * @var string[] 24e4026cd1Sgerardnico */ 25e4026cd1Sgerardnico public const RESERVED_WINDOWS_CHARACTERS = ["\\", "/", ":", "*", "?", "\"", "<", ">", "|"]; 26e4026cd1Sgerardnico 2704fd306cSNickeau const RELATIVE_CURRENT = "."; 2804fd306cSNickeau const RELATIVE_PARENT = ".."; 2904fd306cSNickeau const LINUX_SEPARATOR = "/"; 3004fd306cSNickeau const WINDOWS_SEPARATOR = '\\'; 3104fd306cSNickeau const CANONICAL = "support"; 3204fd306cSNickeau 3304fd306cSNickeau /** 3404fd306cSNickeau * @throws ExceptionBadArgument 3504fd306cSNickeau */ 3604fd306cSNickeau public static function createFromUri($uri): LocalPath 3704fd306cSNickeau { 3804fd306cSNickeau if (strpos($uri, LocalFileSystem::SCHEME) !== 0) { 3904fd306cSNickeau throw new ExceptionBadArgument("$uri is not a local path uri"); 4004fd306cSNickeau } 4104fd306cSNickeau return new LocalPath($uri); 4204fd306cSNickeau } 4304fd306cSNickeau 4404fd306cSNickeau 4504fd306cSNickeau /** 4604fd306cSNickeau * @throws ExceptionBadArgument 4704fd306cSNickeau * @throws ExceptionCast 4804fd306cSNickeau */ 4904fd306cSNickeau public static function createFromPathObject(Path $path): LocalPath 5004fd306cSNickeau { 5104fd306cSNickeau if ($path instanceof LocalPath) { 5204fd306cSNickeau return $path; 5304fd306cSNickeau } 5404fd306cSNickeau if ($path instanceof WikiPath) { 5504fd306cSNickeau return $path->toLocalPath(); 5604fd306cSNickeau } 5704fd306cSNickeau throw new ExceptionBadArgument("The path is not a local path nor a wiki path, we can't transform it"); 5804fd306cSNickeau } 5904fd306cSNickeau 6004fd306cSNickeau /** 6104fd306cSNickeau * 6204fd306cSNickeau * @throws ExceptionNotFound - if the env directory is not found 6304fd306cSNickeau */ 6404fd306cSNickeau public static function createDesktopDirectory(): LocalPath 6504fd306cSNickeau { 6604fd306cSNickeau return LocalPath::createHomeDirectory()->resolve("Desktop"); 6704fd306cSNickeau } 6804fd306cSNickeau 6904fd306cSNickeau 7004fd306cSNickeau public function toUriString(): string 7104fd306cSNickeau { 7204fd306cSNickeau return $this->getUrl()->toString(); 7304fd306cSNickeau } 7404fd306cSNickeau 75c3437056SNickeau private $path; 7604fd306cSNickeau /** 7704fd306cSNickeau * @var mixed 7804fd306cSNickeau */ 7904fd306cSNickeau private $sep = DIRECTORY_SEPARATOR; 8004fd306cSNickeau 8104fd306cSNickeau private ?string $host = null; 82c3437056SNickeau 83c3437056SNickeau /** 84c3437056SNickeau * LocalPath constructor. 8504fd306cSNickeau * @param string $path - relative or absolute, or a locale file uri 8604fd306cSNickeau * @param string|null $sep - the directory separator - it permits to test linux path on windows, and vice-versa 87c3437056SNickeau */ 8804fd306cSNickeau public function __construct(string $path, string $sep = null) 89c3437056SNickeau { 9004fd306cSNickeau /** 9104fd306cSNickeau * php mon amour, 9204fd306cSNickeau * if we pass a {@link LocalPath}, no error, 9304fd306cSNickeau * it just pass the {@link PathAbs::__toString()} 9404fd306cSNickeau */ 95912bb8fdSgerardnico if (strpos($path, LocalFileSystem::SCHEME.'://') === 0) { 9604fd306cSNickeau try { 9704fd306cSNickeau $path = Url::createFromString($path)->getPath(); 9804fd306cSNickeau LogUtility::errorIfDevOrTest("The path given as constructor should not be an uri or a path object"); 9904fd306cSNickeau } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) { 10004fd306cSNickeau LogUtility::internalError("The uri path could not be created", self::CANONICAL, $e); 10104fd306cSNickeau } 10204fd306cSNickeau } 10304fd306cSNickeau if ($sep != null) { 10404fd306cSNickeau $this->sep = $sep; 10504fd306cSNickeau } 10604fd306cSNickeau // The network share windows/wiki styles with with two \\ and not // 10704fd306cSNickeau $networkShare = "\\\\"; 10804fd306cSNickeau if (substr($path, 0, 2) === $networkShare) { 10904fd306cSNickeau // window share 11004fd306cSNickeau $pathWithoutNetworkShare = substr($path, 2); 11104fd306cSNickeau $pathWithoutNetworkShare = str_replace("\\", "/", $pathWithoutNetworkShare); 11204fd306cSNickeau [$this->host, $relativePath] = explode("/", $pathWithoutNetworkShare, 2); 11304fd306cSNickeau $this->path = "/$relativePath"; 11404fd306cSNickeau return; 11504fd306cSNickeau } 11604fd306cSNickeau $this->path = self::normalizeToOsSeparator($path); 117c3437056SNickeau } 118c3437056SNickeau 119c3437056SNickeau 1204cadd4f8SNickeau /** 1214cadd4f8SNickeau * @param string $filePath 1224cadd4f8SNickeau * @return LocalPath 12304fd306cSNickeau * @deprecated for {@link LocalPath::createFromPathString()} 1244cadd4f8SNickeau */ 125c3437056SNickeau public static function create(string $filePath): LocalPath 126c3437056SNickeau { 127c3437056SNickeau return new LocalPath($filePath); 128c3437056SNickeau } 129c3437056SNickeau 13004fd306cSNickeau /** 13104fd306cSNickeau * @param $path 13204fd306cSNickeau * @return array|string|string[] 13304fd306cSNickeau * 13404fd306cSNickeau * For whatever reason, it seems that php/dokuwiki uses always the / separator on windows also 13504fd306cSNickeau * but not always (ie https://www.php.net/manual/en/function.realpath.php output \ on windows) 13604fd306cSNickeau * 13704fd306cSNickeau * Because we want to be able to copy the path value and to be able to use 13804fd306cSNickeau * it directly, we normalize it to the OS separator at build time 13904fd306cSNickeau */ 14004fd306cSNickeau private function normalizeToOsSeparator($path) 141c3437056SNickeau { 14204fd306cSNickeau if ($path === self::RELATIVE_CURRENT || $path === self::RELATIVE_PARENT) { 14304fd306cSNickeau return realpath($path); 14404fd306cSNickeau } 14504fd306cSNickeau $directorySeparator = $this->getDirectorySeparator(); 14604fd306cSNickeau if ($directorySeparator === self::WINDOWS_SEPARATOR) { 14704fd306cSNickeau return str_replace(self::LINUX_SEPARATOR, self::WINDOWS_SEPARATOR, $path); 14804fd306cSNickeau } else { 14904fd306cSNickeau return str_replace(self::WINDOWS_SEPARATOR, self::LINUX_SEPARATOR, $path); 15004fd306cSNickeau } 15104fd306cSNickeau } 15204fd306cSNickeau 15304fd306cSNickeau /** 15404fd306cSNickeau * @throws ExceptionNotFound 15504fd306cSNickeau */ 15604fd306cSNickeau public static function createHomeDirectory(): LocalPath 15704fd306cSNickeau { 15804fd306cSNickeau $home = getenv("HOME"); 15904fd306cSNickeau if ($home === false) { 16004fd306cSNickeau $home = getenv("USERPROFILE"); 16104fd306cSNickeau } 16204fd306cSNickeau if ($home === false) { 16304fd306cSNickeau throw new ExceptionNotFound(" The home directory variable could not be found"); 16404fd306cSNickeau } 16504fd306cSNickeau return LocalPath::createFromPathString($home); 16604fd306cSNickeau } 16704fd306cSNickeau 16804fd306cSNickeau 16904fd306cSNickeau public static function createFromPathString(string $string, string $sep = null): LocalPath 17004fd306cSNickeau { 17104fd306cSNickeau return new LocalPath($string, $sep); 172c3437056SNickeau } 173c3437056SNickeau 174c3437056SNickeau function getScheme(): string 175c3437056SNickeau { 17604fd306cSNickeau return LocalFileSystem::SCHEME; 177c3437056SNickeau } 178c3437056SNickeau 17904fd306cSNickeau function getLastName(): string 180c3437056SNickeau { 181c3437056SNickeau $names = $this->getNames(); 182c3437056SNickeau $sizeof = sizeof($names); 183c3437056SNickeau if ($sizeof === 0) { 18404fd306cSNickeau throw new ExceptionNotFound("No last name for the path ($this)"); 185c3437056SNickeau } 186c3437056SNickeau return $names[$sizeof - 1]; 187c3437056SNickeau 188c3437056SNickeau } 189c3437056SNickeau 19004fd306cSNickeau 19104fd306cSNickeau public function getExtension(): string 192c3437056SNickeau { 19304fd306cSNickeau $extension = pathinfo($this->path, PATHINFO_EXTENSION); 19404fd306cSNickeau if ($extension === "") { 19504fd306cSNickeau throw new ExceptionNotFound("No extension found for the path ($this)"); 19604fd306cSNickeau } 19704fd306cSNickeau return $extension; 198c3437056SNickeau } 199c3437056SNickeau 200c3437056SNickeau function getNames() 201c3437056SNickeau { 202c3437056SNickeau $directorySeparator = $this->getDirectorySeparator(); 203c3437056SNickeau return explode($directorySeparator, $this->path); 204c3437056SNickeau } 205c3437056SNickeau 206c3437056SNickeau 20704fd306cSNickeau function toAbsoluteId(): string 208c3437056SNickeau { 209c3437056SNickeau return $this->path; 210c3437056SNickeau } 211c3437056SNickeau 21204fd306cSNickeau public function getParent(): Path 213c3437056SNickeau { 214c3437056SNickeau $absolutePath = pathinfo($this->path, PATHINFO_DIRNAME); 21504fd306cSNickeau if ($absolutePath === $this->path || empty($absolutePath)) { 21604fd306cSNickeau // the directory on windows of the root (ie C:\) is (C:\), yolo ! 21704fd306cSNickeau throw new ExceptionNotFound("No parent"); 218c3437056SNickeau } 219c3437056SNickeau return new LocalPath($absolutePath); 220c3437056SNickeau } 221c3437056SNickeau 222c3437056SNickeau function toAbsolutePath(): Path 223c3437056SNickeau { 2244cadd4f8SNickeau 2254cadd4f8SNickeau if ($this->isAbsolute()) { 226c3437056SNickeau return $this; 2274cadd4f8SNickeau } 2284cadd4f8SNickeau 22904fd306cSNickeau return $this->toCanonicalAbsolutePath(); 230c3437056SNickeau 231c3437056SNickeau } 232c3437056SNickeau 233c3437056SNickeau 2344cadd4f8SNickeau /** 23504fd306cSNickeau * @throws ExceptionBadArgument - if the path is not inside a drive 2364cadd4f8SNickeau */ 23704fd306cSNickeau public function toWikiPath(): WikiPath 2384cadd4f8SNickeau { 23904fd306cSNickeau return WikiPath::createFromPathObject($this); 2404cadd4f8SNickeau } 2414cadd4f8SNickeau 2424cadd4f8SNickeau public function resolve(string $name): LocalPath 2434cadd4f8SNickeau { 2444cadd4f8SNickeau 24504fd306cSNickeau $newPath = $this->toCanonicalAbsolutePath()->toAbsoluteId() . $this->getDirectorySeparator() . utf8_encodeFN($name); 24604fd306cSNickeau return self::createFromPathString($newPath); 2474cadd4f8SNickeau 2484cadd4f8SNickeau } 2494cadd4f8SNickeau 2504cadd4f8SNickeau /** 25104fd306cSNickeau * @throws ExceptionBadArgument - if the path cannot be relativized 2524cadd4f8SNickeau */ 2534cadd4f8SNickeau public function relativize(LocalPath $localPath): LocalPath 2544cadd4f8SNickeau { 2554cadd4f8SNickeau 25604fd306cSNickeau /** 25704fd306cSNickeau * One of the problem of relativization is 25804fd306cSNickeau * that it may be: 259*0f82904aSgerardnico * * logical (when using a symlink) 26004fd306cSNickeau * * physical 26104fd306cSNickeau */ 26204fd306cSNickeau if (!$this->isAbsolute() || $this->isShortName()) { 26304fd306cSNickeau /** 26404fd306cSNickeau * This is not a logical resolution 26504fd306cSNickeau * (if the path is logically not absolute and is a symlink, 26604fd306cSNickeau * we have a problem) 26704fd306cSNickeau */ 26804fd306cSNickeau $actualPath = $this->toCanonicalAbsolutePath(); 26904fd306cSNickeau } else { 27004fd306cSNickeau $actualPath = $this; 27104fd306cSNickeau } 27204fd306cSNickeau if (!$localPath->isAbsolute() || $localPath->isShortName()) { 27304fd306cSNickeau $localPath = $localPath->toCanonicalAbsolutePath(); 27404fd306cSNickeau } 27504fd306cSNickeau 27604fd306cSNickeau if (strpos($actualPath->toAbsoluteId(), $localPath->toAbsoluteId()) === 0) { 27704fd306cSNickeau if ($actualPath->toAbsoluteId() === $localPath->toAbsoluteId()) { 27804fd306cSNickeau return LocalPath::createFromPathString(""); 2794cadd4f8SNickeau } 2804cadd4f8SNickeau $sepCharacter = 1; // delete the sep characters 28104fd306cSNickeau $relativePath = substr($actualPath->toAbsoluteId(), strlen($localPath->toAbsoluteId()) + $sepCharacter); 28204fd306cSNickeau $relativePath = str_replace($this->getDirectorySeparator(), WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $relativePath); 28304fd306cSNickeau return LocalPath::createFromPathString($relativePath); 28404fd306cSNickeau } 28504fd306cSNickeau /** 28604fd306cSNickeau * May be a symlink link 28704fd306cSNickeau */ 28804fd306cSNickeau if ($this->isSymlink()) { 28904fd306cSNickeau $realPath = $this->toCanonicalAbsolutePath(); 29004fd306cSNickeau return $realPath->relativize($localPath); 29104fd306cSNickeau } 29204fd306cSNickeau if ($localPath->isSymlink()) { 29304fd306cSNickeau $localPath = $localPath->toCanonicalAbsolutePath(); 29404fd306cSNickeau $this->relativize($localPath); 29504fd306cSNickeau } 29604fd306cSNickeau throw new ExceptionBadArgument("The path ($localPath) is not a parent path of the actual path ($actualPath)"); 2974cadd4f8SNickeau 2984cadd4f8SNickeau } 2994cadd4f8SNickeau 3004cadd4f8SNickeau public function isAbsolute(): bool 3014cadd4f8SNickeau { 3024cadd4f8SNickeau /** 3034cadd4f8SNickeau * / 30404fd306cSNickeau * or a-z:\ 3054cadd4f8SNickeau */ 30604fd306cSNickeau if (preg_match("/^(\/|[a-z]:\\\\?).*/i", $this->path)) { 3074cadd4f8SNickeau return true; 3084cadd4f8SNickeau } 3094cadd4f8SNickeau return false; 3104cadd4f8SNickeau 3114cadd4f8SNickeau } 3124cadd4f8SNickeau 3134cadd4f8SNickeau /** 3144cadd4f8SNickeau * An absolute path may not be canonical 3154cadd4f8SNickeau * (ie windows short name or the path separator is not consistent (ie / in place of \ on windows) 3164cadd4f8SNickeau * 3174cadd4f8SNickeau * This function makes the path canonical meaning that two canonical path can be compared. 31804fd306cSNickeau * This is also needed when you path a path string to a php function such as `clearstatcache` 31904fd306cSNickeau * 32004fd306cSNickeau * If this is a symlink, it will resolve it to the real path 3214cadd4f8SNickeau */ 32204fd306cSNickeau public function toCanonicalAbsolutePath(): LocalPath 3234cadd4f8SNickeau { 3244cadd4f8SNickeau 3254cadd4f8SNickeau /** 3264cadd4f8SNickeau * realpath() is just a system/library call to actual realpath() function supported by OS. 3274cadd4f8SNickeau * real path handle also the windows name ie USERNAME~ 3284cadd4f8SNickeau */ 32904fd306cSNickeau $isSymlink = $this->isSymlink(); 3304cadd4f8SNickeau $realPath = realpath($this->path); 33104fd306cSNickeau if($isSymlink){ 33204fd306cSNickeau /** 33304fd306cSNickeau * 33404fd306cSNickeau * What fucked is fucked up 33504fd306cSNickeau * 33604fd306cSNickeau * With the symlink 33704fd306cSNickeau * D:/dokuwiki-animals/combo.nico.lan/data/pages 33804fd306cSNickeau * if you pass it to realpath: 33904fd306cSNickeau * ``` 34004fd306cSNickeau * realpath("D:/dokuwiki-animals/combo.nico.lan/data/pages") 34104fd306cSNickeau * ``` 34204fd306cSNickeau * you get: `d:\dokuwiki\website\pages` 34304fd306cSNickeau * if you pass the result again in realpath 34404fd306cSNickeau * ``` 34504fd306cSNickeau * realpath(d:\dokuwiki\website\pages) 34604fd306cSNickeau * ``` 34704fd306cSNickeau * we get another result `D:\dokuwiki\website\pages` 34804fd306cSNickeau * 34904fd306cSNickeau */ 35004fd306cSNickeau $realPath = realpath($realPath); 35104fd306cSNickeau } 3524cadd4f8SNickeau if ($realPath !== false) { 35304fd306cSNickeau return LocalPath::createFromPathString($realPath); 3544cadd4f8SNickeau } 3554cadd4f8SNickeau 3564cadd4f8SNickeau /** 3574cadd4f8SNickeau * It returns false on on file that does not exists. 3584cadd4f8SNickeau * The suggestion on the realpath man page 3594cadd4f8SNickeau * is to look for an existing parent directory. 3604cadd4f8SNickeau * https://man7.org/linux/man-pages/man3/realpath.3.html 3614cadd4f8SNickeau */ 3624cadd4f8SNickeau $parts = null; 3634cadd4f8SNickeau $isRoot = false; 3644cadd4f8SNickeau $counter = 0; // breaker 3654cadd4f8SNickeau $workingPath = $this->path; 3664cadd4f8SNickeau while ($realPath === false) { 3674cadd4f8SNickeau $counter++; 3684cadd4f8SNickeau $parent = dirname($workingPath); 3694cadd4f8SNickeau /** 3704cadd4f8SNickeau * From the doc: https://www.php.net/manual/en/function.dirname.php 3714cadd4f8SNickeau * dirname('.'); // Will return '.'. 3724cadd4f8SNickeau * dirname('/'); // Will return `\` on Windows and '/' on *nix systems. 3734cadd4f8SNickeau * dirname('\\'); // Will return `\` on Windows and '.' on *nix systems. 3744cadd4f8SNickeau * dirname('C:\\'); // Will return 'C:\' on Windows and '.' on *nix systems. 3754cadd4f8SNickeau * dirname('\'); // Will return `C:\` on Windows and ??? on *nix systems. 3764cadd4f8SNickeau */ 3774cadd4f8SNickeau if (preg_match("/^(\.|\/|\\\\|[a-z]:\\\\)$/i", $parent) 3784cadd4f8SNickeau || $parent === $workingPath 3794cadd4f8SNickeau || $parent === "\\" // bug on regexp 3804cadd4f8SNickeau ) { 3814cadd4f8SNickeau $isRoot = true; 3824cadd4f8SNickeau } 3834cadd4f8SNickeau // root, no need to delete the last sep 3844cadd4f8SNickeau $lastSep = 1; 3854cadd4f8SNickeau if ($isRoot) { 3864cadd4f8SNickeau $lastSep = 0; 3874cadd4f8SNickeau } 3884cadd4f8SNickeau $parts[] = substr($workingPath, strlen($parent) + $lastSep); 3894cadd4f8SNickeau 3904cadd4f8SNickeau $realPath = realpath($parent); 3914cadd4f8SNickeau if ($isRoot) { 3924cadd4f8SNickeau break; 3934cadd4f8SNickeau } 3944cadd4f8SNickeau if ($counter > 200) { 3954cadd4f8SNickeau $message = "Bad absolute local path file ($this->path)"; 3964cadd4f8SNickeau if (PluginUtility::isDevOrTest()) { 39704fd306cSNickeau throw new ExceptionRuntime($message); 3984cadd4f8SNickeau } else { 3994cadd4f8SNickeau LogUtility::msg($message); 4004cadd4f8SNickeau } 4014cadd4f8SNickeau return $this; 4024cadd4f8SNickeau } 4034cadd4f8SNickeau if ($realPath === false) { 4044cadd4f8SNickeau // loop 4054cadd4f8SNickeau $workingPath = $parent; 4064cadd4f8SNickeau } 4074cadd4f8SNickeau } 4084cadd4f8SNickeau if ($parts !== null) { 4094cadd4f8SNickeau if (!$isRoot) { 41004fd306cSNickeau $realPath .= $this->getDirectorySeparator(); 4114cadd4f8SNickeau } 4124cadd4f8SNickeau $parts = array_reverse($parts); 41304fd306cSNickeau $realPath .= implode($this->getDirectorySeparator(), $parts); 4144cadd4f8SNickeau } 41504fd306cSNickeau return LocalPath::createFromPathString($realPath); 41604fd306cSNickeau } 41704fd306cSNickeau 41804fd306cSNickeau public function getDirectorySeparator() 41904fd306cSNickeau { 42004fd306cSNickeau return $this->sep; 4214cadd4f8SNickeau } 4224cadd4f8SNickeau 4234cadd4f8SNickeau 42404fd306cSNickeau function getUrl(): Url 42504fd306cSNickeau { 42604fd306cSNickeau 42704fd306cSNickeau /** 42804fd306cSNickeau * file://host/path 42904fd306cSNickeau */ 43004fd306cSNickeau $uri = LocalFileSystem::SCHEME . '://'; 43104fd306cSNickeau try { 43204fd306cSNickeau // Windows share host 43304fd306cSNickeau $uri = "$uri{$this->getHost()}"; 43404fd306cSNickeau } catch (ExceptionNotFound $e) { 43504fd306cSNickeau // ok 43604fd306cSNickeau } 43704fd306cSNickeau $pathNormalized = str_replace(self::WINDOWS_SEPARATOR, self::LINUX_SEPARATOR, $this->path); 43804fd306cSNickeau if ($pathNormalized[0] !== "/") { 43904fd306cSNickeau $uri = $uri . "/" . $pathNormalized; 44004fd306cSNickeau } else { 44104fd306cSNickeau $uri = $uri . $pathNormalized; 44204fd306cSNickeau } 44304fd306cSNickeau try { 44404fd306cSNickeau return Url::createFromString($uri); 44504fd306cSNickeau } catch (ExceptionBadSyntax|ExceptionBadArgument $e) { 44604fd306cSNickeau $message = "Local Uri Path has a bad syntax ($uri)"; 44704fd306cSNickeau // should not happen 44804fd306cSNickeau LogUtility::internalError($message); 44904fd306cSNickeau throw new ExceptionRuntime($message); 45004fd306cSNickeau } 45104fd306cSNickeau 45204fd306cSNickeau } 45304fd306cSNickeau 45404fd306cSNickeau /** 45504fd306cSNickeau * @throws ExceptionNotFound 45604fd306cSNickeau */ 45704fd306cSNickeau function getHost(): string 45804fd306cSNickeau { 45904fd306cSNickeau if ($this->host === null) { 46004fd306cSNickeau throw new ExceptionNotFound("No host. Localhost should be the default"); 46104fd306cSNickeau } 46204fd306cSNickeau return $this->host; 46304fd306cSNickeau } 46404fd306cSNickeau 46504fd306cSNickeau public function isSymlink(): bool 46604fd306cSNickeau { 46704fd306cSNickeau return is_link($this->path); 46804fd306cSNickeau } 46904fd306cSNickeau 47004fd306cSNickeau private function isShortName(): bool 47104fd306cSNickeau { 47204fd306cSNickeau /** 47304fd306cSNickeau * See short name in windows 47404fd306cSNickeau * https://datacadamia.com/os/windows/path#pathname 47504fd306cSNickeau */ 47604fd306cSNickeau return strpos($this->path, "~1") !== false; 47704fd306cSNickeau } 478c3437056SNickeau} 479