xref: /plugin/combo/ComboStrap/LocalPath.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1c3437056SNickeau<?php
2c3437056SNickeau
3c3437056SNickeau
4c3437056SNickeaunamespace ComboStrap;
5c3437056SNickeau
6*04fd306cSNickeauuse ComboStrap\Web\Url;
7*04fd306cSNickeau
8c3437056SNickeau/**
9c3437056SNickeau * Class LocalPath
10c3437056SNickeau * @package ComboStrap
11c3437056SNickeau * A local file system path
12*04fd306cSNickeau *
13*04fd306cSNickeau * File protocol Uri:
14*04fd306cSNickeau *
15*04fd306cSNickeau * 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
27*04fd306cSNickeau    const RELATIVE_CURRENT = ".";
28*04fd306cSNickeau    const RELATIVE_PARENT = "..";
29*04fd306cSNickeau    const LINUX_SEPARATOR = "/";
30*04fd306cSNickeau    const WINDOWS_SEPARATOR = '\\';
31*04fd306cSNickeau    const CANONICAL = "support";
32*04fd306cSNickeau
33*04fd306cSNickeau    /**
34*04fd306cSNickeau     * @throws ExceptionBadArgument
35*04fd306cSNickeau     */
36*04fd306cSNickeau    public static function createFromUri($uri): LocalPath
37*04fd306cSNickeau    {
38*04fd306cSNickeau        if (strpos($uri, LocalFileSystem::SCHEME) !== 0) {
39*04fd306cSNickeau            throw new ExceptionBadArgument("$uri is not a local path uri");
40*04fd306cSNickeau        }
41*04fd306cSNickeau        return new LocalPath($uri);
42*04fd306cSNickeau    }
43*04fd306cSNickeau
44*04fd306cSNickeau
45*04fd306cSNickeau    /**
46*04fd306cSNickeau     * @throws ExceptionBadArgument
47*04fd306cSNickeau     * @throws ExceptionCast
48*04fd306cSNickeau     */
49*04fd306cSNickeau    public static function createFromPathObject(Path $path): LocalPath
50*04fd306cSNickeau    {
51*04fd306cSNickeau        if ($path instanceof LocalPath) {
52*04fd306cSNickeau            return $path;
53*04fd306cSNickeau        }
54*04fd306cSNickeau        if ($path instanceof WikiPath) {
55*04fd306cSNickeau            return $path->toLocalPath();
56*04fd306cSNickeau        }
57*04fd306cSNickeau        throw new ExceptionBadArgument("The path is not a local path nor a wiki path, we can't transform it");
58*04fd306cSNickeau    }
59*04fd306cSNickeau
60*04fd306cSNickeau    /**
61*04fd306cSNickeau     *
62*04fd306cSNickeau     * @throws ExceptionNotFound - if the env directory is not found
63*04fd306cSNickeau     */
64*04fd306cSNickeau    public static function createDesktopDirectory(): LocalPath
65*04fd306cSNickeau    {
66*04fd306cSNickeau        return LocalPath::createHomeDirectory()->resolve("Desktop");
67*04fd306cSNickeau    }
68*04fd306cSNickeau
69*04fd306cSNickeau
70*04fd306cSNickeau    public function toUriString(): string
71*04fd306cSNickeau    {
72*04fd306cSNickeau        return $this->getUrl()->toString();
73*04fd306cSNickeau    }
74*04fd306cSNickeau
75c3437056SNickeau    private $path;
76*04fd306cSNickeau    /**
77*04fd306cSNickeau     * @var mixed
78*04fd306cSNickeau     */
79*04fd306cSNickeau    private $sep = DIRECTORY_SEPARATOR;
80*04fd306cSNickeau
81*04fd306cSNickeau    private ?string $host = null;
82c3437056SNickeau
83c3437056SNickeau    /**
84c3437056SNickeau     * LocalPath constructor.
85*04fd306cSNickeau     * @param string $path - relative or absolute, or a locale file uri
86*04fd306cSNickeau     * @param string|null $sep - the directory separator - it permits to test linux path on windows, and vice-versa
87c3437056SNickeau     */
88*04fd306cSNickeau    public function __construct(string $path, string $sep = null)
89c3437056SNickeau    {
90*04fd306cSNickeau        /**
91*04fd306cSNickeau         * php mon amour,
92*04fd306cSNickeau         * if we pass a {@link LocalPath}, no error,
93*04fd306cSNickeau         * it just pass the {@link PathAbs::__toString()}
94*04fd306cSNickeau         */
95*04fd306cSNickeau        if (strpos($path, LocalFileSystem::SCHEME) === 0) {
96*04fd306cSNickeau            try {
97*04fd306cSNickeau                $path = Url::createFromString($path)->getPath();
98*04fd306cSNickeau                LogUtility::errorIfDevOrTest("The path given as constructor should not be an uri or a path object");
99*04fd306cSNickeau            } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) {
100*04fd306cSNickeau                LogUtility::internalError("The uri path could not be created", self::CANONICAL, $e);
101*04fd306cSNickeau            }
102*04fd306cSNickeau        }
103*04fd306cSNickeau        if ($sep != null) {
104*04fd306cSNickeau            $this->sep = $sep;
105*04fd306cSNickeau        }
106*04fd306cSNickeau        // The network share windows/wiki styles with with two \\ and not //
107*04fd306cSNickeau        $networkShare = "\\\\";
108*04fd306cSNickeau        if (substr($path, 0, 2) === $networkShare) {
109*04fd306cSNickeau            // window share
110*04fd306cSNickeau            $pathWithoutNetworkShare = substr($path, 2);
111*04fd306cSNickeau            $pathWithoutNetworkShare = str_replace("\\", "/", $pathWithoutNetworkShare);
112*04fd306cSNickeau            [$this->host, $relativePath] = explode("/", $pathWithoutNetworkShare, 2);
113*04fd306cSNickeau            $this->path = "/$relativePath";
114*04fd306cSNickeau            return;
115*04fd306cSNickeau        }
116*04fd306cSNickeau        $this->path = self::normalizeToOsSeparator($path);
117c3437056SNickeau    }
118c3437056SNickeau
119c3437056SNickeau
1204cadd4f8SNickeau    /**
1214cadd4f8SNickeau     * @param string $filePath
1224cadd4f8SNickeau     * @return LocalPath
123*04fd306cSNickeau     * @deprecated for {@link LocalPath::createFromPathString()}
1244cadd4f8SNickeau     */
125c3437056SNickeau    public static function create(string $filePath): LocalPath
126c3437056SNickeau    {
127c3437056SNickeau        return new LocalPath($filePath);
128c3437056SNickeau    }
129c3437056SNickeau
130*04fd306cSNickeau    /**
131*04fd306cSNickeau     * @param $path
132*04fd306cSNickeau     * @return array|string|string[]
133*04fd306cSNickeau     *
134*04fd306cSNickeau     * For whatever reason, it seems that php/dokuwiki uses always the / separator on windows also
135*04fd306cSNickeau     * but not always (ie  https://www.php.net/manual/en/function.realpath.php output \ on windows)
136*04fd306cSNickeau     *
137*04fd306cSNickeau     * Because we want to be able to copy the path value and to be able to use
138*04fd306cSNickeau     * it directly, we normalize it to the OS separator at build time
139*04fd306cSNickeau     */
140*04fd306cSNickeau    private function normalizeToOsSeparator($path)
141c3437056SNickeau    {
142*04fd306cSNickeau        if ($path === self::RELATIVE_CURRENT || $path === self::RELATIVE_PARENT) {
143*04fd306cSNickeau            return realpath($path);
144*04fd306cSNickeau        }
145*04fd306cSNickeau        $directorySeparator = $this->getDirectorySeparator();
146*04fd306cSNickeau        if ($directorySeparator === self::WINDOWS_SEPARATOR) {
147*04fd306cSNickeau            return str_replace(self::LINUX_SEPARATOR, self::WINDOWS_SEPARATOR, $path);
148*04fd306cSNickeau        } else {
149*04fd306cSNickeau            return str_replace(self::WINDOWS_SEPARATOR, self::LINUX_SEPARATOR, $path);
150*04fd306cSNickeau        }
151*04fd306cSNickeau    }
152*04fd306cSNickeau
153*04fd306cSNickeau    /**
154*04fd306cSNickeau     * @throws ExceptionNotFound
155*04fd306cSNickeau     */
156*04fd306cSNickeau    public static function createHomeDirectory(): LocalPath
157*04fd306cSNickeau    {
158*04fd306cSNickeau        $home = getenv("HOME");
159*04fd306cSNickeau        if ($home === false) {
160*04fd306cSNickeau            $home = getenv("USERPROFILE");
161*04fd306cSNickeau        }
162*04fd306cSNickeau        if ($home === false) {
163*04fd306cSNickeau            throw new ExceptionNotFound(" The home directory variable could not be found");
164*04fd306cSNickeau        }
165*04fd306cSNickeau        return LocalPath::createFromPathString($home);
166*04fd306cSNickeau    }
167*04fd306cSNickeau
168*04fd306cSNickeau
169*04fd306cSNickeau    public static function createFromPathString(string $string, string $sep = null): LocalPath
170*04fd306cSNickeau    {
171*04fd306cSNickeau        return new LocalPath($string, $sep);
172c3437056SNickeau    }
173c3437056SNickeau
174c3437056SNickeau    function getScheme(): string
175c3437056SNickeau    {
176*04fd306cSNickeau        return LocalFileSystem::SCHEME;
177c3437056SNickeau    }
178c3437056SNickeau
179*04fd306cSNickeau    function getLastName(): string
180c3437056SNickeau    {
181c3437056SNickeau        $names = $this->getNames();
182c3437056SNickeau        $sizeof = sizeof($names);
183c3437056SNickeau        if ($sizeof === 0) {
184*04fd306cSNickeau            throw new ExceptionNotFound("No last name for the path ($this)");
185c3437056SNickeau        }
186c3437056SNickeau        return $names[$sizeof - 1];
187c3437056SNickeau
188c3437056SNickeau    }
189c3437056SNickeau
190*04fd306cSNickeau
191*04fd306cSNickeau    public function getExtension(): string
192c3437056SNickeau    {
193*04fd306cSNickeau        $extension = pathinfo($this->path, PATHINFO_EXTENSION);
194*04fd306cSNickeau        if ($extension === "") {
195*04fd306cSNickeau            throw new ExceptionNotFound("No extension found for the path ($this)");
196*04fd306cSNickeau        }
197*04fd306cSNickeau        return $extension;
198c3437056SNickeau    }
199c3437056SNickeau
200c3437056SNickeau    function getNames()
201c3437056SNickeau    {
202c3437056SNickeau        $directorySeparator = $this->getDirectorySeparator();
203c3437056SNickeau        return explode($directorySeparator, $this->path);
204c3437056SNickeau    }
205c3437056SNickeau
206c3437056SNickeau
207*04fd306cSNickeau    function toAbsoluteId(): string
208c3437056SNickeau    {
209c3437056SNickeau        return $this->path;
210c3437056SNickeau    }
211c3437056SNickeau
212*04fd306cSNickeau    public function getParent(): Path
213c3437056SNickeau    {
214c3437056SNickeau        $absolutePath = pathinfo($this->path, PATHINFO_DIRNAME);
215*04fd306cSNickeau        if ($absolutePath === $this->path || empty($absolutePath)) {
216*04fd306cSNickeau            // the directory on windows of the root (ie C:\) is (C:\), yolo !
217*04fd306cSNickeau            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
229*04fd306cSNickeau        return $this->toCanonicalAbsolutePath();
230c3437056SNickeau
231c3437056SNickeau    }
232c3437056SNickeau
233c3437056SNickeau
2344cadd4f8SNickeau    /**
235*04fd306cSNickeau     * @throws ExceptionBadArgument - if the path is not inside a drive
2364cadd4f8SNickeau     */
237*04fd306cSNickeau    public function toWikiPath(): WikiPath
2384cadd4f8SNickeau    {
239*04fd306cSNickeau        return WikiPath::createFromPathObject($this);
2404cadd4f8SNickeau    }
2414cadd4f8SNickeau
2424cadd4f8SNickeau    public function resolve(string $name): LocalPath
2434cadd4f8SNickeau    {
2444cadd4f8SNickeau
245*04fd306cSNickeau        $newPath = $this->toCanonicalAbsolutePath()->toAbsoluteId() . $this->getDirectorySeparator() . utf8_encodeFN($name);
246*04fd306cSNickeau        return self::createFromPathString($newPath);
2474cadd4f8SNickeau
2484cadd4f8SNickeau    }
2494cadd4f8SNickeau
2504cadd4f8SNickeau    /**
251*04fd306cSNickeau     * @throws ExceptionBadArgument - if the path cannot be relativized
2524cadd4f8SNickeau     */
2534cadd4f8SNickeau    public function relativize(LocalPath $localPath): LocalPath
2544cadd4f8SNickeau    {
2554cadd4f8SNickeau
256*04fd306cSNickeau        /**
257*04fd306cSNickeau         * One of the problem of relativization is
258*04fd306cSNickeau         * that it may be:
259*04fd306cSNickeau         * * logical (when using a symling)
260*04fd306cSNickeau         * * physical
261*04fd306cSNickeau         */
262*04fd306cSNickeau        if (!$this->isAbsolute() || $this->isShortName()) {
263*04fd306cSNickeau            /**
264*04fd306cSNickeau             * This is not a logical resolution
265*04fd306cSNickeau             * (if the path is logically not absolute and is a symlink,
266*04fd306cSNickeau             * we have a problem)
267*04fd306cSNickeau             */
268*04fd306cSNickeau            $actualPath = $this->toCanonicalAbsolutePath();
269*04fd306cSNickeau        } else {
270*04fd306cSNickeau            $actualPath = $this;
271*04fd306cSNickeau        }
272*04fd306cSNickeau        if (!$localPath->isAbsolute() || $localPath->isShortName()) {
273*04fd306cSNickeau            $localPath = $localPath->toCanonicalAbsolutePath();
274*04fd306cSNickeau        }
275*04fd306cSNickeau
276*04fd306cSNickeau        if (strpos($actualPath->toAbsoluteId(), $localPath->toAbsoluteId()) === 0) {
277*04fd306cSNickeau            if ($actualPath->toAbsoluteId() === $localPath->toAbsoluteId()) {
278*04fd306cSNickeau                return LocalPath::createFromPathString("");
2794cadd4f8SNickeau            }
2804cadd4f8SNickeau            $sepCharacter = 1; // delete the sep characters
281*04fd306cSNickeau            $relativePath = substr($actualPath->toAbsoluteId(), strlen($localPath->toAbsoluteId()) + $sepCharacter);
282*04fd306cSNickeau            $relativePath = str_replace($this->getDirectorySeparator(), WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $relativePath);
283*04fd306cSNickeau            return LocalPath::createFromPathString($relativePath);
284*04fd306cSNickeau        }
285*04fd306cSNickeau        /**
286*04fd306cSNickeau         * May be a symlink link
287*04fd306cSNickeau         */
288*04fd306cSNickeau        if ($this->isSymlink()) {
289*04fd306cSNickeau            $realPath = $this->toCanonicalAbsolutePath();
290*04fd306cSNickeau            return $realPath->relativize($localPath);
291*04fd306cSNickeau        }
292*04fd306cSNickeau        if ($localPath->isSymlink()) {
293*04fd306cSNickeau            $localPath = $localPath->toCanonicalAbsolutePath();
294*04fd306cSNickeau            $this->relativize($localPath);
295*04fd306cSNickeau        }
296*04fd306cSNickeau        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         * /
304*04fd306cSNickeau         * or a-z:\
3054cadd4f8SNickeau         */
306*04fd306cSNickeau        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.
318*04fd306cSNickeau     * This is also needed when you path a path string to a php function such as `clearstatcache`
319*04fd306cSNickeau     *
320*04fd306cSNickeau     * If this is a symlink, it will resolve it to the real path
3214cadd4f8SNickeau     */
322*04fd306cSNickeau    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         */
329*04fd306cSNickeau        $isSymlink = $this->isSymlink();
3304cadd4f8SNickeau        $realPath = realpath($this->path);
331*04fd306cSNickeau        if($isSymlink){
332*04fd306cSNickeau            /**
333*04fd306cSNickeau             *
334*04fd306cSNickeau             * What fucked is fucked up
335*04fd306cSNickeau             *
336*04fd306cSNickeau             * With the symlink
337*04fd306cSNickeau             * D:/dokuwiki-animals/combo.nico.lan/data/pages
338*04fd306cSNickeau             * if you pass it to realpath:
339*04fd306cSNickeau             * ```
340*04fd306cSNickeau             * realpath("D:/dokuwiki-animals/combo.nico.lan/data/pages")
341*04fd306cSNickeau             * ```
342*04fd306cSNickeau             * you get: `d:\dokuwiki\website\pages`
343*04fd306cSNickeau             * if you pass the result again in realpath
344*04fd306cSNickeau             * ```
345*04fd306cSNickeau             * realpath(d:\dokuwiki\website\pages)
346*04fd306cSNickeau             * ```
347*04fd306cSNickeau             * we get another result `D:\dokuwiki\website\pages`
348*04fd306cSNickeau             *
349*04fd306cSNickeau             */
350*04fd306cSNickeau            $realPath = realpath($realPath);
351*04fd306cSNickeau        }
3524cadd4f8SNickeau        if ($realPath !== false) {
353*04fd306cSNickeau            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()) {
397*04fd306cSNickeau                    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) {
410*04fd306cSNickeau                $realPath .= $this->getDirectorySeparator();
4114cadd4f8SNickeau            }
4124cadd4f8SNickeau            $parts = array_reverse($parts);
413*04fd306cSNickeau            $realPath .= implode($this->getDirectorySeparator(), $parts);
4144cadd4f8SNickeau        }
415*04fd306cSNickeau        return LocalPath::createFromPathString($realPath);
416*04fd306cSNickeau    }
417*04fd306cSNickeau
418*04fd306cSNickeau    public function getDirectorySeparator()
419*04fd306cSNickeau    {
420*04fd306cSNickeau        return $this->sep;
4214cadd4f8SNickeau    }
4224cadd4f8SNickeau
4234cadd4f8SNickeau
424*04fd306cSNickeau    function getUrl(): Url
425*04fd306cSNickeau    {
426*04fd306cSNickeau
427*04fd306cSNickeau        /**
428*04fd306cSNickeau         * file://host/path
429*04fd306cSNickeau         */
430*04fd306cSNickeau        $uri = LocalFileSystem::SCHEME . '://';
431*04fd306cSNickeau        try {
432*04fd306cSNickeau            // Windows share host
433*04fd306cSNickeau            $uri = "$uri{$this->getHost()}";
434*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
435*04fd306cSNickeau            // ok
436*04fd306cSNickeau        }
437*04fd306cSNickeau        $pathNormalized = str_replace(self::WINDOWS_SEPARATOR, self::LINUX_SEPARATOR, $this->path);
438*04fd306cSNickeau        if ($pathNormalized[0] !== "/") {
439*04fd306cSNickeau            $uri = $uri . "/" . $pathNormalized;
440*04fd306cSNickeau        } else {
441*04fd306cSNickeau            $uri = $uri . $pathNormalized;
442*04fd306cSNickeau        }
443*04fd306cSNickeau        try {
444*04fd306cSNickeau            return Url::createFromString($uri);
445*04fd306cSNickeau        } catch (ExceptionBadSyntax|ExceptionBadArgument $e) {
446*04fd306cSNickeau            $message = "Local Uri Path has a bad syntax ($uri)";
447*04fd306cSNickeau            // should not happen
448*04fd306cSNickeau            LogUtility::internalError($message);
449*04fd306cSNickeau            throw new ExceptionRuntime($message);
450*04fd306cSNickeau        }
451*04fd306cSNickeau
452*04fd306cSNickeau    }
453*04fd306cSNickeau
454*04fd306cSNickeau    /**
455*04fd306cSNickeau     * @throws ExceptionNotFound
456*04fd306cSNickeau     */
457*04fd306cSNickeau    function getHost(): string
458*04fd306cSNickeau    {
459*04fd306cSNickeau        if ($this->host === null) {
460*04fd306cSNickeau            throw new ExceptionNotFound("No host. Localhost should be the default");
461*04fd306cSNickeau        }
462*04fd306cSNickeau        return $this->host;
463*04fd306cSNickeau    }
464*04fd306cSNickeau
465*04fd306cSNickeau    public function isSymlink(): bool
466*04fd306cSNickeau    {
467*04fd306cSNickeau        return is_link($this->path);
468*04fd306cSNickeau    }
469*04fd306cSNickeau
470*04fd306cSNickeau    private function isShortName(): bool
471*04fd306cSNickeau    {
472*04fd306cSNickeau        /**
473*04fd306cSNickeau         * See short name in windows
474*04fd306cSNickeau         * https://datacadamia.com/os/windows/path#pathname
475*04fd306cSNickeau         */
476*04fd306cSNickeau        return strpos($this->path, "~1") !== false;
477*04fd306cSNickeau    }
478c3437056SNickeau}
479