xref: /template/strap/ComboStrap/WikiPath.php (revision d342d501a16e3f896dbce26739006ade12bfbe1b)
104fd306cSNickeau<?php
204fd306cSNickeau
304fd306cSNickeaunamespace ComboStrap;
404fd306cSNickeau
504fd306cSNickeau
604fd306cSNickeauuse ComboStrap\Web\Url;
704fd306cSNickeau
804fd306cSNickeau/**
904fd306cSNickeau * Class DokuPath
1004fd306cSNickeau * @package ComboStrap
1104fd306cSNickeau * A dokuwiki path has the same structure than a windows path with a drive and a path
1204fd306cSNickeau *
1304fd306cSNickeau * The drive being a local path on the local file system
1404fd306cSNickeau *
1504fd306cSNickeau * Ultimately, this path is the application path and should be used everywhere.
1604fd306cSNickeau * * for users input (ie in a link markup such as media and page link)
1704fd306cSNickeau * * for output (ie creating the id for the url)
1804fd306cSNickeau *
1904fd306cSNickeau * Dokuwiki knows only two drives ({@link WikiPath::MARKUP_DRIVE} and {@link WikiPath::MEDIA_DRIVE}
2004fd306cSNickeau * but we have added a couple more such as the {@link WikiPath::COMBO_DRIVE combo resources}
2104fd306cSNickeau * and the {@link WikiPath::CACHE_DRIVE} to be able to serve resources
2204fd306cSNickeau *
2304fd306cSNickeau * TODO: because all {@link LocalPath} has at minium a drive (ie C:,D:, E: for windows or \ for linux)
2404fd306cSNickeau *    A Wiki Path can be just a wrapper around every local path)
2504fd306cSNickeau *    The {@link LocalPath::toWikiPath()} should not throw then but as not all drive
2604fd306cSNickeau *    may be public, we need to add a drive functionality to get this information.
2704fd306cSNickeau */
2804fd306cSNickeauclass WikiPath extends PathAbs
2904fd306cSNickeau{
3004fd306cSNickeau
3104fd306cSNickeau    const MEDIA_DRIVE = "media";
3204fd306cSNickeau    const MARKUP_DRIVE = "markup";
3304fd306cSNickeau    const UNKNOWN_DRIVE = "unknown";
3404fd306cSNickeau    const NAMESPACE_SEPARATOR_DOUBLE_POINT = ":";
3504fd306cSNickeau
3604fd306cSNickeau    // https://www.dokuwiki.org/config:useslash
3704fd306cSNickeau    const NAMESPACE_SEPARATOR_SLASH = "/";
3804fd306cSNickeau
3904fd306cSNickeau    const SEPARATORS = [self::NAMESPACE_SEPARATOR_DOUBLE_POINT, self::NAMESPACE_SEPARATOR_SLASH];
4004fd306cSNickeau
4104fd306cSNickeau    /**
4204fd306cSNickeau     * For whatever reason, dokuwiki uses also on windows
4304fd306cSNickeau     * the linux separator
4404fd306cSNickeau     */
4504fd306cSNickeau    public const DIRECTORY_SEPARATOR = "/";
4604fd306cSNickeau    public const SLUG_SEPARATOR = "-";
4704fd306cSNickeau
4804fd306cSNickeau
4904fd306cSNickeau    /**
5004fd306cSNickeau     * Dokuwiki has a file system that starts at a page and/or media
5104fd306cSNickeau     * directory that depends on the used syntax.
5204fd306cSNickeau     *
5304fd306cSNickeau     * It's a little bit the same than as the icon library (we set it as library then)
5404fd306cSNickeau     *
5504fd306cSNickeau     * This parameters is an URL parameter
5604fd306cSNickeau     * that permits to set an another one
5704fd306cSNickeau     * when retrieving the file via HTTP
5804fd306cSNickeau     * For now, there is only one value: {@link WikiPath::COMBO_DRIVE}
5904fd306cSNickeau     */
6004fd306cSNickeau    public const DRIVE_ATTRIBUTE = "drive";
6104fd306cSNickeau
6204fd306cSNickeau    /**
6304fd306cSNickeau     * The interwiki scheme that points to the
6404fd306cSNickeau     * combo resources directory ie {@link WikiPath::COMBO_DRIVE}
6504fd306cSNickeau     * ie
6604fd306cSNickeau     *   combo>library:
6704fd306cSNickeau     *   combo>image:
6804fd306cSNickeau     */
6904fd306cSNickeau    const COMBO_DRIVE = "combo";
7004fd306cSNickeau    /**
7104fd306cSNickeau     * The home directory for all themes
7204fd306cSNickeau     */
7304fd306cSNickeau    const COMBO_DATA_THEME_DRIVE = "combo-theme";
7404fd306cSNickeau    const CACHE_DRIVE = "cache";
7504fd306cSNickeau    const MARKUP_DEFAULT_TXT_EXTENSION = "txt";
7604fd306cSNickeau    const MARKUP_MD_TXT_EXTENSION = "md";
7704fd306cSNickeau    const REV_ATTRIBUTE = "rev";
7804fd306cSNickeau    const CURRENT_PATH_CHARACTER = ".";
7904fd306cSNickeau    const CURRENT_PARENT_PATH_CHARACTER = "..";
8004fd306cSNickeau    const CANONICAL = "wiki-path";
8104fd306cSNickeau    const ALL_MARKUP_EXTENSIONS = [self::MARKUP_DEFAULT_TXT_EXTENSION, self::MARKUP_MD_TXT_EXTENSION];
8204fd306cSNickeau
8304fd306cSNickeau
8404fd306cSNickeau    /**
8504fd306cSNickeau     * @var string[]
8604fd306cSNickeau     */
8704fd306cSNickeau    private static $reservedWords;
8804fd306cSNickeau
8904fd306cSNickeau    /**
9004fd306cSNickeau     * @var string the path id passed to function (cleaned)
9104fd306cSNickeau     */
9204fd306cSNickeau    private $id;
9304fd306cSNickeau
9404fd306cSNickeau
9504fd306cSNickeau    /**
9604fd306cSNickeau     * @var string
9704fd306cSNickeau     */
9804fd306cSNickeau    private $drive;
9904fd306cSNickeau    /**
10004fd306cSNickeau     * @var string|null - ie mtime
10104fd306cSNickeau     */
10204fd306cSNickeau    private $rev;
10304fd306cSNickeau
10404fd306cSNickeau
10504fd306cSNickeau    /**
10604fd306cSNickeau     * The separator from the {@link WikiPath::getDrive()}
10704fd306cSNickeau     */
10804fd306cSNickeau    const DRIVE_SEPARATOR = ">";
10904fd306cSNickeau    /**
11004fd306cSNickeau     * @var string - the absolute path (we use it for now to handle directory by adding a separator at the end)
11104fd306cSNickeau     */
11204fd306cSNickeau    protected $absolutePath;
11304fd306cSNickeau
11404fd306cSNickeau    /**
11504fd306cSNickeau     * DokuPath constructor.
11604fd306cSNickeau     *
11704fd306cSNickeau     * A path for the Dokuwiki File System
11804fd306cSNickeau     *
11904fd306cSNickeau     * @param string $path - the path (may be relative)
12004fd306cSNickeau     * @param string $drive - the drive (media, page, combo) - same as in windows for the drive prefix (c, d, ...)
12104fd306cSNickeau     * @param string|null $rev - the revision (mtime)
12204fd306cSNickeau     *
12304fd306cSNickeau     * Thee path should be a qualified/absolute path because in Dokuwiki, a link to a {@link MarkupPath}
12404fd306cSNickeau     * that ends with the {@link WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT} points to a start page
12504fd306cSNickeau     * and not to a namespace. The qualification occurs in the transformation
12604fd306cSNickeau     * from ref to page.
12704fd306cSNickeau     *   For a page: in {@link MarkupRef::getInternalPage()}
12804fd306cSNickeau     *   For a media: in the {@link MediaLink::createMediaLinkFromId()}
12904fd306cSNickeau     * Because this class is mostly the file representation, it should be able to
13004fd306cSNickeau     * represents also a namespace
13104fd306cSNickeau     */
13204fd306cSNickeau    protected function __construct(string $path, string $drive, string $rev = null)
13304fd306cSNickeau    {
13404fd306cSNickeau
13504fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv();
13604fd306cSNickeau
13704fd306cSNickeau        /**
13804fd306cSNickeau         * Due to the fact that the request environment is set on the setup in test,
13904fd306cSNickeau         * the path may be not normalized
14004fd306cSNickeau         */
14104fd306cSNickeau        $path = self::normalizeWikiPath($path);
14204fd306cSNickeau
14304fd306cSNickeau        if (trim($path) === "") {
14404fd306cSNickeau            try {
14504fd306cSNickeau                $path = WikiPath::getContextPath()->toAbsoluteId();
14604fd306cSNickeau            } catch (ExceptionNotFound $e) {
14704fd306cSNickeau                throw new ExceptionRuntimeInternal("The context path is unknwon. The empty path string needs it.");
14804fd306cSNickeau            }
14904fd306cSNickeau        }
15004fd306cSNickeau
15104fd306cSNickeau        /**
15204fd306cSNickeau         * Relative Path ?
15304fd306cSNickeau         */
15404fd306cSNickeau        $this->absolutePath = $path;
15504fd306cSNickeau        $firstCharacter = substr($path, 0, 1);
15604fd306cSNickeau        if ($drive === self::MARKUP_DRIVE && $firstCharacter !== WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
15704fd306cSNickeau            $parts = preg_split('/' . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . '/', $path);
15804fd306cSNickeau            switch ($parts[0]) {
15904fd306cSNickeau                case WikiPath::CURRENT_PATH_CHARACTER:
16004fd306cSNickeau                    // delete the relative character
16104fd306cSNickeau                    $parts = array_splice($parts, 1);
16204fd306cSNickeau                    try {
16304fd306cSNickeau                        $rootRelativePath = $executionContext->getContextNamespacePath();
16404fd306cSNickeau                    } catch (ExceptionNotFound $e) {
16504fd306cSNickeau                        // Root case: the relative path is in the root
16604fd306cSNickeau                        // the root has no parent
16704fd306cSNickeau                        LogUtility::error("The current relative path ({$this->absolutePath}) returns an error: {$e->getMessage()}", self::CANONICAL);
16804fd306cSNickeau                        $rootRelativePath = WikiPath::createMarkupPathFromPath(WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT);
16904fd306cSNickeau                    }
17004fd306cSNickeau                    break;
17104fd306cSNickeau                case WikiPath::CURRENT_PARENT_PATH_CHARACTER:
17204fd306cSNickeau                    // delete the relative character
17304fd306cSNickeau                    $parts = array_splice($parts, 1);
17404fd306cSNickeau
17504fd306cSNickeau                    $currentPagePath = $executionContext->getContextNamespacePath();
17604fd306cSNickeau                    try {
17704fd306cSNickeau                        $rootRelativePath = $currentPagePath->getParent();
17804fd306cSNickeau                    } catch (ExceptionNotFound $e) {
179*d342d501SNico                        // No parent
18004fd306cSNickeau                        $rootRelativePath = $executionContext->getContextNamespacePath();
18104fd306cSNickeau                    }
18204fd306cSNickeau
18304fd306cSNickeau                    break;
18404fd306cSNickeau                default:
18504fd306cSNickeau                    /**
18604fd306cSNickeau                     * just a relative name path
18704fd306cSNickeau                     * (ie hallo)
18804fd306cSNickeau                     */
18904fd306cSNickeau                    $rootRelativePath = $executionContext->getContextNamespacePath();
19004fd306cSNickeau                    break;
19104fd306cSNickeau            }
19204fd306cSNickeau            // is relative directory path ?
19304fd306cSNickeau            // ie ..: or .:
19404fd306cSNickeau            $isRelativeDirectoryPath = false;
19504fd306cSNickeau            $countParts = sizeof($parts);
19604fd306cSNickeau            if ($countParts > 0 && $parts[$countParts - 1] === "") {
19704fd306cSNickeau                $isRelativeDirectoryPath = true;
19804fd306cSNickeau                $parts = array_splice($parts, 0, $countParts - 1);
19904fd306cSNickeau            }
20004fd306cSNickeau            foreach ($parts as $part) {
20104fd306cSNickeau                $rootRelativePath = $rootRelativePath->resolve($part);
20204fd306cSNickeau            }
20304fd306cSNickeau            $absolutePathString = $rootRelativePath->getAbsolutePath();
20404fd306cSNickeau            if ($isRelativeDirectoryPath && !WikiPath::isNamespacePath($absolutePathString)) {
20504fd306cSNickeau                $absolutePathString = $absolutePathString . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT;
20604fd306cSNickeau            }
20704fd306cSNickeau            $this->absolutePath = $absolutePathString;
20804fd306cSNickeau        }
20904fd306cSNickeau
21004fd306cSNickeau
21104fd306cSNickeau        /**
21204fd306cSNickeau         * ACL check does not care about the type of id
21304fd306cSNickeau         * https://www.dokuwiki.org/devel:event:auth_acl_check
21404fd306cSNickeau         * https://github.com/splitbrain/dokuwiki/issues/3476
21504fd306cSNickeau         *
21604fd306cSNickeau         * We check if there is an extension
21704fd306cSNickeau         * If this is the case, this is a media
21804fd306cSNickeau         */
21904fd306cSNickeau        if ($drive === self::UNKNOWN_DRIVE) {
22004fd306cSNickeau            $lastPosition = StringUtility::lastIndexOf($path, ".");
22104fd306cSNickeau            if ($lastPosition === FALSE) {
22204fd306cSNickeau                $drive = self::MARKUP_DRIVE;
22304fd306cSNickeau            } else {
22404fd306cSNickeau                $drive = self::MEDIA_DRIVE;
22504fd306cSNickeau            }
22604fd306cSNickeau        }
22704fd306cSNickeau        $this->drive = $drive;
22804fd306cSNickeau
22904fd306cSNickeau
23004fd306cSNickeau        /**
23104fd306cSNickeau         * We use interwiki to define the combo resources
23204fd306cSNickeau         * (Internal use only)
23304fd306cSNickeau         */
23404fd306cSNickeau        $comboInterWikiScheme = "combo>";
23504fd306cSNickeau        if (strpos($this->absolutePath, $comboInterWikiScheme) === 0) {
23604fd306cSNickeau            $pathPart = substr($this->absolutePath, strlen($comboInterWikiScheme));
23704fd306cSNickeau            $this->id = $this->toDokuWikiIdDriveContextual($pathPart);
23804fd306cSNickeau            $this->drive = self::COMBO_DRIVE;
23904fd306cSNickeau        } else {
24004fd306cSNickeau            WikiPath::addRootSeparatorIfNotPresent($this->absolutePath);
24104fd306cSNickeau            $this->id = $this->toDokuWikiIdDriveContextual($this->absolutePath);
24204fd306cSNickeau        }
24304fd306cSNickeau
24404fd306cSNickeau
24504fd306cSNickeau        $this->rev = $rev;
24604fd306cSNickeau
24704fd306cSNickeau    }
24804fd306cSNickeau
24904fd306cSNickeau
25004fd306cSNickeau    /**
25104fd306cSNickeau     * For a Markup drive path, a file path should have an extension
25204fd306cSNickeau     * if it's not a namespace
25304fd306cSNickeau     *
25404fd306cSNickeau     * This function checks that
25504fd306cSNickeau     *
25604fd306cSNickeau     * @param string $parameterPath - the path in a wiki form that may be relative - if the path is blank, it's the current markup (the requested markup)
25704fd306cSNickeau     * @param string|null $rev - the revision (ie timestamp in number format)
25804fd306cSNickeau     * @return WikiPath - the wiki path
25904fd306cSNickeau     * @throws ExceptionBadArgument - if a relative path is given and the context path does not have any parent
26004fd306cSNickeau     */
26104fd306cSNickeau    public static function createMarkupPathFromPath(string $parameterPath, string $rev = null): WikiPath
26204fd306cSNickeau    {
26304fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv();
26404fd306cSNickeau
26504fd306cSNickeau        if ($parameterPath == "") {
26604fd306cSNickeau            return $executionContext->getContextPath();
26704fd306cSNickeau        }
26804fd306cSNickeau        if (WikiPath::isNamespacePath($parameterPath)) {
26904fd306cSNickeau
27004fd306cSNickeau            if ($parameterPath[0] !== self::CURRENT_PATH_CHARACTER) {
27104fd306cSNickeau                /**
27204fd306cSNickeau                 * Not a relative path
27304fd306cSNickeau                 */
27404fd306cSNickeau                return new WikiPath($parameterPath, self::MARKUP_DRIVE, $rev);
27504fd306cSNickeau            }
27604fd306cSNickeau            /**
27704fd306cSNickeau             * A relative path
27804fd306cSNickeau             */
27904fd306cSNickeau            $contextPath = $executionContext->getContextPath();
28004fd306cSNickeau            if ($parameterPath === self::CURRENT_PARENT_PATH_CHARACTER . self::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
28104fd306cSNickeau                /**
28204fd306cSNickeau                 * ie processing `..:`
28304fd306cSNickeau                 */
28404fd306cSNickeau                try {
28504fd306cSNickeau                    return $contextPath->getParent()->getParent();
28604fd306cSNickeau                } catch (ExceptionNotFound $e) {
28704fd306cSNickeau                    throw new ExceptionBadArgument("The context path ($contextPath) does not have a grand parent, therefore the relative path ($parameterPath) is invalid.", $e);
28804fd306cSNickeau                }
28904fd306cSNickeau            }
29004fd306cSNickeau            /**
29104fd306cSNickeau             * ie processing `.:`
29204fd306cSNickeau             */
29304fd306cSNickeau            try {
29404fd306cSNickeau                return $contextPath->getParent();
29504fd306cSNickeau            } catch (ExceptionNotFound $e) {
29604fd306cSNickeau                LogUtility::internalError("A context path is a page and should therefore have a parent", $e);
29704fd306cSNickeau            }
29804fd306cSNickeau
29904fd306cSNickeau        }
30004fd306cSNickeau
30104fd306cSNickeau        /**
30204fd306cSNickeau         * Default Path
30304fd306cSNickeau         * (we add the txt extension if not present)
30404fd306cSNickeau         */
30504fd306cSNickeau        $defaultPath = $parameterPath;
30604fd306cSNickeau        $lastName = $parameterPath;
30704fd306cSNickeau        $lastSeparator = strrpos($parameterPath, self::NAMESPACE_SEPARATOR_DOUBLE_POINT);
30804fd306cSNickeau        if ($lastSeparator !== false) {
30904fd306cSNickeau            $lastName = substr($parameterPath, $lastSeparator);
31004fd306cSNickeau        }
31104fd306cSNickeau        $lastPoint = strpos($lastName, ".");
31204fd306cSNickeau        if ($lastPoint === false) {
31304fd306cSNickeau            $defaultPath = $defaultPath . '.' . self::MARKUP_DEFAULT_TXT_EXTENSION;
31404fd306cSNickeau        } else {
31504fd306cSNickeau            /**
31604fd306cSNickeau             * Case such as file `1.22`
31704fd306cSNickeau             */
31804fd306cSNickeau            $parameterPathExtension = substr($lastName, $lastPoint + 1);
31904fd306cSNickeau            if (!in_array($parameterPathExtension, self::ALL_MARKUP_EXTENSIONS)) {
32004fd306cSNickeau                $defaultPath = $defaultPath . '.' . self::MARKUP_DEFAULT_TXT_EXTENSION;
32104fd306cSNickeau            }
32204fd306cSNickeau        }
32304fd306cSNickeau        $defaultWikiPath = new WikiPath($defaultPath, self::MARKUP_DRIVE, $rev);
32404fd306cSNickeau        if (FileSystems::exists($defaultWikiPath)) {
32504fd306cSNickeau            return $defaultWikiPath;
32604fd306cSNickeau        }
32704fd306cSNickeau
32804fd306cSNickeau        /**
32904fd306cSNickeau         * Markup extension (Markdown, ...)
33004fd306cSNickeau         */
33104fd306cSNickeau        if (!isset($parameterPathExtension)) {
33204fd306cSNickeau            foreach (self::ALL_MARKUP_EXTENSIONS as $markupExtension) {
33304fd306cSNickeau                if ($markupExtension == self::MARKUP_DEFAULT_TXT_EXTENSION) {
33404fd306cSNickeau                    continue;
33504fd306cSNickeau                }
33604fd306cSNickeau                $markupWikiPath = new WikiPath($parameterPath . '.' . $markupExtension, self::MARKUP_DRIVE, $rev);
33704fd306cSNickeau                if (FileSystems::exists($markupWikiPath)) {
33804fd306cSNickeau                    return $markupWikiPath;
33904fd306cSNickeau                }
34004fd306cSNickeau            }
34104fd306cSNickeau        }
34204fd306cSNickeau
34304fd306cSNickeau        /**
34404fd306cSNickeau         * Return the non-existen default wiki path
34504fd306cSNickeau         */
34604fd306cSNickeau        return $defaultWikiPath;
34704fd306cSNickeau
34804fd306cSNickeau    }
34904fd306cSNickeau
35004fd306cSNickeau
35104fd306cSNickeau    public
35204fd306cSNickeau    static function createMediaPathFromPath($path, $rev = null): WikiPath
35304fd306cSNickeau    {
35404fd306cSNickeau        return new WikiPath($path, WikiPath::MEDIA_DRIVE, $rev);
35504fd306cSNickeau    }
35604fd306cSNickeau
35704fd306cSNickeau    /**
35804fd306cSNickeau     * If the media may come from the
35904fd306cSNickeau     * dokuwiki media or combo resources media,
36004fd306cSNickeau     * you should use this function
36104fd306cSNickeau     *
36204fd306cSNickeau     * The constructor will determine the type based on
36304fd306cSNickeau     * the id structure.
36404fd306cSNickeau     * @param $id
36504fd306cSNickeau     * @return WikiPath
36604fd306cSNickeau     */
36704fd306cSNickeau    public
36804fd306cSNickeau    static function createFromUnknownRoot($id): WikiPath
36904fd306cSNickeau    {
37004fd306cSNickeau        return new WikiPath($id, WikiPath::UNKNOWN_DRIVE);
37104fd306cSNickeau    }
37204fd306cSNickeau
37304fd306cSNickeau    /**
37404fd306cSNickeau     * @param $url - a URL path http://whatever/hello/my/lord (The canonical)
37504fd306cSNickeau     * @return WikiPath - a dokuwiki Id hello:my:lord
37604fd306cSNickeau     * @deprecated for {@link FetcherPage::createPageFragmentFetcherFromUrl()}
37704fd306cSNickeau     */
37804fd306cSNickeau    public
37904fd306cSNickeau    static function createFromUrl($url): WikiPath
38004fd306cSNickeau    {
38104fd306cSNickeau        // Replace / by : and suppress the first : because the global $ID does not have it
38204fd306cSNickeau        $parsedQuery = parse_url($url, PHP_URL_QUERY);
38304fd306cSNickeau        $parsedQueryArray = [];
38404fd306cSNickeau        parse_str($parsedQuery, $parsedQueryArray);
38504fd306cSNickeau        $queryId = 'id';
38604fd306cSNickeau        if (array_key_exists($queryId, $parsedQueryArray)) {
38704fd306cSNickeau            // Doku form (ie doku.php?id=)
38804fd306cSNickeau            $id = $parsedQueryArray[$queryId];
38904fd306cSNickeau        } else {
39004fd306cSNickeau            // Slash form ie (/my/id)
39104fd306cSNickeau            $urlPath = parse_url($url, PHP_URL_PATH);
39204fd306cSNickeau            $id = substr(str_replace("/", ":", $urlPath), 1);
39304fd306cSNickeau        }
39404fd306cSNickeau        return self::createMarkupPathFromPath(":$id");
39504fd306cSNickeau    }
39604fd306cSNickeau
39704fd306cSNickeau    /**
39804fd306cSNickeau     * Static don't ask why
39904fd306cSNickeau     * @param $pathId
40004fd306cSNickeau     * @return false|string
40104fd306cSNickeau     */
40204fd306cSNickeau    public
40304fd306cSNickeau    static function getLastPart($pathId)
40404fd306cSNickeau    {
40504fd306cSNickeau        $endSeparatorLocation = StringUtility::lastIndexOf($pathId, WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT);
40604fd306cSNickeau        if ($endSeparatorLocation === false) {
40704fd306cSNickeau            $endSeparatorLocation = StringUtility::lastIndexOf($pathId, WikiPath::NAMESPACE_SEPARATOR_SLASH);
40804fd306cSNickeau        }
40904fd306cSNickeau        if ($endSeparatorLocation === false) {
41004fd306cSNickeau            $lastPathPart = $pathId;
41104fd306cSNickeau        } else {
41204fd306cSNickeau            $lastPathPart = substr($pathId, $endSeparatorLocation + 1);
41304fd306cSNickeau        }
41404fd306cSNickeau        return $lastPathPart;
41504fd306cSNickeau    }
41604fd306cSNickeau
41704fd306cSNickeau    /**
41804fd306cSNickeau     * @param $id
41904fd306cSNickeau     * @return string
42004fd306cSNickeau     * Return an path from a id
42104fd306cSNickeau     */
42204fd306cSNickeau    public
42304fd306cSNickeau    static function IdToAbsolutePath($id)
42404fd306cSNickeau    {
42504fd306cSNickeau        if (is_null($id)) {
42604fd306cSNickeau            LogUtility::msg("The id passed should not be null");
42704fd306cSNickeau        }
42804fd306cSNickeau        return WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $id;
42904fd306cSNickeau    }
43004fd306cSNickeau
43104fd306cSNickeau    function toDokuWikiIdDriveContextual($path): string
43204fd306cSNickeau    {
43304fd306cSNickeau        /**
43404fd306cSNickeau         * Delete the first separator
43504fd306cSNickeau         */
43604fd306cSNickeau        $id = self::removeRootSepIfPresent($path);
43704fd306cSNickeau
43804fd306cSNickeau        /**
43904fd306cSNickeau         * If this is a markup, we delete the txt extension if any
44004fd306cSNickeau         */
44104fd306cSNickeau        if ($this->getDrive() === self::MARKUP_DRIVE) {
44204fd306cSNickeau            StringUtility::rtrim($id, '.' . self::MARKUP_DEFAULT_TXT_EXTENSION);
44304fd306cSNickeau        }
44404fd306cSNickeau        return $id;
44504fd306cSNickeau
44604fd306cSNickeau    }
44704fd306cSNickeau
44804fd306cSNickeau    public
44904fd306cSNickeau    static function createMediaPathFromId($id, $rev = null): WikiPath
45004fd306cSNickeau    {
45104fd306cSNickeau        WikiPath::addRootSeparatorIfNotPresent($id);
45204fd306cSNickeau        return self::createMediaPathFromPath($id, $rev);
45304fd306cSNickeau    }
45404fd306cSNickeau
45504fd306cSNickeau    public static function getComboCustomThemeHomeDirectory(): WikiPath
45604fd306cSNickeau    {
45704fd306cSNickeau        return new WikiPath(self::NAMESPACE_SEPARATOR_DOUBLE_POINT, self::COMBO_DATA_THEME_DRIVE);
45804fd306cSNickeau    }
45904fd306cSNickeau
46004fd306cSNickeau    /**
46104fd306cSNickeau     * @throws ExceptionBadArgument
46204fd306cSNickeau     */
46304fd306cSNickeau    public
46404fd306cSNickeau    static function createFromUri(string $uri): WikiPath
46504fd306cSNickeau    {
46604fd306cSNickeau
46704fd306cSNickeau        $schemeQualified = WikiFileSystem::SCHEME . "://";
46804fd306cSNickeau        $lengthSchemeQualified = strlen($schemeQualified);
46904fd306cSNickeau        $uriScheme = substr($uri, 0, $lengthSchemeQualified);
47004fd306cSNickeau        if ($uriScheme !== $schemeQualified) {
47104fd306cSNickeau            throw new ExceptionBadArgument("The uri ($uri) is not a wiki uri");
47204fd306cSNickeau        }
47304fd306cSNickeau        $uriWithoutScheme = substr($uri, $lengthSchemeQualified);
47404fd306cSNickeau        $locationQuestionMark = strpos($uriWithoutScheme, "?");
47504fd306cSNickeau        if ($locationQuestionMark === false) {
47604fd306cSNickeau            $pathAndDrive = $uriWithoutScheme;
47704fd306cSNickeau            $rev = '';
47804fd306cSNickeau        } else {
47904fd306cSNickeau            $pathAndDrive = substr($uriWithoutScheme, 0, $locationQuestionMark);
48004fd306cSNickeau            $query = substr($uriWithoutScheme, $locationQuestionMark + 1);
48104fd306cSNickeau            parse_str($query, $queryKeys);
48204fd306cSNickeau            $queryKeys = new ArrayCaseInsensitive($queryKeys);
48304fd306cSNickeau            $rev = $queryKeys['rev'];
48404fd306cSNickeau        }
48504fd306cSNickeau        $locationGreaterThan = strpos($pathAndDrive, ">");
48604fd306cSNickeau        if ($locationGreaterThan === false) {
48704fd306cSNickeau            $path = $pathAndDrive;
48804fd306cSNickeau            $locationLastPoint = strrpos($pathAndDrive, ".");
48904fd306cSNickeau            if ($locationLastPoint === false) {
49004fd306cSNickeau                $drive = WikiPath::MARKUP_DRIVE;
49104fd306cSNickeau            } else {
49204fd306cSNickeau                $extension = substr($pathAndDrive, $locationLastPoint + 1);
49304fd306cSNickeau                if (in_array($extension, WikiPath::ALL_MARKUP_EXTENSIONS)) {
49404fd306cSNickeau                    $drive = WikiPath::MARKUP_DRIVE;
49504fd306cSNickeau                } else {
49604fd306cSNickeau                    $drive = WikiPath::MEDIA_DRIVE;
49704fd306cSNickeau                }
49804fd306cSNickeau            }
49904fd306cSNickeau        } else {
50004fd306cSNickeau            $drive = substr($pathAndDrive, 0, $locationGreaterThan);
50104fd306cSNickeau            $path = substr($pathAndDrive, $locationGreaterThan + 1);
50204fd306cSNickeau        }
50304fd306cSNickeau        return new WikiPath(":$path", $drive, $rev);
50404fd306cSNickeau    }
50504fd306cSNickeau
50604fd306cSNickeau
50704fd306cSNickeau    public
50804fd306cSNickeau    static function createMarkupPathFromId($id, $rev = null): WikiPath
50904fd306cSNickeau    {
51004fd306cSNickeau        if (strpos($id, WikiFileSystem::SCHEME . "://") !== false) {
51104fd306cSNickeau            return WikiPath::createFromUri($id);
51204fd306cSNickeau        }
51304fd306cSNickeau        WikiPath::addRootSeparatorIfNotPresent($id);
51404fd306cSNickeau        return self::createMarkupPathFromPath($id);
51504fd306cSNickeau    }
51604fd306cSNickeau
51704fd306cSNickeau    /**
51804fd306cSNickeau     * If the id does not have a root separator,
51904fd306cSNickeau     * it's added (ie to transform an id to a path)
52004fd306cSNickeau     * @param string $path
52104fd306cSNickeau     */
52204fd306cSNickeau    public
52304fd306cSNickeau    static function addRootSeparatorIfNotPresent(string &$path)
52404fd306cSNickeau    {
52504fd306cSNickeau        $firstCharacter = substr($path, 0, 1);
52604fd306cSNickeau        if (!in_array($firstCharacter, [WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, WikiPath::CURRENT_PATH_CHARACTER])) {
52704fd306cSNickeau            $path = WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $path;
52804fd306cSNickeau        }
52904fd306cSNickeau    }
53004fd306cSNickeau
53104fd306cSNickeau    /**
53204fd306cSNickeau     * @param string $relativePath
53304fd306cSNickeau     * @return string - a dokuwiki path (replacing the windows or linux path separator to the dokuwiki separator)
53404fd306cSNickeau     */
53504fd306cSNickeau    public
53604fd306cSNickeau    static function toDokuWikiSeparator(string $relativePath): string
53704fd306cSNickeau    {
53804fd306cSNickeau        return preg_replace('/[\\\\\/]/', ":", $relativePath);
53904fd306cSNickeau    }
54004fd306cSNickeau
54104fd306cSNickeau
54204fd306cSNickeau    /**
54304fd306cSNickeau     * @param $path - a manual path value
54404fd306cSNickeau     * @return string -  a valid path
54504fd306cSNickeau     */
54604fd306cSNickeau    public
54704fd306cSNickeau    static function toValidAbsolutePath($path): string
54804fd306cSNickeau    {
54904fd306cSNickeau        $path = cleanID($path);
55004fd306cSNickeau        WikiPath::addRootSeparatorIfNotPresent($path);
55104fd306cSNickeau        return $path;
55204fd306cSNickeau    }
55304fd306cSNickeau
55404fd306cSNickeau    /**
55504fd306cSNickeau     */
55604fd306cSNickeau    public
55704fd306cSNickeau    static function createComboResource($stringPath): WikiPath
55804fd306cSNickeau    {
55904fd306cSNickeau        return new WikiPath($stringPath, self::COMBO_DRIVE);
56004fd306cSNickeau    }
56104fd306cSNickeau
56204fd306cSNickeau
56304fd306cSNickeau    /**
56404fd306cSNickeau     * @param $path - relative or absolute path
56504fd306cSNickeau     * @param $drive - the drive
56604fd306cSNickeau     * @param string $rev - the revision
56704fd306cSNickeau     * @return WikiPath
56804fd306cSNickeau     */
56904fd306cSNickeau    public
57004fd306cSNickeau    static function createWikiPath($path, $drive, string $rev = ''): WikiPath
57104fd306cSNickeau    {
57204fd306cSNickeau        return new WikiPath($path, $drive, $rev);
57304fd306cSNickeau    }
57404fd306cSNickeau
57504fd306cSNickeau    /**
57604fd306cSNickeau     * The executing markup
57704fd306cSNickeau     * @throws ExceptionNotFound
57804fd306cSNickeau     */
57904fd306cSNickeau    public
58004fd306cSNickeau    static function createExecutingMarkupWikiPath(): WikiPath
58104fd306cSNickeau    {
58204fd306cSNickeau        return ExecutionContext::getActualOrCreateFromEnv()
58304fd306cSNickeau            ->getExecutingWikiPath();
58404fd306cSNickeau
58504fd306cSNickeau    }
58604fd306cSNickeau
58704fd306cSNickeau
58804fd306cSNickeau    /**
58904fd306cSNickeau     * @throws ExceptionNotFound
59004fd306cSNickeau     */
59104fd306cSNickeau    public
59204fd306cSNickeau    static function createRequestedPagePathFromRequest(): WikiPath
59304fd306cSNickeau    {
59404fd306cSNickeau        return ExecutionContext::getActualOrCreateFromEnv()->getRequestedPath();
59504fd306cSNickeau    }
59604fd306cSNickeau
59704fd306cSNickeau    /**
59804fd306cSNickeau     * @throws ExceptionBadArgument - if the path is not a local path or is not in a known drive
59904fd306cSNickeau     */
60004fd306cSNickeau    public
60104fd306cSNickeau    static function createFromPathObject(Path $path): WikiPath
60204fd306cSNickeau    {
60304fd306cSNickeau        if ($path instanceof WikiPath) {
60404fd306cSNickeau            return $path;
60504fd306cSNickeau        }
60604fd306cSNickeau        if (!($path instanceof LocalPath)) {
60704fd306cSNickeau            throw new ExceptionBadArgument("The path ($path) is not a local path and cannot be converted to a wiki path");
60804fd306cSNickeau        }
60904fd306cSNickeau        $driveRoots = WikiPath::getDriveRoots();
61004fd306cSNickeau
61104fd306cSNickeau        foreach ($driveRoots as $driveRoot => $drivePath) {
61204fd306cSNickeau
61304fd306cSNickeau            try {
61404fd306cSNickeau                $relativePath = $path->relativize($drivePath);
61504fd306cSNickeau            } catch (ExceptionBadArgument $e) {
61604fd306cSNickeau                /**
61704fd306cSNickeau                 * The drive may be a symlink link
61804fd306cSNickeau                 * (not the path)
61904fd306cSNickeau                 */
62004fd306cSNickeau                if (!$drivePath->isSymlink()) {
62104fd306cSNickeau                    continue;
62204fd306cSNickeau                }
62304fd306cSNickeau                try {
62404fd306cSNickeau                    $drivePath = $drivePath->toCanonicalAbsolutePath();
62504fd306cSNickeau                    $relativePath = $path->relativize($drivePath);
62604fd306cSNickeau                } catch (ExceptionBadArgument $e) {
62704fd306cSNickeau                    // not a relative path
62804fd306cSNickeau                    continue;
62904fd306cSNickeau                }
63004fd306cSNickeau            }
63104fd306cSNickeau            $wikiId = $relativePath->toAbsoluteId();
63204fd306cSNickeau            if (FileSystems::isDirectory($path)) {
63304fd306cSNickeau                WikiPath::addNamespaceEndSeparatorIfNotPresent($wikiId);
63404fd306cSNickeau            }
63504fd306cSNickeau            WikiPath::addRootSeparatorIfNotPresent($wikiId);
63604fd306cSNickeau            return WikiPath::createWikiPath($wikiId, $driveRoot);
63704fd306cSNickeau
63804fd306cSNickeau        }
63904fd306cSNickeau        throw new ExceptionBadArgument("The local path ($path) is not inside a wiki path drive");
64004fd306cSNickeau
64104fd306cSNickeau    }
64204fd306cSNickeau
64304fd306cSNickeau    /**
64404fd306cSNickeau     * @return LocalPath[]
64504fd306cSNickeau     */
64604fd306cSNickeau    public
64704fd306cSNickeau    static function getDriveRoots(): array
64804fd306cSNickeau    {
64904fd306cSNickeau        return [
65004fd306cSNickeau            self::MEDIA_DRIVE => Site::getMediaDirectory(),
65104fd306cSNickeau            self::MARKUP_DRIVE => Site::getPageDirectory(),
65204fd306cSNickeau            self::COMBO_DRIVE => DirectoryLayout::getComboResourcesDirectory(),
65304fd306cSNickeau            self::COMBO_DATA_THEME_DRIVE => Site::getDataDirectory()->resolve("combo")->resolve("theme"),
65404fd306cSNickeau            self::CACHE_DRIVE => Site::getCacheDirectory()
65504fd306cSNickeau        ];
65604fd306cSNickeau    }
65704fd306cSNickeau
65804fd306cSNickeau    /**
65904fd306cSNickeau     *
66004fd306cSNickeau     * Wiki path system cannot make the difference between a txt file
66104fd306cSNickeau     * and a directory natively because there is no extension.
66204fd306cSNickeau     *
66304fd306cSNickeau     * ie `ns:name` is by default the file `ns:name.txt`
66404fd306cSNickeau     *
66504fd306cSNickeau     * To make this distinction, we add a `:` at the end
66604fd306cSNickeau     *
66704fd306cSNickeau     * TODO: May be ? We may also just check if the txt file exists
66804fd306cSNickeau     *   and if not if the directory exists
66904fd306cSNickeau     *
67004fd306cSNickeau     * Also related {@link WikiPath::addNamespaceEndSeparatorIfNotPresent()}
67104fd306cSNickeau     *
67204fd306cSNickeau     * @param string $namespacePath
67304fd306cSNickeau     * @return bool
67404fd306cSNickeau     */
67504fd306cSNickeau    public
67604fd306cSNickeau    static function isNamespacePath(string $namespacePath): bool
67704fd306cSNickeau    {
67804fd306cSNickeau        if (substr($namespacePath, -1) !== WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
67904fd306cSNickeau            return false;
68004fd306cSNickeau        }
68104fd306cSNickeau        return true;
68204fd306cSNickeau
68304fd306cSNickeau    }
68404fd306cSNickeau
68504fd306cSNickeau    /**
68604fd306cSNickeau     * @throws ExceptionBadSyntax
68704fd306cSNickeau     */
68804fd306cSNickeau    public
68904fd306cSNickeau    static function checkNamespacePath(string $namespacePath)
69004fd306cSNickeau    {
69104fd306cSNickeau        if (!self::isNamespacePath($namespacePath)) {
69204fd306cSNickeau            throw new ExceptionBadSyntax("The path ($namespacePath) is not a namespace path");
69304fd306cSNickeau        }
69404fd306cSNickeau    }
69504fd306cSNickeau
69604fd306cSNickeau    /**
69704fd306cSNickeau     * Add a end separator to the wiki path to pass the fact that this is a directory/namespace
69804fd306cSNickeau     * See {@link WikiPath::isNamespacePath()} for more info
69904fd306cSNickeau     *
70004fd306cSNickeau     * @param string $namespaceAttribute
70104fd306cSNickeau     * @return void
70204fd306cSNickeau     */
70304fd306cSNickeau    public
70404fd306cSNickeau    static function addNamespaceEndSeparatorIfNotPresent(string &$namespaceAttribute)
70504fd306cSNickeau    {
70604fd306cSNickeau        if (substr($namespaceAttribute, -1) !== WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
70704fd306cSNickeau            $namespaceAttribute = $namespaceAttribute . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT;
70804fd306cSNickeau        }
70904fd306cSNickeau    }
71004fd306cSNickeau
71104fd306cSNickeau    /**
71204fd306cSNickeau     * @param string $path - path or id
71304fd306cSNickeau     * @param string $drive
71404fd306cSNickeau     * @param string|null $rev
71504fd306cSNickeau     * @return WikiPath
71604fd306cSNickeau     */
71704fd306cSNickeau    public
71804fd306cSNickeau    static function createFromPath(string $path, string $drive, string $rev = null): WikiPath
71904fd306cSNickeau    {
72004fd306cSNickeau        return new WikiPath($path, $drive, $rev);
72104fd306cSNickeau    }
72204fd306cSNickeau
72304fd306cSNickeau
72404fd306cSNickeau    public
72504fd306cSNickeau    static function getContextPath(): WikiPath
72604fd306cSNickeau    {
72704fd306cSNickeau        return ExecutionContext::getActualOrCreateFromEnv()->getContextPath();
72804fd306cSNickeau    }
72904fd306cSNickeau
73004fd306cSNickeau    /**
73104fd306cSNickeau     * Normalize a valid id
73204fd306cSNickeau     * (ie from / to :)
73304fd306cSNickeau     *
73404fd306cSNickeau     * @param string $id
73504fd306cSNickeau     * @return array|string|string[]
73604fd306cSNickeau     *
73704fd306cSNickeau     * This is not the same than {@link MarkupRef::normalizePath()}
73804fd306cSNickeau     * because there is no relativity or any reserved character in a id
73904fd306cSNickeau     *
74004fd306cSNickeau     * as an {@link WikiPath::getWikiId() id} is a validated absolute path without root character
74104fd306cSNickeau     */
74204fd306cSNickeau    public
74304fd306cSNickeau    static function normalizeWikiPath(string $id)
74404fd306cSNickeau    {
74504fd306cSNickeau        return str_replace(WikiPath::NAMESPACE_SEPARATOR_SLASH, WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $id);
74604fd306cSNickeau    }
74704fd306cSNickeau
74804fd306cSNickeau    public
74904fd306cSNickeau    static function createRootNamespacePathOnMarkupDrive(): WikiPath
75004fd306cSNickeau    {
75104fd306cSNickeau        return WikiPath::createMarkupPathFromPath(self::NAMESPACE_SEPARATOR_DOUBLE_POINT);
75204fd306cSNickeau    }
75304fd306cSNickeau
75404fd306cSNickeau    /**
75504fd306cSNickeau     * @param $path
75604fd306cSNickeau     * @return string with the root path
75704fd306cSNickeau     */
75804fd306cSNickeau    public
75904fd306cSNickeau    static function removeRootSepIfPresent($path): string
76004fd306cSNickeau    {
76104fd306cSNickeau        $id = $path;
76204fd306cSNickeau        if ($id[0] === WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
76304fd306cSNickeau            return substr($id, 1);
76404fd306cSNickeau        }
76504fd306cSNickeau        return $id;
76604fd306cSNickeau    }
76704fd306cSNickeau
76804fd306cSNickeau
76904fd306cSNickeau    /**
77004fd306cSNickeau     * The last part of the path
77104fd306cSNickeau     * @throws ExceptionNotFound
77204fd306cSNickeau     */
77304fd306cSNickeau    public
77404fd306cSNickeau    function getLastName(): string
77504fd306cSNickeau    {
77604fd306cSNickeau        /**
77704fd306cSNickeau         * See also {@link noNSorNS}
77804fd306cSNickeau         */
77904fd306cSNickeau        $names = $this->getNames();
78070bbd7f1Sgerardnico        $lastName = $names[sizeOf($names) - 1] ?? null;
78104fd306cSNickeau        if ($lastName === null) {
78204fd306cSNickeau            throw new ExceptionNotFound("This path ($this) does not have any last name");
78304fd306cSNickeau        }
78404fd306cSNickeau        return $lastName;
78504fd306cSNickeau    }
78604fd306cSNickeau
78704fd306cSNickeau    public
78804fd306cSNickeau    function getNames(): array
78904fd306cSNickeau    {
79004fd306cSNickeau
79104fd306cSNickeau        $actualNames = explode(self::NAMESPACE_SEPARATOR_DOUBLE_POINT, $this->absolutePath);
79204fd306cSNickeau
79304fd306cSNickeau        /**
79404fd306cSNickeau         * First element can be an empty string
79504fd306cSNickeau         * Case of only one string without path separator
79604fd306cSNickeau         * the first element returned is an empty string
79704fd306cSNickeau         * Last element can be empty (namespace split, ie :ns:)
79804fd306cSNickeau         */
79904fd306cSNickeau        $names = [];
80004fd306cSNickeau        foreach ($actualNames as $name) {
80104fd306cSNickeau            /**
80204fd306cSNickeau             * Don't use the {@link empty()} function
80304fd306cSNickeau             * In the cache, we may have the directory '0'
80404fd306cSNickeau             * and it's empty but is valid name
80504fd306cSNickeau             */
80604fd306cSNickeau            if ($name !== "") {
80704fd306cSNickeau                $names[] = $name;
80804fd306cSNickeau            }
80904fd306cSNickeau        }
81004fd306cSNickeau
81104fd306cSNickeau        return $names;
81204fd306cSNickeau    }
81304fd306cSNickeau
81404fd306cSNickeau    /**
81504fd306cSNickeau     * @return bool true if this id represents a page
81604fd306cSNickeau     */
81704fd306cSNickeau    public
81804fd306cSNickeau    function isPage(): bool
81904fd306cSNickeau    {
82004fd306cSNickeau
82104fd306cSNickeau        if (
82204fd306cSNickeau            $this->drive === self::MARKUP_DRIVE
82304fd306cSNickeau            &&
82404fd306cSNickeau            !$this->isGlob()
82504fd306cSNickeau        ) {
82604fd306cSNickeau            return true;
82704fd306cSNickeau        } else {
82804fd306cSNickeau            return false;
82904fd306cSNickeau        }
83004fd306cSNickeau
83104fd306cSNickeau    }
83204fd306cSNickeau
83304fd306cSNickeau
83404fd306cSNickeau    public
83504fd306cSNickeau    function isGlob(): bool
83604fd306cSNickeau    {
83704fd306cSNickeau        /**
83804fd306cSNickeau         * {@link search_universal} triggers ACL check
83904fd306cSNickeau         * with id of the form :path:*
84004fd306cSNickeau         * (for directory ?)
84104fd306cSNickeau         */
84204fd306cSNickeau        return StringUtility::endWiths($this->getWikiId(), ":*");
84304fd306cSNickeau    }
84404fd306cSNickeau
84504fd306cSNickeau    public
84604fd306cSNickeau    function __toString()
84704fd306cSNickeau    {
84804fd306cSNickeau        return $this->toUriString();
84904fd306cSNickeau    }
85004fd306cSNickeau
85104fd306cSNickeau    /**
85204fd306cSNickeau     *
85304fd306cSNickeau     *
85404fd306cSNickeau     * @return string - the wiki id is the absolute path
85504fd306cSNickeau     * without the root separator (ie normalized)
85604fd306cSNickeau     *
85704fd306cSNickeau     * The index stores needs this value
85804fd306cSNickeau     * And most of the function that are not links related
85904fd306cSNickeau     * use this format (What fucked up is fucked up)
86004fd306cSNickeau     *
86104fd306cSNickeau     * The id is a validated absolute path without any root character.
86204fd306cSNickeau     *
86304fd306cSNickeau     * Heavily used inside Dokuwiki
86404fd306cSNickeau     */
86504fd306cSNickeau    public
86604fd306cSNickeau    function getWikiId(): string
86704fd306cSNickeau    {
86804fd306cSNickeau
86904fd306cSNickeau        return $this->id;
87004fd306cSNickeau
87104fd306cSNickeau    }
87204fd306cSNickeau
87304fd306cSNickeau    public
87404fd306cSNickeau    function getPath(): string
87504fd306cSNickeau    {
87604fd306cSNickeau
87704fd306cSNickeau        return $this->absolutePath;
87804fd306cSNickeau
87904fd306cSNickeau    }
88004fd306cSNickeau
88104fd306cSNickeau
88204fd306cSNickeau    public
88304fd306cSNickeau    function getScheme(): string
88404fd306cSNickeau    {
88504fd306cSNickeau
88604fd306cSNickeau        return WikiFileSystem::SCHEME;
88704fd306cSNickeau
88804fd306cSNickeau    }
88904fd306cSNickeau
89004fd306cSNickeau    /**
89104fd306cSNickeau     * The wiki revision value
89204fd306cSNickeau     * as seen in the {@link basicinfo()} function
89304fd306cSNickeau     * is the {@link File::getModifiedTime()} of the file
89404fd306cSNickeau     *
89504fd306cSNickeau     * Let op passing a revision to Dokuwiki will
89604fd306cSNickeau     * make it search to the history
89704fd306cSNickeau     * The actual file will then not be found
89804fd306cSNickeau     *
89904fd306cSNickeau     * @return string|null
90004fd306cSNickeau     * @throws ExceptionNotFound
90104fd306cSNickeau     */
90204fd306cSNickeau    public
90304fd306cSNickeau    function getRevision(): string
90404fd306cSNickeau    {
90504fd306cSNickeau        /**
90604fd306cSNickeau         * Empty because the value may be null or empty string
90704fd306cSNickeau         */
90804fd306cSNickeau        if (empty($this->rev)) {
90904fd306cSNickeau            throw new ExceptionNotFound("The rev was not set");
91004fd306cSNickeau        }
91104fd306cSNickeau        return $this->rev;
91204fd306cSNickeau    }
91304fd306cSNickeau
91404fd306cSNickeau    /**
91504fd306cSNickeau     *
91604fd306cSNickeau     * @throws ExceptionNotFound - if the revision is not set and the path does not exist
91704fd306cSNickeau     */
91804fd306cSNickeau    public
91904fd306cSNickeau    function getRevisionOrDefault()
92004fd306cSNickeau    {
92104fd306cSNickeau        try {
92204fd306cSNickeau            return $this->getRevision();
92304fd306cSNickeau        } catch (ExceptionNotFound $e) {
92404fd306cSNickeau            // same as $INFO['lastmod'];
92504fd306cSNickeau            return FileSystems::getModifiedTime($this)->getTimestamp();
92604fd306cSNickeau        }
92704fd306cSNickeau
92804fd306cSNickeau    }
92904fd306cSNickeau
93004fd306cSNickeau
93104fd306cSNickeau    /**
93204fd306cSNickeau     * @return string
93304fd306cSNickeau     *
93404fd306cSNickeau     * This is the local absolute path WITH the root separator.
93504fd306cSNickeau     * It's used in ref present in {@link MarkupRef link} or {@link MediaLink}
93604fd306cSNickeau     * when creating test, otherwise the ref is considered as relative
93704fd306cSNickeau     *
93804fd306cSNickeau     *
93904fd306cSNickeau     * Otherwise everywhere in Dokuwiki, they use the {@link WikiPath::getWikiId()} absolute value that does not have any root separator
94004fd306cSNickeau     * and is absolute (internal index, function, ...)
94104fd306cSNickeau     *
94204fd306cSNickeau     */
94304fd306cSNickeau    public
94404fd306cSNickeau    function getAbsolutePath(): string
94504fd306cSNickeau    {
94604fd306cSNickeau
94704fd306cSNickeau        return $this->absolutePath;
94804fd306cSNickeau
94904fd306cSNickeau    }
95004fd306cSNickeau
95104fd306cSNickeau    /**
95204fd306cSNickeau     * @return array the pages where the wiki file (page or media) is used
95304fd306cSNickeau     *   * backlinks for page
95404fd306cSNickeau     *   * page with media for media
95504fd306cSNickeau     */
95604fd306cSNickeau    public
95704fd306cSNickeau    function getReferencedBy(): array
95804fd306cSNickeau    {
95904fd306cSNickeau        $absoluteId = $this->getWikiId();
96004fd306cSNickeau        if ($this->drive == self::MEDIA_DRIVE) {
96104fd306cSNickeau            return idx_get_indexer()->lookupKey('relation_media', $absoluteId);
96204fd306cSNickeau        } else {
96304fd306cSNickeau            return idx_get_indexer()->lookupKey('relation_references', $absoluteId);
96404fd306cSNickeau        }
96504fd306cSNickeau    }
96604fd306cSNickeau
96704fd306cSNickeau
96804fd306cSNickeau    /**
96904fd306cSNickeau     * Return the path relative to the base directory
97004fd306cSNickeau     * (ie $conf[basedir])
97104fd306cSNickeau     * @return string
97204fd306cSNickeau     */
97304fd306cSNickeau    public
97404fd306cSNickeau    function toRelativeFileSystemPath(): string
97504fd306cSNickeau    {
97604fd306cSNickeau        $relativeSystemPath = ".";
97704fd306cSNickeau        if (!empty($this->getWikiId())) {
97804fd306cSNickeau            $relativeSystemPath .= "/" . utf8_encodeFN(str_replace(':', '/', $this->getWikiId()));
97904fd306cSNickeau        }
98004fd306cSNickeau        return $relativeSystemPath;
98104fd306cSNickeau
98204fd306cSNickeau    }
98304fd306cSNickeau
98404fd306cSNickeau    public
98504fd306cSNickeau    function isPublic(): bool
98604fd306cSNickeau    {
98704fd306cSNickeau        return $this->getAuthAclValue() >= AUTH_READ;
98804fd306cSNickeau    }
98904fd306cSNickeau
99004fd306cSNickeau    /**
99104fd306cSNickeau     * @return int - An AUTH_ value for this page for the current logged user
99204fd306cSNickeau     * See the file defines.php
99304fd306cSNickeau     *
99404fd306cSNickeau     */
99504fd306cSNickeau    public
99604fd306cSNickeau    function getAuthAclValue(): int
99704fd306cSNickeau    {
99804fd306cSNickeau        return auth_quickaclcheck($this->getWikiId());
99904fd306cSNickeau    }
100004fd306cSNickeau
100104fd306cSNickeau
100204fd306cSNickeau    public
100304fd306cSNickeau    static function getReservedWords(): array
100404fd306cSNickeau    {
100504fd306cSNickeau        if (self::$reservedWords == null) {
100604fd306cSNickeau            self::$reservedWords = array_merge(Url::RESERVED_WORDS, LocalPath::RESERVED_WINDOWS_CHARACTERS);
100704fd306cSNickeau        }
100804fd306cSNickeau        return self::$reservedWords;
100904fd306cSNickeau    }
101004fd306cSNickeau
101104fd306cSNickeau
101204fd306cSNickeau    /**
101304fd306cSNickeau     * The absolute path for a wiki path
101404fd306cSNickeau     * @return string - the wiki id with a root separator
101504fd306cSNickeau     */
101604fd306cSNickeau    function toAbsoluteId(): string
101704fd306cSNickeau    {
101804fd306cSNickeau        return self::NAMESPACE_SEPARATOR_DOUBLE_POINT . $this->getWikiId();
101904fd306cSNickeau    }
102004fd306cSNickeau
102104fd306cSNickeau
102204fd306cSNickeau    function toAbsolutePath(): Path
102304fd306cSNickeau    {
102404fd306cSNickeau        return new WikiPath($this->absolutePath, $this->drive, $this->rev);
102504fd306cSNickeau    }
102604fd306cSNickeau
102704fd306cSNickeau    /**
102804fd306cSNickeau     * The parent path is a directory (namespace)
102904fd306cSNickeau     * The root path throw an errors
103004fd306cSNickeau     *
103104fd306cSNickeau     * @return WikiPath
103204fd306cSNickeau     * @throws ExceptionNotFound when the root
103304fd306cSNickeau     */
103404fd306cSNickeau    function getParent(): Path
103504fd306cSNickeau    {
103604fd306cSNickeau        /**
103704fd306cSNickeau         * Same as {@link getNS()}
103804fd306cSNickeau         */
103904fd306cSNickeau        $names = $this->getNames();
104004fd306cSNickeau        switch (sizeof($names)) {
104104fd306cSNickeau            case 0:
104204fd306cSNickeau                throw new ExceptionNotFound("The path `{$this}` does not have any parent");
104304fd306cSNickeau            case 1:
104404fd306cSNickeau                return new WikiPath(WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $this->drive, $this->rev);
104504fd306cSNickeau            default:
104604fd306cSNickeau                $names = array_slice($names, 0, sizeof($names) - 1);
104704fd306cSNickeau                $path = implode(WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $names);
104804fd306cSNickeau                /**
104904fd306cSNickeau                 * Because DokuPath does not have the notion of extension
105004fd306cSNickeau                 * if this is a page, we don't known if this is a directory
105104fd306cSNickeau                 * or a page. To make the difference, we add a separator at the end
105204fd306cSNickeau                 */
105304fd306cSNickeau                $sep = self::NAMESPACE_SEPARATOR_DOUBLE_POINT;
105404fd306cSNickeau                $path = "$sep$path$sep";
105504fd306cSNickeau                return new WikiPath($path, $this->drive, $this->rev);
105604fd306cSNickeau        }
105704fd306cSNickeau
105804fd306cSNickeau    }
105904fd306cSNickeau
106004fd306cSNickeau    /**
106104fd306cSNickeau     * @throws ExceptionNotFound
106204fd306cSNickeau     */
106304fd306cSNickeau    function getMime(): Mime
106404fd306cSNickeau    {
106504fd306cSNickeau        if ($this->drive === self::MARKUP_DRIVE) {
106604fd306cSNickeau            return new Mime(Mime::PLAIN_TEXT);
106704fd306cSNickeau        }
106804fd306cSNickeau        return FileSystems::getMime($this);
106904fd306cSNickeau
107004fd306cSNickeau    }
107104fd306cSNickeau
107204fd306cSNickeau
107304fd306cSNickeau    public
107404fd306cSNickeau    function getDrive(): string
107504fd306cSNickeau    {
107604fd306cSNickeau        return $this->drive;
107704fd306cSNickeau    }
107804fd306cSNickeau
107904fd306cSNickeau    public
108004fd306cSNickeau    function resolve(string $name): WikiPath
108104fd306cSNickeau    {
108204fd306cSNickeau
108304fd306cSNickeau        // Directory path have already separator at the end, don't add it
108404fd306cSNickeau        if ($this->absolutePath[strlen($this->absolutePath) - 1] !== WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
108504fd306cSNickeau            $path = $this->absolutePath . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $name;
108604fd306cSNickeau        } else {
108704fd306cSNickeau            $path = $this->absolutePath . $name;
108804fd306cSNickeau        }
108904fd306cSNickeau        return new WikiPath($path, $this->getDrive());
109004fd306cSNickeau
109104fd306cSNickeau    }
109204fd306cSNickeau
109304fd306cSNickeau
109404fd306cSNickeau    function toUriString(): string
109504fd306cSNickeau    {
109604fd306cSNickeau        $driveSep = self::DRIVE_SEPARATOR;
109704fd306cSNickeau        $absolutePath = self::removeRootSepIfPresent($this->absolutePath);
109804fd306cSNickeau        $uri = "{$this->getScheme()}://$this->drive$driveSep$absolutePath";
109904fd306cSNickeau        if (!empty($this->rev)) {
110004fd306cSNickeau            $uri = "$uri?rev={$this->rev}";
110104fd306cSNickeau        }
110204fd306cSNickeau        return $uri;
110304fd306cSNickeau
110404fd306cSNickeau    }
110504fd306cSNickeau
110604fd306cSNickeau    function getUrl(): Url
110704fd306cSNickeau    {
110804fd306cSNickeau        return $this->toLocalPath()->getUrl();
110904fd306cSNickeau    }
111004fd306cSNickeau
111104fd306cSNickeau    function getHost(): string
111204fd306cSNickeau    {
111304fd306cSNickeau        return "localhost";
111404fd306cSNickeau    }
111504fd306cSNickeau
111604fd306cSNickeau    public
111704fd306cSNickeau    function resolveId($markupId): WikiPath
111804fd306cSNickeau    {
111904fd306cSNickeau        if ($this->getDrive() !== self::MARKUP_DRIVE) {
112004fd306cSNickeau            return $this->resolve($markupId);
112104fd306cSNickeau        }
112204fd306cSNickeau        if (!WikiPath::isNamespacePath($this->absolutePath)) {
112304fd306cSNickeau            try {
112404fd306cSNickeau                $contextId = $this->getParent()->getWikiId() . self::NAMESPACE_SEPARATOR_DOUBLE_POINT;
112504fd306cSNickeau            } catch (ExceptionNotFound $e) {
112604fd306cSNickeau                $contextId = "";
112704fd306cSNickeau            }
112804fd306cSNickeau        } else {
112904fd306cSNickeau            $contextId = $this->getWikiId();
113004fd306cSNickeau        }
113104fd306cSNickeau        return WikiPath::createMarkupPathFromId($contextId . $markupId);
113204fd306cSNickeau
113304fd306cSNickeau    }
113404fd306cSNickeau
113504fd306cSNickeau    /**
113604fd306cSNickeau     * @return LocalPath
113704fd306cSNickeau     * TODO: change it for a constructor on LocalPath
113804fd306cSNickeau     * @throws ExceptionCast
113904fd306cSNickeau     */
114004fd306cSNickeau    public
114104fd306cSNickeau    function toLocalPath(): LocalPath
114204fd306cSNickeau    {
114304fd306cSNickeau        /**
114404fd306cSNickeau         * File path
114504fd306cSNickeau         */
114604fd306cSNickeau        $isNamespacePath = self::isNamespacePath($this->absolutePath);
114704fd306cSNickeau        if ($isNamespacePath) {
114804fd306cSNickeau            /**
114904fd306cSNickeau             * Namespace
115004fd306cSNickeau             * (Fucked up is fucked up)
115104fd306cSNickeau             * We qualify for the namespace here
115204fd306cSNickeau             * because there is no link or media for a namespace
115304fd306cSNickeau             */
115404fd306cSNickeau            global $conf;
115504fd306cSNickeau            switch ($this->drive) {
115604fd306cSNickeau                case self::MEDIA_DRIVE:
115704fd306cSNickeau                    $localPath = LocalPath::createFromPathString($conf['mediadir']);
115804fd306cSNickeau                    break;
115904fd306cSNickeau                case self::MARKUP_DRIVE:
116004fd306cSNickeau                    $localPath = LocalPath::createFromPathString($conf['datadir']);
116104fd306cSNickeau                    break;
116204fd306cSNickeau                default:
116304fd306cSNickeau                    $localPath = WikiPath::getDriveRoots()[$this->drive];
116404fd306cSNickeau                    break;
116504fd306cSNickeau            }
116604fd306cSNickeau
116704fd306cSNickeau            foreach ($this->getNames() as $name) {
116804fd306cSNickeau                $localPath = $localPath->resolve($name);
116904fd306cSNickeau            }
117004fd306cSNickeau            return $localPath;
117104fd306cSNickeau        }
117204fd306cSNickeau
117304fd306cSNickeau        // File
117404fd306cSNickeau        switch ($this->drive) {
117504fd306cSNickeau            case self::MEDIA_DRIVE:
117604fd306cSNickeau                if (!empty($rev)) {
117704fd306cSNickeau                    $filePathString = mediaFN($this->id, $rev);
117804fd306cSNickeau                } else {
117904fd306cSNickeau                    $filePathString = mediaFN($this->id);
118004fd306cSNickeau                }
118104fd306cSNickeau                break;
118204fd306cSNickeau            case self::MARKUP_DRIVE:
118304fd306cSNickeau                /**
118404fd306cSNickeau                 * Adaptation of {@link WikiFN}
118504fd306cSNickeau                 */
118604fd306cSNickeau                global $conf;
118704fd306cSNickeau                try {
118804fd306cSNickeau                    $extension = $this->getExtension();
118904fd306cSNickeau                } catch (ExceptionNotFound $e) {
119004fd306cSNickeau                    LogUtility::internalError("For a markup path file, the extension should have been set. This is not the case for ($this)");
119104fd306cSNickeau                    $extension = self::MARKUP_DEFAULT_TXT_EXTENSION;
119204fd306cSNickeau                }
119304fd306cSNickeau                $idFileSystem = str_replace(':', '/', $this->id);
119404fd306cSNickeau                if (empty($this->rev)) {
119504fd306cSNickeau                    $filePathString = Site::getPageDirectory()->resolve(utf8_encodeFN($idFileSystem) . '.' . $extension)->toAbsoluteId();
119604fd306cSNickeau                } else {
119704fd306cSNickeau                    $filePathString = Site::getOldDirectory()->resolve(utf8_encodeFN($idFileSystem) . '.' . $this->rev . '.' . $extension)->toAbsoluteId();
119804fd306cSNickeau                    if ($conf['compression']) {
119904fd306cSNickeau                        //test for extensions here, we want to read both compressions
120004fd306cSNickeau                        if (file_exists($filePathString . '.gz')) {
120104fd306cSNickeau                            $filePathString .= '.gz';
120204fd306cSNickeau                        } elseif (file_exists($filePathString . '.bz2')) {
120304fd306cSNickeau                            $filePathString .= '.bz2';
120404fd306cSNickeau                        } else {
120504fd306cSNickeau                            // File doesnt exist yet, so we take the configured extension
120604fd306cSNickeau                            $filePathString .= '.' . $conf['compression'];
120704fd306cSNickeau                        }
120804fd306cSNickeau                    }
120904fd306cSNickeau                }
121004fd306cSNickeau
121104fd306cSNickeau                break;
121204fd306cSNickeau            default:
121304fd306cSNickeau                $baseDirectory = WikiPath::getDriveRoots()[$this->drive];
121404fd306cSNickeau                if ($baseDirectory === null) {
121504fd306cSNickeau                    // We don't throw, the file will just not exist
121604fd306cSNickeau                    // this is metadata
121704fd306cSNickeau                    throw new ExceptionCast("The drive ($this->drive) is unknown, the local file system path could not be found");
121804fd306cSNickeau                }
121904fd306cSNickeau                $filePath = $baseDirectory;
122004fd306cSNickeau                foreach ($this->getNames() as $name) {
122104fd306cSNickeau                    $filePath = $filePath->resolve($name);
122204fd306cSNickeau                }
122304fd306cSNickeau                $filePathString = $filePath->toAbsoluteId();
122404fd306cSNickeau                break;
122504fd306cSNickeau        }
122604fd306cSNickeau        return LocalPath::createFromPathString($filePathString);
122704fd306cSNickeau
122804fd306cSNickeau    }
122904fd306cSNickeau
123004fd306cSNickeau    public function hasRevision(): bool
123104fd306cSNickeau    {
123204fd306cSNickeau        try {
123304fd306cSNickeau            $this->getRevision();
123404fd306cSNickeau            return true;
123504fd306cSNickeau        } catch (ExceptionNotFound $e) {
123604fd306cSNickeau            return false;
123704fd306cSNickeau        }
123804fd306cSNickeau    }
123904fd306cSNickeau
124004fd306cSNickeau}
1241