xref: /template/strap/ComboStrap/WikiPath.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau
3*04fd306cSNickeaunamespace ComboStrap;
4*04fd306cSNickeau
5*04fd306cSNickeau
6*04fd306cSNickeauuse ComboStrap\Web\Url;
7*04fd306cSNickeau
8*04fd306cSNickeau/**
9*04fd306cSNickeau * Class DokuPath
10*04fd306cSNickeau * @package ComboStrap
11*04fd306cSNickeau * A dokuwiki path has the same structure than a windows path with a drive and a path
12*04fd306cSNickeau *
13*04fd306cSNickeau * The drive being a local path on the local file system
14*04fd306cSNickeau *
15*04fd306cSNickeau * Ultimately, this path is the application path and should be used everywhere.
16*04fd306cSNickeau * * for users input (ie in a link markup such as media and page link)
17*04fd306cSNickeau * * for output (ie creating the id for the url)
18*04fd306cSNickeau *
19*04fd306cSNickeau * Dokuwiki knows only two drives ({@link WikiPath::MARKUP_DRIVE} and {@link WikiPath::MEDIA_DRIVE}
20*04fd306cSNickeau * but we have added a couple more such as the {@link WikiPath::COMBO_DRIVE combo resources}
21*04fd306cSNickeau * and the {@link WikiPath::CACHE_DRIVE} to be able to serve resources
22*04fd306cSNickeau *
23*04fd306cSNickeau * TODO: because all {@link LocalPath} has at minium a drive (ie C:,D:, E: for windows or \ for linux)
24*04fd306cSNickeau *    A Wiki Path can be just a wrapper around every local path)
25*04fd306cSNickeau *    The {@link LocalPath::toWikiPath()} should not throw then but as not all drive
26*04fd306cSNickeau *    may be public, we need to add a drive functionality to get this information.
27*04fd306cSNickeau */
28*04fd306cSNickeauclass WikiPath extends PathAbs
29*04fd306cSNickeau{
30*04fd306cSNickeau
31*04fd306cSNickeau    const MEDIA_DRIVE = "media";
32*04fd306cSNickeau    const MARKUP_DRIVE = "markup";
33*04fd306cSNickeau    const UNKNOWN_DRIVE = "unknown";
34*04fd306cSNickeau    const NAMESPACE_SEPARATOR_DOUBLE_POINT = ":";
35*04fd306cSNickeau
36*04fd306cSNickeau    // https://www.dokuwiki.org/config:useslash
37*04fd306cSNickeau    const NAMESPACE_SEPARATOR_SLASH = "/";
38*04fd306cSNickeau
39*04fd306cSNickeau    const SEPARATORS = [self::NAMESPACE_SEPARATOR_DOUBLE_POINT, self::NAMESPACE_SEPARATOR_SLASH];
40*04fd306cSNickeau
41*04fd306cSNickeau    /**
42*04fd306cSNickeau     * For whatever reason, dokuwiki uses also on windows
43*04fd306cSNickeau     * the linux separator
44*04fd306cSNickeau     */
45*04fd306cSNickeau    public const DIRECTORY_SEPARATOR = "/";
46*04fd306cSNickeau    public const SLUG_SEPARATOR = "-";
47*04fd306cSNickeau
48*04fd306cSNickeau
49*04fd306cSNickeau    /**
50*04fd306cSNickeau     * Dokuwiki has a file system that starts at a page and/or media
51*04fd306cSNickeau     * directory that depends on the used syntax.
52*04fd306cSNickeau     *
53*04fd306cSNickeau     * It's a little bit the same than as the icon library (we set it as library then)
54*04fd306cSNickeau     *
55*04fd306cSNickeau     * This parameters is an URL parameter
56*04fd306cSNickeau     * that permits to set an another one
57*04fd306cSNickeau     * when retrieving the file via HTTP
58*04fd306cSNickeau     * For now, there is only one value: {@link WikiPath::COMBO_DRIVE}
59*04fd306cSNickeau     */
60*04fd306cSNickeau    public const DRIVE_ATTRIBUTE = "drive";
61*04fd306cSNickeau
62*04fd306cSNickeau    /**
63*04fd306cSNickeau     * The interwiki scheme that points to the
64*04fd306cSNickeau     * combo resources directory ie {@link WikiPath::COMBO_DRIVE}
65*04fd306cSNickeau     * ie
66*04fd306cSNickeau     *   combo>library:
67*04fd306cSNickeau     *   combo>image:
68*04fd306cSNickeau     */
69*04fd306cSNickeau    const COMBO_DRIVE = "combo";
70*04fd306cSNickeau    /**
71*04fd306cSNickeau     * The home directory for all themes
72*04fd306cSNickeau     */
73*04fd306cSNickeau    const COMBO_DATA_THEME_DRIVE = "combo-theme";
74*04fd306cSNickeau    const CACHE_DRIVE = "cache";
75*04fd306cSNickeau    const MARKUP_DEFAULT_TXT_EXTENSION = "txt";
76*04fd306cSNickeau    const MARKUP_MD_TXT_EXTENSION = "md";
77*04fd306cSNickeau    const REV_ATTRIBUTE = "rev";
78*04fd306cSNickeau    const CURRENT_PATH_CHARACTER = ".";
79*04fd306cSNickeau    const CURRENT_PARENT_PATH_CHARACTER = "..";
80*04fd306cSNickeau    const CANONICAL = "wiki-path";
81*04fd306cSNickeau    const ALL_MARKUP_EXTENSIONS = [self::MARKUP_DEFAULT_TXT_EXTENSION, self::MARKUP_MD_TXT_EXTENSION];
82*04fd306cSNickeau
83*04fd306cSNickeau
84*04fd306cSNickeau    /**
85*04fd306cSNickeau     * @var string[]
86*04fd306cSNickeau     */
87*04fd306cSNickeau    private static $reservedWords;
88*04fd306cSNickeau
89*04fd306cSNickeau    /**
90*04fd306cSNickeau     * @var string the path id passed to function (cleaned)
91*04fd306cSNickeau     */
92*04fd306cSNickeau    private $id;
93*04fd306cSNickeau
94*04fd306cSNickeau
95*04fd306cSNickeau    /**
96*04fd306cSNickeau     * @var string
97*04fd306cSNickeau     */
98*04fd306cSNickeau    private $drive;
99*04fd306cSNickeau    /**
100*04fd306cSNickeau     * @var string|null - ie mtime
101*04fd306cSNickeau     */
102*04fd306cSNickeau    private $rev;
103*04fd306cSNickeau
104*04fd306cSNickeau
105*04fd306cSNickeau    /**
106*04fd306cSNickeau     * The separator from the {@link WikiPath::getDrive()}
107*04fd306cSNickeau     */
108*04fd306cSNickeau    const DRIVE_SEPARATOR = ">";
109*04fd306cSNickeau    /**
110*04fd306cSNickeau     * @var string - the absolute path (we use it for now to handle directory by adding a separator at the end)
111*04fd306cSNickeau     */
112*04fd306cSNickeau    protected $absolutePath;
113*04fd306cSNickeau
114*04fd306cSNickeau    /**
115*04fd306cSNickeau     * DokuPath constructor.
116*04fd306cSNickeau     *
117*04fd306cSNickeau     * A path for the Dokuwiki File System
118*04fd306cSNickeau     *
119*04fd306cSNickeau     * @param string $path - the path (may be relative)
120*04fd306cSNickeau     * @param string $drive - the drive (media, page, combo) - same as in windows for the drive prefix (c, d, ...)
121*04fd306cSNickeau     * @param string|null $rev - the revision (mtime)
122*04fd306cSNickeau     *
123*04fd306cSNickeau     * Thee path should be a qualified/absolute path because in Dokuwiki, a link to a {@link MarkupPath}
124*04fd306cSNickeau     * that ends with the {@link WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT} points to a start page
125*04fd306cSNickeau     * and not to a namespace. The qualification occurs in the transformation
126*04fd306cSNickeau     * from ref to page.
127*04fd306cSNickeau     *   For a page: in {@link MarkupRef::getInternalPage()}
128*04fd306cSNickeau     *   For a media: in the {@link MediaLink::createMediaLinkFromId()}
129*04fd306cSNickeau     * Because this class is mostly the file representation, it should be able to
130*04fd306cSNickeau     * represents also a namespace
131*04fd306cSNickeau     */
132*04fd306cSNickeau    protected function __construct(string $path, string $drive, string $rev = null)
133*04fd306cSNickeau    {
134*04fd306cSNickeau
135*04fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv();
136*04fd306cSNickeau
137*04fd306cSNickeau        /**
138*04fd306cSNickeau         * Due to the fact that the request environment is set on the setup in test,
139*04fd306cSNickeau         * the path may be not normalized
140*04fd306cSNickeau         */
141*04fd306cSNickeau        $path = self::normalizeWikiPath($path);
142*04fd306cSNickeau
143*04fd306cSNickeau        if (trim($path) === "") {
144*04fd306cSNickeau            try {
145*04fd306cSNickeau                $path = WikiPath::getContextPath()->toAbsoluteId();
146*04fd306cSNickeau            } catch (ExceptionNotFound $e) {
147*04fd306cSNickeau                throw new ExceptionRuntimeInternal("The context path is unknwon. The empty path string needs it.");
148*04fd306cSNickeau            }
149*04fd306cSNickeau        }
150*04fd306cSNickeau
151*04fd306cSNickeau        /**
152*04fd306cSNickeau         * Relative Path ?
153*04fd306cSNickeau         */
154*04fd306cSNickeau        $this->absolutePath = $path;
155*04fd306cSNickeau        $firstCharacter = substr($path, 0, 1);
156*04fd306cSNickeau        if ($drive === self::MARKUP_DRIVE && $firstCharacter !== WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
157*04fd306cSNickeau            $parts = preg_split('/' . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . '/', $path);
158*04fd306cSNickeau            switch ($parts[0]) {
159*04fd306cSNickeau                case WikiPath::CURRENT_PATH_CHARACTER:
160*04fd306cSNickeau                    // delete the relative character
161*04fd306cSNickeau                    $parts = array_splice($parts, 1);
162*04fd306cSNickeau                    try {
163*04fd306cSNickeau                        $rootRelativePath = $executionContext->getContextNamespacePath();
164*04fd306cSNickeau                    } catch (ExceptionNotFound $e) {
165*04fd306cSNickeau                        // Root case: the relative path is in the root
166*04fd306cSNickeau                        // the root has no parent
167*04fd306cSNickeau                        LogUtility::error("The current relative path ({$this->absolutePath}) returns an error: {$e->getMessage()}", self::CANONICAL);
168*04fd306cSNickeau                        $rootRelativePath = WikiPath::createMarkupPathFromPath(WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT);
169*04fd306cSNickeau                    }
170*04fd306cSNickeau                    break;
171*04fd306cSNickeau                case WikiPath::CURRENT_PARENT_PATH_CHARACTER:
172*04fd306cSNickeau                    // delete the relative character
173*04fd306cSNickeau                    $parts = array_splice($parts, 1);
174*04fd306cSNickeau
175*04fd306cSNickeau                    $currentPagePath = $executionContext->getContextNamespacePath();
176*04fd306cSNickeau                    try {
177*04fd306cSNickeau                        $rootRelativePath = $currentPagePath->getParent();
178*04fd306cSNickeau                    } catch (ExceptionNotFound $e) {
179*04fd306cSNickeau                        LogUtility::error("The parent relative path ({$this->absolutePath}) returns an error: {$e->getMessage()}", self::CANONICAL);
180*04fd306cSNickeau                        $rootRelativePath = $executionContext->getContextNamespacePath();
181*04fd306cSNickeau                    }
182*04fd306cSNickeau
183*04fd306cSNickeau                    break;
184*04fd306cSNickeau                default:
185*04fd306cSNickeau                    /**
186*04fd306cSNickeau                     * just a relative name path
187*04fd306cSNickeau                     * (ie hallo)
188*04fd306cSNickeau                     */
189*04fd306cSNickeau                    $rootRelativePath = $executionContext->getContextNamespacePath();
190*04fd306cSNickeau                    break;
191*04fd306cSNickeau            }
192*04fd306cSNickeau            // is relative directory path ?
193*04fd306cSNickeau            // ie ..: or .:
194*04fd306cSNickeau            $isRelativeDirectoryPath = false;
195*04fd306cSNickeau            $countParts = sizeof($parts);
196*04fd306cSNickeau            if ($countParts > 0 && $parts[$countParts - 1] === "") {
197*04fd306cSNickeau                $isRelativeDirectoryPath = true;
198*04fd306cSNickeau                $parts = array_splice($parts, 0, $countParts - 1);
199*04fd306cSNickeau            }
200*04fd306cSNickeau            foreach ($parts as $part) {
201*04fd306cSNickeau                $rootRelativePath = $rootRelativePath->resolve($part);
202*04fd306cSNickeau            }
203*04fd306cSNickeau            $absolutePathString = $rootRelativePath->getAbsolutePath();
204*04fd306cSNickeau            if ($isRelativeDirectoryPath && !WikiPath::isNamespacePath($absolutePathString)) {
205*04fd306cSNickeau                $absolutePathString = $absolutePathString . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT;
206*04fd306cSNickeau            }
207*04fd306cSNickeau            $this->absolutePath = $absolutePathString;
208*04fd306cSNickeau        }
209*04fd306cSNickeau
210*04fd306cSNickeau
211*04fd306cSNickeau        /**
212*04fd306cSNickeau         * ACL check does not care about the type of id
213*04fd306cSNickeau         * https://www.dokuwiki.org/devel:event:auth_acl_check
214*04fd306cSNickeau         * https://github.com/splitbrain/dokuwiki/issues/3476
215*04fd306cSNickeau         *
216*04fd306cSNickeau         * We check if there is an extension
217*04fd306cSNickeau         * If this is the case, this is a media
218*04fd306cSNickeau         */
219*04fd306cSNickeau        if ($drive === self::UNKNOWN_DRIVE) {
220*04fd306cSNickeau            $lastPosition = StringUtility::lastIndexOf($path, ".");
221*04fd306cSNickeau            if ($lastPosition === FALSE) {
222*04fd306cSNickeau                $drive = self::MARKUP_DRIVE;
223*04fd306cSNickeau            } else {
224*04fd306cSNickeau                $drive = self::MEDIA_DRIVE;
225*04fd306cSNickeau            }
226*04fd306cSNickeau        }
227*04fd306cSNickeau        $this->drive = $drive;
228*04fd306cSNickeau
229*04fd306cSNickeau
230*04fd306cSNickeau        /**
231*04fd306cSNickeau         * We use interwiki to define the combo resources
232*04fd306cSNickeau         * (Internal use only)
233*04fd306cSNickeau         */
234*04fd306cSNickeau        $comboInterWikiScheme = "combo>";
235*04fd306cSNickeau        if (strpos($this->absolutePath, $comboInterWikiScheme) === 0) {
236*04fd306cSNickeau            $pathPart = substr($this->absolutePath, strlen($comboInterWikiScheme));
237*04fd306cSNickeau            $this->id = $this->toDokuWikiIdDriveContextual($pathPart);
238*04fd306cSNickeau            $this->drive = self::COMBO_DRIVE;
239*04fd306cSNickeau        } else {
240*04fd306cSNickeau            WikiPath::addRootSeparatorIfNotPresent($this->absolutePath);
241*04fd306cSNickeau            $this->id = $this->toDokuWikiIdDriveContextual($this->absolutePath);
242*04fd306cSNickeau        }
243*04fd306cSNickeau
244*04fd306cSNickeau
245*04fd306cSNickeau        $this->rev = $rev;
246*04fd306cSNickeau
247*04fd306cSNickeau    }
248*04fd306cSNickeau
249*04fd306cSNickeau
250*04fd306cSNickeau    /**
251*04fd306cSNickeau     * For a Markup drive path, a file path should have an extension
252*04fd306cSNickeau     * if it's not a namespace
253*04fd306cSNickeau     *
254*04fd306cSNickeau     * This function checks that
255*04fd306cSNickeau     *
256*04fd306cSNickeau     * @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)
257*04fd306cSNickeau     * @param string|null $rev - the revision (ie timestamp in number format)
258*04fd306cSNickeau     * @return WikiPath - the wiki path
259*04fd306cSNickeau     * @throws ExceptionBadArgument - if a relative path is given and the context path does not have any parent
260*04fd306cSNickeau     */
261*04fd306cSNickeau    public static function createMarkupPathFromPath(string $parameterPath, string $rev = null): WikiPath
262*04fd306cSNickeau    {
263*04fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv();
264*04fd306cSNickeau
265*04fd306cSNickeau        if ($parameterPath == "") {
266*04fd306cSNickeau            return $executionContext->getContextPath();
267*04fd306cSNickeau        }
268*04fd306cSNickeau        if (WikiPath::isNamespacePath($parameterPath)) {
269*04fd306cSNickeau
270*04fd306cSNickeau            if ($parameterPath[0] !== self::CURRENT_PATH_CHARACTER) {
271*04fd306cSNickeau                /**
272*04fd306cSNickeau                 * Not a relative path
273*04fd306cSNickeau                 */
274*04fd306cSNickeau                return new WikiPath($parameterPath, self::MARKUP_DRIVE, $rev);
275*04fd306cSNickeau            }
276*04fd306cSNickeau            /**
277*04fd306cSNickeau             * A relative path
278*04fd306cSNickeau             */
279*04fd306cSNickeau            $contextPath = $executionContext->getContextPath();
280*04fd306cSNickeau            if ($parameterPath === self::CURRENT_PARENT_PATH_CHARACTER . self::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
281*04fd306cSNickeau                /**
282*04fd306cSNickeau                 * ie processing `..:`
283*04fd306cSNickeau                 */
284*04fd306cSNickeau                try {
285*04fd306cSNickeau                    return $contextPath->getParent()->getParent();
286*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
287*04fd306cSNickeau                    throw new ExceptionBadArgument("The context path ($contextPath) does not have a grand parent, therefore the relative path ($parameterPath) is invalid.", $e);
288*04fd306cSNickeau                }
289*04fd306cSNickeau            }
290*04fd306cSNickeau            /**
291*04fd306cSNickeau             * ie processing `.:`
292*04fd306cSNickeau             */
293*04fd306cSNickeau            try {
294*04fd306cSNickeau                return $contextPath->getParent();
295*04fd306cSNickeau            } catch (ExceptionNotFound $e) {
296*04fd306cSNickeau                LogUtility::internalError("A context path is a page and should therefore have a parent", $e);
297*04fd306cSNickeau            }
298*04fd306cSNickeau
299*04fd306cSNickeau        }
300*04fd306cSNickeau
301*04fd306cSNickeau        /**
302*04fd306cSNickeau         * Default Path
303*04fd306cSNickeau         * (we add the txt extension if not present)
304*04fd306cSNickeau         */
305*04fd306cSNickeau        $defaultPath = $parameterPath;
306*04fd306cSNickeau        $lastName = $parameterPath;
307*04fd306cSNickeau        $lastSeparator = strrpos($parameterPath, self::NAMESPACE_SEPARATOR_DOUBLE_POINT);
308*04fd306cSNickeau        if ($lastSeparator !== false) {
309*04fd306cSNickeau            $lastName = substr($parameterPath, $lastSeparator);
310*04fd306cSNickeau        }
311*04fd306cSNickeau        $lastPoint = strpos($lastName, ".");
312*04fd306cSNickeau        if ($lastPoint === false) {
313*04fd306cSNickeau            $defaultPath = $defaultPath . '.' . self::MARKUP_DEFAULT_TXT_EXTENSION;
314*04fd306cSNickeau        } else {
315*04fd306cSNickeau            /**
316*04fd306cSNickeau             * Case such as file `1.22`
317*04fd306cSNickeau             */
318*04fd306cSNickeau            $parameterPathExtension = substr($lastName, $lastPoint + 1);
319*04fd306cSNickeau            if (!in_array($parameterPathExtension, self::ALL_MARKUP_EXTENSIONS)) {
320*04fd306cSNickeau                $defaultPath = $defaultPath . '.' . self::MARKUP_DEFAULT_TXT_EXTENSION;
321*04fd306cSNickeau            }
322*04fd306cSNickeau        }
323*04fd306cSNickeau        $defaultWikiPath = new WikiPath($defaultPath, self::MARKUP_DRIVE, $rev);
324*04fd306cSNickeau        if (FileSystems::exists($defaultWikiPath)) {
325*04fd306cSNickeau            return $defaultWikiPath;
326*04fd306cSNickeau        }
327*04fd306cSNickeau
328*04fd306cSNickeau        /**
329*04fd306cSNickeau         * Markup extension (Markdown, ...)
330*04fd306cSNickeau         */
331*04fd306cSNickeau        if(!isset($parameterPathExtension)) {
332*04fd306cSNickeau            foreach (self::ALL_MARKUP_EXTENSIONS as $markupExtension) {
333*04fd306cSNickeau                if ($markupExtension == self::MARKUP_DEFAULT_TXT_EXTENSION) {
334*04fd306cSNickeau                    continue;
335*04fd306cSNickeau                }
336*04fd306cSNickeau                $markupWikiPath = new WikiPath($parameterPath . '.' . $markupExtension, self::MARKUP_DRIVE, $rev);
337*04fd306cSNickeau                if (FileSystems::exists($markupWikiPath)) {
338*04fd306cSNickeau                    return $markupWikiPath;
339*04fd306cSNickeau                }
340*04fd306cSNickeau            }
341*04fd306cSNickeau        }
342*04fd306cSNickeau
343*04fd306cSNickeau        /**
344*04fd306cSNickeau         * Return the non-existen default wiki path
345*04fd306cSNickeau         */
346*04fd306cSNickeau        return $defaultWikiPath;
347*04fd306cSNickeau
348*04fd306cSNickeau    }
349*04fd306cSNickeau
350*04fd306cSNickeau
351*04fd306cSNickeau    public
352*04fd306cSNickeau    static function createMediaPathFromPath($path, $rev = null): WikiPath
353*04fd306cSNickeau    {
354*04fd306cSNickeau        return new WikiPath($path, WikiPath::MEDIA_DRIVE, $rev);
355*04fd306cSNickeau    }
356*04fd306cSNickeau
357*04fd306cSNickeau    /**
358*04fd306cSNickeau     * If the media may come from the
359*04fd306cSNickeau     * dokuwiki media or combo resources media,
360*04fd306cSNickeau     * you should use this function
361*04fd306cSNickeau     *
362*04fd306cSNickeau     * The constructor will determine the type based on
363*04fd306cSNickeau     * the id structure.
364*04fd306cSNickeau     * @param $id
365*04fd306cSNickeau     * @return WikiPath
366*04fd306cSNickeau     */
367*04fd306cSNickeau    public
368*04fd306cSNickeau    static function createFromUnknownRoot($id): WikiPath
369*04fd306cSNickeau    {
370*04fd306cSNickeau        return new WikiPath($id, WikiPath::UNKNOWN_DRIVE);
371*04fd306cSNickeau    }
372*04fd306cSNickeau
373*04fd306cSNickeau    /**
374*04fd306cSNickeau     * @param $url - a URL path http://whatever/hello/my/lord (The canonical)
375*04fd306cSNickeau     * @return WikiPath - a dokuwiki Id hello:my:lord
376*04fd306cSNickeau     * @deprecated for {@link FetcherPage::createPageFragmentFetcherFromUrl()}
377*04fd306cSNickeau     */
378*04fd306cSNickeau    public
379*04fd306cSNickeau    static function createFromUrl($url): WikiPath
380*04fd306cSNickeau    {
381*04fd306cSNickeau        // Replace / by : and suppress the first : because the global $ID does not have it
382*04fd306cSNickeau        $parsedQuery = parse_url($url, PHP_URL_QUERY);
383*04fd306cSNickeau        $parsedQueryArray = [];
384*04fd306cSNickeau        parse_str($parsedQuery, $parsedQueryArray);
385*04fd306cSNickeau        $queryId = 'id';
386*04fd306cSNickeau        if (array_key_exists($queryId, $parsedQueryArray)) {
387*04fd306cSNickeau            // Doku form (ie doku.php?id=)
388*04fd306cSNickeau            $id = $parsedQueryArray[$queryId];
389*04fd306cSNickeau        } else {
390*04fd306cSNickeau            // Slash form ie (/my/id)
391*04fd306cSNickeau            $urlPath = parse_url($url, PHP_URL_PATH);
392*04fd306cSNickeau            $id = substr(str_replace("/", ":", $urlPath), 1);
393*04fd306cSNickeau        }
394*04fd306cSNickeau        return self::createMarkupPathFromPath(":$id");
395*04fd306cSNickeau    }
396*04fd306cSNickeau
397*04fd306cSNickeau    /**
398*04fd306cSNickeau     * Static don't ask why
399*04fd306cSNickeau     * @param $pathId
400*04fd306cSNickeau     * @return false|string
401*04fd306cSNickeau     */
402*04fd306cSNickeau    public
403*04fd306cSNickeau    static function getLastPart($pathId)
404*04fd306cSNickeau    {
405*04fd306cSNickeau        $endSeparatorLocation = StringUtility::lastIndexOf($pathId, WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT);
406*04fd306cSNickeau        if ($endSeparatorLocation === false) {
407*04fd306cSNickeau            $endSeparatorLocation = StringUtility::lastIndexOf($pathId, WikiPath::NAMESPACE_SEPARATOR_SLASH);
408*04fd306cSNickeau        }
409*04fd306cSNickeau        if ($endSeparatorLocation === false) {
410*04fd306cSNickeau            $lastPathPart = $pathId;
411*04fd306cSNickeau        } else {
412*04fd306cSNickeau            $lastPathPart = substr($pathId, $endSeparatorLocation + 1);
413*04fd306cSNickeau        }
414*04fd306cSNickeau        return $lastPathPart;
415*04fd306cSNickeau    }
416*04fd306cSNickeau
417*04fd306cSNickeau    /**
418*04fd306cSNickeau     * @param $id
419*04fd306cSNickeau     * @return string
420*04fd306cSNickeau     * Return an path from a id
421*04fd306cSNickeau     */
422*04fd306cSNickeau    public
423*04fd306cSNickeau    static function IdToAbsolutePath($id)
424*04fd306cSNickeau    {
425*04fd306cSNickeau        if (is_null($id)) {
426*04fd306cSNickeau            LogUtility::msg("The id passed should not be null");
427*04fd306cSNickeau        }
428*04fd306cSNickeau        return WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $id;
429*04fd306cSNickeau    }
430*04fd306cSNickeau
431*04fd306cSNickeau    function toDokuWikiIdDriveContextual($path): string
432*04fd306cSNickeau    {
433*04fd306cSNickeau        /**
434*04fd306cSNickeau         * Delete the first separator
435*04fd306cSNickeau         */
436*04fd306cSNickeau        $id = self::removeRootSepIfPresent($path);
437*04fd306cSNickeau
438*04fd306cSNickeau        /**
439*04fd306cSNickeau         * If this is a markup, we delete the txt extension if any
440*04fd306cSNickeau         */
441*04fd306cSNickeau        if ($this->getDrive() === self::MARKUP_DRIVE) {
442*04fd306cSNickeau            StringUtility::rtrim($id, '.' . self::MARKUP_DEFAULT_TXT_EXTENSION);
443*04fd306cSNickeau        }
444*04fd306cSNickeau        return $id;
445*04fd306cSNickeau
446*04fd306cSNickeau    }
447*04fd306cSNickeau
448*04fd306cSNickeau    public
449*04fd306cSNickeau    static function createMediaPathFromId($id, $rev = null): WikiPath
450*04fd306cSNickeau    {
451*04fd306cSNickeau        WikiPath::addRootSeparatorIfNotPresent($id);
452*04fd306cSNickeau        return self::createMediaPathFromPath($id, $rev);
453*04fd306cSNickeau    }
454*04fd306cSNickeau
455*04fd306cSNickeau    public static function getComboCustomThemeHomeDirectory(): WikiPath
456*04fd306cSNickeau    {
457*04fd306cSNickeau        return new WikiPath(self::NAMESPACE_SEPARATOR_DOUBLE_POINT, self::COMBO_DATA_THEME_DRIVE);
458*04fd306cSNickeau    }
459*04fd306cSNickeau
460*04fd306cSNickeau    /**
461*04fd306cSNickeau     * @throws ExceptionBadArgument
462*04fd306cSNickeau     */
463*04fd306cSNickeau    public
464*04fd306cSNickeau    static function createFromUri(string $uri): WikiPath
465*04fd306cSNickeau    {
466*04fd306cSNickeau
467*04fd306cSNickeau        $schemeQualified = WikiFileSystem::SCHEME . "://";
468*04fd306cSNickeau        $lengthSchemeQualified = strlen($schemeQualified);
469*04fd306cSNickeau        $uriScheme = substr($uri, 0, $lengthSchemeQualified);
470*04fd306cSNickeau        if ($uriScheme !== $schemeQualified) {
471*04fd306cSNickeau            throw new ExceptionBadArgument("The uri ($uri) is not a wiki uri");
472*04fd306cSNickeau        }
473*04fd306cSNickeau        $uriWithoutScheme = substr($uri, $lengthSchemeQualified);
474*04fd306cSNickeau        $locationQuestionMark = strpos($uriWithoutScheme, "?");
475*04fd306cSNickeau        if ($locationQuestionMark === false) {
476*04fd306cSNickeau            $pathAndDrive = $uriWithoutScheme;
477*04fd306cSNickeau            $rev = '';
478*04fd306cSNickeau        } else {
479*04fd306cSNickeau            $pathAndDrive = substr($uriWithoutScheme, 0, $locationQuestionMark);
480*04fd306cSNickeau            $query = substr($uriWithoutScheme, $locationQuestionMark + 1);
481*04fd306cSNickeau            parse_str($query, $queryKeys);
482*04fd306cSNickeau            $queryKeys = new ArrayCaseInsensitive($queryKeys);
483*04fd306cSNickeau            $rev = $queryKeys['rev'];
484*04fd306cSNickeau        }
485*04fd306cSNickeau        $locationGreaterThan = strpos($pathAndDrive, ">");
486*04fd306cSNickeau        if ($locationGreaterThan === false) {
487*04fd306cSNickeau            $path = $pathAndDrive;
488*04fd306cSNickeau            $locationLastPoint = strrpos($pathAndDrive, ".");
489*04fd306cSNickeau            if ($locationLastPoint === false) {
490*04fd306cSNickeau                $drive = WikiPath::MARKUP_DRIVE;
491*04fd306cSNickeau            } else {
492*04fd306cSNickeau                $extension = substr($pathAndDrive, $locationLastPoint + 1);
493*04fd306cSNickeau                if (in_array($extension, WikiPath::ALL_MARKUP_EXTENSIONS)) {
494*04fd306cSNickeau                    $drive = WikiPath::MARKUP_DRIVE;
495*04fd306cSNickeau                } else {
496*04fd306cSNickeau                    $drive = WikiPath::MEDIA_DRIVE;
497*04fd306cSNickeau                }
498*04fd306cSNickeau            }
499*04fd306cSNickeau        } else {
500*04fd306cSNickeau            $drive = substr($pathAndDrive, 0, $locationGreaterThan);
501*04fd306cSNickeau            $path = substr($pathAndDrive, $locationGreaterThan + 1);
502*04fd306cSNickeau        }
503*04fd306cSNickeau        return new WikiPath(":$path", $drive, $rev);
504*04fd306cSNickeau    }
505*04fd306cSNickeau
506*04fd306cSNickeau
507*04fd306cSNickeau    public
508*04fd306cSNickeau    static function createMarkupPathFromId($id, $rev = null): WikiPath
509*04fd306cSNickeau    {
510*04fd306cSNickeau        if (strpos($id, WikiFileSystem::SCHEME . "://") !== false) {
511*04fd306cSNickeau            return WikiPath::createFromUri($id);
512*04fd306cSNickeau        }
513*04fd306cSNickeau        WikiPath::addRootSeparatorIfNotPresent($id);
514*04fd306cSNickeau        return self::createMarkupPathFromPath($id);
515*04fd306cSNickeau    }
516*04fd306cSNickeau
517*04fd306cSNickeau    /**
518*04fd306cSNickeau     * If the id does not have a root separator,
519*04fd306cSNickeau     * it's added (ie to transform an id to a path)
520*04fd306cSNickeau     * @param string $path
521*04fd306cSNickeau     */
522*04fd306cSNickeau    public
523*04fd306cSNickeau    static function addRootSeparatorIfNotPresent(string &$path)
524*04fd306cSNickeau    {
525*04fd306cSNickeau        $firstCharacter = substr($path, 0, 1);
526*04fd306cSNickeau        if (!in_array($firstCharacter, [WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, WikiPath::CURRENT_PATH_CHARACTER])) {
527*04fd306cSNickeau            $path = WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $path;
528*04fd306cSNickeau        }
529*04fd306cSNickeau    }
530*04fd306cSNickeau
531*04fd306cSNickeau    /**
532*04fd306cSNickeau     * @param string $relativePath
533*04fd306cSNickeau     * @return string - a dokuwiki path (replacing the windows or linux path separator to the dokuwiki separator)
534*04fd306cSNickeau     */
535*04fd306cSNickeau    public
536*04fd306cSNickeau    static function toDokuWikiSeparator(string $relativePath): string
537*04fd306cSNickeau    {
538*04fd306cSNickeau        return preg_replace('/[\\\\\/]/', ":", $relativePath);
539*04fd306cSNickeau    }
540*04fd306cSNickeau
541*04fd306cSNickeau
542*04fd306cSNickeau    /**
543*04fd306cSNickeau     * @param $path - a manual path value
544*04fd306cSNickeau     * @return string -  a valid path
545*04fd306cSNickeau     */
546*04fd306cSNickeau    public
547*04fd306cSNickeau    static function toValidAbsolutePath($path): string
548*04fd306cSNickeau    {
549*04fd306cSNickeau        $path = cleanID($path);
550*04fd306cSNickeau        WikiPath::addRootSeparatorIfNotPresent($path);
551*04fd306cSNickeau        return $path;
552*04fd306cSNickeau    }
553*04fd306cSNickeau
554*04fd306cSNickeau    /**
555*04fd306cSNickeau     */
556*04fd306cSNickeau    public
557*04fd306cSNickeau    static function createComboResource($stringPath): WikiPath
558*04fd306cSNickeau    {
559*04fd306cSNickeau        return new WikiPath($stringPath, self::COMBO_DRIVE);
560*04fd306cSNickeau    }
561*04fd306cSNickeau
562*04fd306cSNickeau
563*04fd306cSNickeau    /**
564*04fd306cSNickeau     * @param $path - relative or absolute path
565*04fd306cSNickeau     * @param $drive - the drive
566*04fd306cSNickeau     * @param string $rev - the revision
567*04fd306cSNickeau     * @return WikiPath
568*04fd306cSNickeau     */
569*04fd306cSNickeau    public
570*04fd306cSNickeau    static function createWikiPath($path, $drive, string $rev = ''): WikiPath
571*04fd306cSNickeau    {
572*04fd306cSNickeau        return new WikiPath($path, $drive, $rev);
573*04fd306cSNickeau    }
574*04fd306cSNickeau
575*04fd306cSNickeau    /**
576*04fd306cSNickeau     * The executing markup
577*04fd306cSNickeau     * @throws ExceptionNotFound
578*04fd306cSNickeau     */
579*04fd306cSNickeau    public
580*04fd306cSNickeau    static function createExecutingMarkupWikiPath(): WikiPath
581*04fd306cSNickeau    {
582*04fd306cSNickeau        return ExecutionContext::getActualOrCreateFromEnv()
583*04fd306cSNickeau            ->getExecutingWikiPath();
584*04fd306cSNickeau
585*04fd306cSNickeau    }
586*04fd306cSNickeau
587*04fd306cSNickeau
588*04fd306cSNickeau    /**
589*04fd306cSNickeau     * @throws ExceptionNotFound
590*04fd306cSNickeau     */
591*04fd306cSNickeau    public
592*04fd306cSNickeau    static function createRequestedPagePathFromRequest(): WikiPath
593*04fd306cSNickeau    {
594*04fd306cSNickeau        return ExecutionContext::getActualOrCreateFromEnv()->getRequestedPath();
595*04fd306cSNickeau    }
596*04fd306cSNickeau
597*04fd306cSNickeau    /**
598*04fd306cSNickeau     * @throws ExceptionBadArgument - if the path is not a local path or is not in a known drive
599*04fd306cSNickeau     */
600*04fd306cSNickeau    public
601*04fd306cSNickeau    static function createFromPathObject(Path $path): WikiPath
602*04fd306cSNickeau    {
603*04fd306cSNickeau        if ($path instanceof WikiPath) {
604*04fd306cSNickeau            return $path;
605*04fd306cSNickeau        }
606*04fd306cSNickeau        if (!($path instanceof LocalPath)) {
607*04fd306cSNickeau            throw new ExceptionBadArgument("The path ($path) is not a local path and cannot be converted to a wiki path");
608*04fd306cSNickeau        }
609*04fd306cSNickeau        $driveRoots = WikiPath::getDriveRoots();
610*04fd306cSNickeau
611*04fd306cSNickeau        foreach ($driveRoots as $driveRoot => $drivePath) {
612*04fd306cSNickeau
613*04fd306cSNickeau            try {
614*04fd306cSNickeau                $relativePath = $path->relativize($drivePath);
615*04fd306cSNickeau            } catch (ExceptionBadArgument $e) {
616*04fd306cSNickeau                /**
617*04fd306cSNickeau                 * The drive may be a symlink link
618*04fd306cSNickeau                 * (not the path)
619*04fd306cSNickeau                 */
620*04fd306cSNickeau                if (!$drivePath->isSymlink()) {
621*04fd306cSNickeau                    continue;
622*04fd306cSNickeau                }
623*04fd306cSNickeau                try {
624*04fd306cSNickeau                    $drivePath = $drivePath->toCanonicalAbsolutePath();
625*04fd306cSNickeau                    $relativePath = $path->relativize($drivePath);
626*04fd306cSNickeau                } catch (ExceptionBadArgument $e) {
627*04fd306cSNickeau                    // not a relative path
628*04fd306cSNickeau                    continue;
629*04fd306cSNickeau                }
630*04fd306cSNickeau            }
631*04fd306cSNickeau            $wikiId = $relativePath->toAbsoluteId();
632*04fd306cSNickeau            if (FileSystems::isDirectory($path)) {
633*04fd306cSNickeau                WikiPath::addNamespaceEndSeparatorIfNotPresent($wikiId);
634*04fd306cSNickeau            }
635*04fd306cSNickeau            WikiPath::addRootSeparatorIfNotPresent($wikiId);
636*04fd306cSNickeau            return WikiPath::createWikiPath($wikiId, $driveRoot);
637*04fd306cSNickeau
638*04fd306cSNickeau        }
639*04fd306cSNickeau        throw new ExceptionBadArgument("The local path ($path) is not inside a wiki path drive");
640*04fd306cSNickeau
641*04fd306cSNickeau    }
642*04fd306cSNickeau
643*04fd306cSNickeau    /**
644*04fd306cSNickeau     * @return LocalPath[]
645*04fd306cSNickeau     */
646*04fd306cSNickeau    public
647*04fd306cSNickeau    static function getDriveRoots(): array
648*04fd306cSNickeau    {
649*04fd306cSNickeau        return [
650*04fd306cSNickeau            self::MEDIA_DRIVE => Site::getMediaDirectory(),
651*04fd306cSNickeau            self::MARKUP_DRIVE => Site::getPageDirectory(),
652*04fd306cSNickeau            self::COMBO_DRIVE => DirectoryLayout::getComboResourcesDirectory(),
653*04fd306cSNickeau            self::COMBO_DATA_THEME_DRIVE => Site::getDataDirectory()->resolve("combo")->resolve("theme"),
654*04fd306cSNickeau            self::CACHE_DRIVE => Site::getCacheDirectory()
655*04fd306cSNickeau        ];
656*04fd306cSNickeau    }
657*04fd306cSNickeau
658*04fd306cSNickeau    /**
659*04fd306cSNickeau     *
660*04fd306cSNickeau     * Wiki path system cannot make the difference between a txt file
661*04fd306cSNickeau     * and a directory natively because there is no extension.
662*04fd306cSNickeau     *
663*04fd306cSNickeau     * ie `ns:name` is by default the file `ns:name.txt`
664*04fd306cSNickeau     *
665*04fd306cSNickeau     * To make this distinction, we add a `:` at the end
666*04fd306cSNickeau     *
667*04fd306cSNickeau     * TODO: May be ? We may also just check if the txt file exists
668*04fd306cSNickeau     *   and if not if the directory exists
669*04fd306cSNickeau     *
670*04fd306cSNickeau     * Also related {@link WikiPath::addNamespaceEndSeparatorIfNotPresent()}
671*04fd306cSNickeau     *
672*04fd306cSNickeau     * @param string $namespacePath
673*04fd306cSNickeau     * @return bool
674*04fd306cSNickeau     */
675*04fd306cSNickeau    public
676*04fd306cSNickeau    static function isNamespacePath(string $namespacePath): bool
677*04fd306cSNickeau    {
678*04fd306cSNickeau        if (substr($namespacePath, -1) !== WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
679*04fd306cSNickeau            return false;
680*04fd306cSNickeau        }
681*04fd306cSNickeau        return true;
682*04fd306cSNickeau
683*04fd306cSNickeau    }
684*04fd306cSNickeau
685*04fd306cSNickeau    /**
686*04fd306cSNickeau     * @throws ExceptionBadSyntax
687*04fd306cSNickeau     */
688*04fd306cSNickeau    public
689*04fd306cSNickeau    static function checkNamespacePath(string $namespacePath)
690*04fd306cSNickeau    {
691*04fd306cSNickeau        if (!self::isNamespacePath($namespacePath)) {
692*04fd306cSNickeau            throw new ExceptionBadSyntax("The path ($namespacePath) is not a namespace path");
693*04fd306cSNickeau        }
694*04fd306cSNickeau    }
695*04fd306cSNickeau
696*04fd306cSNickeau    /**
697*04fd306cSNickeau     * Add a end separator to the wiki path to pass the fact that this is a directory/namespace
698*04fd306cSNickeau     * See {@link WikiPath::isNamespacePath()} for more info
699*04fd306cSNickeau     *
700*04fd306cSNickeau     * @param string $namespaceAttribute
701*04fd306cSNickeau     * @return void
702*04fd306cSNickeau     */
703*04fd306cSNickeau    public
704*04fd306cSNickeau    static function addNamespaceEndSeparatorIfNotPresent(string &$namespaceAttribute)
705*04fd306cSNickeau    {
706*04fd306cSNickeau        if (substr($namespaceAttribute, -1) !== WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
707*04fd306cSNickeau            $namespaceAttribute = $namespaceAttribute . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT;
708*04fd306cSNickeau        }
709*04fd306cSNickeau    }
710*04fd306cSNickeau
711*04fd306cSNickeau    /**
712*04fd306cSNickeau     * @param string $path - path or id
713*04fd306cSNickeau     * @param string $drive
714*04fd306cSNickeau     * @param string|null $rev
715*04fd306cSNickeau     * @return WikiPath
716*04fd306cSNickeau     */
717*04fd306cSNickeau    public
718*04fd306cSNickeau    static function createFromPath(string $path, string $drive, string $rev = null): WikiPath
719*04fd306cSNickeau    {
720*04fd306cSNickeau        return new WikiPath($path, $drive, $rev);
721*04fd306cSNickeau    }
722*04fd306cSNickeau
723*04fd306cSNickeau
724*04fd306cSNickeau    public
725*04fd306cSNickeau    static function getContextPath(): WikiPath
726*04fd306cSNickeau    {
727*04fd306cSNickeau        return ExecutionContext::getActualOrCreateFromEnv()->getContextPath();
728*04fd306cSNickeau    }
729*04fd306cSNickeau
730*04fd306cSNickeau    /**
731*04fd306cSNickeau     * Normalize a valid id
732*04fd306cSNickeau     * (ie from / to :)
733*04fd306cSNickeau     *
734*04fd306cSNickeau     * @param string $id
735*04fd306cSNickeau     * @return array|string|string[]
736*04fd306cSNickeau     *
737*04fd306cSNickeau     * This is not the same than {@link MarkupRef::normalizePath()}
738*04fd306cSNickeau     * because there is no relativity or any reserved character in a id
739*04fd306cSNickeau     *
740*04fd306cSNickeau     * as an {@link WikiPath::getWikiId() id} is a validated absolute path without root character
741*04fd306cSNickeau     */
742*04fd306cSNickeau    public
743*04fd306cSNickeau    static function normalizeWikiPath(string $id)
744*04fd306cSNickeau    {
745*04fd306cSNickeau        return str_replace(WikiPath::NAMESPACE_SEPARATOR_SLASH, WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $id);
746*04fd306cSNickeau    }
747*04fd306cSNickeau
748*04fd306cSNickeau    public
749*04fd306cSNickeau    static function createRootNamespacePathOnMarkupDrive(): WikiPath
750*04fd306cSNickeau    {
751*04fd306cSNickeau        return WikiPath::createMarkupPathFromPath(self::NAMESPACE_SEPARATOR_DOUBLE_POINT);
752*04fd306cSNickeau    }
753*04fd306cSNickeau
754*04fd306cSNickeau    /**
755*04fd306cSNickeau     * @param $path
756*04fd306cSNickeau     * @return string with the root path
757*04fd306cSNickeau     */
758*04fd306cSNickeau    public
759*04fd306cSNickeau    static function removeRootSepIfPresent($path): string
760*04fd306cSNickeau    {
761*04fd306cSNickeau        $id = $path;
762*04fd306cSNickeau        if ($id[0] === WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
763*04fd306cSNickeau            return substr($id, 1);
764*04fd306cSNickeau        }
765*04fd306cSNickeau        return $id;
766*04fd306cSNickeau    }
767*04fd306cSNickeau
768*04fd306cSNickeau
769*04fd306cSNickeau    /**
770*04fd306cSNickeau     * The last part of the path
771*04fd306cSNickeau     * @throws ExceptionNotFound
772*04fd306cSNickeau     */
773*04fd306cSNickeau    public
774*04fd306cSNickeau    function getLastName(): string
775*04fd306cSNickeau    {
776*04fd306cSNickeau        /**
777*04fd306cSNickeau         * See also {@link noNSorNS}
778*04fd306cSNickeau         */
779*04fd306cSNickeau        $names = $this->getNames();
780*04fd306cSNickeau        $lastName = $names[sizeOf($names) - 1];
781*04fd306cSNickeau        if ($lastName === null) {
782*04fd306cSNickeau            throw new ExceptionNotFound("This path ($this) does not have any last name");
783*04fd306cSNickeau        }
784*04fd306cSNickeau        return $lastName;
785*04fd306cSNickeau    }
786*04fd306cSNickeau
787*04fd306cSNickeau    public
788*04fd306cSNickeau    function getNames(): array
789*04fd306cSNickeau    {
790*04fd306cSNickeau
791*04fd306cSNickeau        $actualNames = explode(self::NAMESPACE_SEPARATOR_DOUBLE_POINT, $this->absolutePath);
792*04fd306cSNickeau
793*04fd306cSNickeau        /**
794*04fd306cSNickeau         * First element can be an empty string
795*04fd306cSNickeau         * Case of only one string without path separator
796*04fd306cSNickeau         * the first element returned is an empty string
797*04fd306cSNickeau         * Last element can be empty (namespace split, ie :ns:)
798*04fd306cSNickeau         */
799*04fd306cSNickeau        $names = [];
800*04fd306cSNickeau        foreach ($actualNames as $name) {
801*04fd306cSNickeau            /**
802*04fd306cSNickeau             * Don't use the {@link empty()} function
803*04fd306cSNickeau             * In the cache, we may have the directory '0'
804*04fd306cSNickeau             * and it's empty but is valid name
805*04fd306cSNickeau             */
806*04fd306cSNickeau            if ($name !== "") {
807*04fd306cSNickeau                $names[] = $name;
808*04fd306cSNickeau            }
809*04fd306cSNickeau        }
810*04fd306cSNickeau
811*04fd306cSNickeau        return $names;
812*04fd306cSNickeau    }
813*04fd306cSNickeau
814*04fd306cSNickeau    /**
815*04fd306cSNickeau     * @return bool true if this id represents a page
816*04fd306cSNickeau     */
817*04fd306cSNickeau    public
818*04fd306cSNickeau    function isPage(): bool
819*04fd306cSNickeau    {
820*04fd306cSNickeau
821*04fd306cSNickeau        if (
822*04fd306cSNickeau            $this->drive === self::MARKUP_DRIVE
823*04fd306cSNickeau            &&
824*04fd306cSNickeau            !$this->isGlob()
825*04fd306cSNickeau        ) {
826*04fd306cSNickeau            return true;
827*04fd306cSNickeau        } else {
828*04fd306cSNickeau            return false;
829*04fd306cSNickeau        }
830*04fd306cSNickeau
831*04fd306cSNickeau    }
832*04fd306cSNickeau
833*04fd306cSNickeau
834*04fd306cSNickeau    public
835*04fd306cSNickeau    function isGlob(): bool
836*04fd306cSNickeau    {
837*04fd306cSNickeau        /**
838*04fd306cSNickeau         * {@link search_universal} triggers ACL check
839*04fd306cSNickeau         * with id of the form :path:*
840*04fd306cSNickeau         * (for directory ?)
841*04fd306cSNickeau         */
842*04fd306cSNickeau        return StringUtility::endWiths($this->getWikiId(), ":*");
843*04fd306cSNickeau    }
844*04fd306cSNickeau
845*04fd306cSNickeau    public
846*04fd306cSNickeau    function __toString()
847*04fd306cSNickeau    {
848*04fd306cSNickeau        return $this->toUriString();
849*04fd306cSNickeau    }
850*04fd306cSNickeau
851*04fd306cSNickeau    /**
852*04fd306cSNickeau     *
853*04fd306cSNickeau     *
854*04fd306cSNickeau     * @return string - the wiki id is the absolute path
855*04fd306cSNickeau     * without the root separator (ie normalized)
856*04fd306cSNickeau     *
857*04fd306cSNickeau     * The index stores needs this value
858*04fd306cSNickeau     * And most of the function that are not links related
859*04fd306cSNickeau     * use this format (What fucked up is fucked up)
860*04fd306cSNickeau     *
861*04fd306cSNickeau     * The id is a validated absolute path without any root character.
862*04fd306cSNickeau     *
863*04fd306cSNickeau     * Heavily used inside Dokuwiki
864*04fd306cSNickeau     */
865*04fd306cSNickeau    public
866*04fd306cSNickeau    function getWikiId(): string
867*04fd306cSNickeau    {
868*04fd306cSNickeau
869*04fd306cSNickeau        return $this->id;
870*04fd306cSNickeau
871*04fd306cSNickeau    }
872*04fd306cSNickeau
873*04fd306cSNickeau    public
874*04fd306cSNickeau    function getPath(): string
875*04fd306cSNickeau    {
876*04fd306cSNickeau
877*04fd306cSNickeau        return $this->absolutePath;
878*04fd306cSNickeau
879*04fd306cSNickeau    }
880*04fd306cSNickeau
881*04fd306cSNickeau
882*04fd306cSNickeau    public
883*04fd306cSNickeau    function getScheme(): string
884*04fd306cSNickeau    {
885*04fd306cSNickeau
886*04fd306cSNickeau        return WikiFileSystem::SCHEME;
887*04fd306cSNickeau
888*04fd306cSNickeau    }
889*04fd306cSNickeau
890*04fd306cSNickeau    /**
891*04fd306cSNickeau     * The wiki revision value
892*04fd306cSNickeau     * as seen in the {@link basicinfo()} function
893*04fd306cSNickeau     * is the {@link File::getModifiedTime()} of the file
894*04fd306cSNickeau     *
895*04fd306cSNickeau     * Let op passing a revision to Dokuwiki will
896*04fd306cSNickeau     * make it search to the history
897*04fd306cSNickeau     * The actual file will then not be found
898*04fd306cSNickeau     *
899*04fd306cSNickeau     * @return string|null
900*04fd306cSNickeau     * @throws ExceptionNotFound
901*04fd306cSNickeau     */
902*04fd306cSNickeau    public
903*04fd306cSNickeau    function getRevision(): string
904*04fd306cSNickeau    {
905*04fd306cSNickeau        /**
906*04fd306cSNickeau         * Empty because the value may be null or empty string
907*04fd306cSNickeau         */
908*04fd306cSNickeau        if (empty($this->rev)) {
909*04fd306cSNickeau            throw new ExceptionNotFound("The rev was not set");
910*04fd306cSNickeau        }
911*04fd306cSNickeau        return $this->rev;
912*04fd306cSNickeau    }
913*04fd306cSNickeau
914*04fd306cSNickeau    /**
915*04fd306cSNickeau     *
916*04fd306cSNickeau     * @throws ExceptionNotFound - if the revision is not set and the path does not exist
917*04fd306cSNickeau     */
918*04fd306cSNickeau    public
919*04fd306cSNickeau    function getRevisionOrDefault()
920*04fd306cSNickeau    {
921*04fd306cSNickeau        try {
922*04fd306cSNickeau            return $this->getRevision();
923*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
924*04fd306cSNickeau            // same as $INFO['lastmod'];
925*04fd306cSNickeau            return FileSystems::getModifiedTime($this)->getTimestamp();
926*04fd306cSNickeau        }
927*04fd306cSNickeau
928*04fd306cSNickeau    }
929*04fd306cSNickeau
930*04fd306cSNickeau
931*04fd306cSNickeau    /**
932*04fd306cSNickeau     * @return string
933*04fd306cSNickeau     *
934*04fd306cSNickeau     * This is the local absolute path WITH the root separator.
935*04fd306cSNickeau     * It's used in ref present in {@link MarkupRef link} or {@link MediaLink}
936*04fd306cSNickeau     * when creating test, otherwise the ref is considered as relative
937*04fd306cSNickeau     *
938*04fd306cSNickeau     *
939*04fd306cSNickeau     * Otherwise everywhere in Dokuwiki, they use the {@link WikiPath::getWikiId()} absolute value that does not have any root separator
940*04fd306cSNickeau     * and is absolute (internal index, function, ...)
941*04fd306cSNickeau     *
942*04fd306cSNickeau     */
943*04fd306cSNickeau    public
944*04fd306cSNickeau    function getAbsolutePath(): string
945*04fd306cSNickeau    {
946*04fd306cSNickeau
947*04fd306cSNickeau        return $this->absolutePath;
948*04fd306cSNickeau
949*04fd306cSNickeau    }
950*04fd306cSNickeau
951*04fd306cSNickeau    /**
952*04fd306cSNickeau     * @return array the pages where the wiki file (page or media) is used
953*04fd306cSNickeau     *   * backlinks for page
954*04fd306cSNickeau     *   * page with media for media
955*04fd306cSNickeau     */
956*04fd306cSNickeau    public
957*04fd306cSNickeau    function getReferencedBy(): array
958*04fd306cSNickeau    {
959*04fd306cSNickeau        $absoluteId = $this->getWikiId();
960*04fd306cSNickeau        if ($this->drive == self::MEDIA_DRIVE) {
961*04fd306cSNickeau            return idx_get_indexer()->lookupKey('relation_media', $absoluteId);
962*04fd306cSNickeau        } else {
963*04fd306cSNickeau            return idx_get_indexer()->lookupKey('relation_references', $absoluteId);
964*04fd306cSNickeau        }
965*04fd306cSNickeau    }
966*04fd306cSNickeau
967*04fd306cSNickeau
968*04fd306cSNickeau    /**
969*04fd306cSNickeau     * Return the path relative to the base directory
970*04fd306cSNickeau     * (ie $conf[basedir])
971*04fd306cSNickeau     * @return string
972*04fd306cSNickeau     */
973*04fd306cSNickeau    public
974*04fd306cSNickeau    function toRelativeFileSystemPath(): string
975*04fd306cSNickeau    {
976*04fd306cSNickeau        $relativeSystemPath = ".";
977*04fd306cSNickeau        if (!empty($this->getWikiId())) {
978*04fd306cSNickeau            $relativeSystemPath .= "/" . utf8_encodeFN(str_replace(':', '/', $this->getWikiId()));
979*04fd306cSNickeau        }
980*04fd306cSNickeau        return $relativeSystemPath;
981*04fd306cSNickeau
982*04fd306cSNickeau    }
983*04fd306cSNickeau
984*04fd306cSNickeau    public
985*04fd306cSNickeau    function isPublic(): bool
986*04fd306cSNickeau    {
987*04fd306cSNickeau        return $this->getAuthAclValue() >= AUTH_READ;
988*04fd306cSNickeau    }
989*04fd306cSNickeau
990*04fd306cSNickeau    /**
991*04fd306cSNickeau     * @return int - An AUTH_ value for this page for the current logged user
992*04fd306cSNickeau     * See the file defines.php
993*04fd306cSNickeau     *
994*04fd306cSNickeau     */
995*04fd306cSNickeau    public
996*04fd306cSNickeau    function getAuthAclValue(): int
997*04fd306cSNickeau    {
998*04fd306cSNickeau        return auth_quickaclcheck($this->getWikiId());
999*04fd306cSNickeau    }
1000*04fd306cSNickeau
1001*04fd306cSNickeau
1002*04fd306cSNickeau    public
1003*04fd306cSNickeau    static function getReservedWords(): array
1004*04fd306cSNickeau    {
1005*04fd306cSNickeau        if (self::$reservedWords == null) {
1006*04fd306cSNickeau            self::$reservedWords = array_merge(Url::RESERVED_WORDS, LocalPath::RESERVED_WINDOWS_CHARACTERS);
1007*04fd306cSNickeau        }
1008*04fd306cSNickeau        return self::$reservedWords;
1009*04fd306cSNickeau    }
1010*04fd306cSNickeau
1011*04fd306cSNickeau
1012*04fd306cSNickeau    /**
1013*04fd306cSNickeau     * The absolute path for a wiki path
1014*04fd306cSNickeau     * @return string - the wiki id with a root separator
1015*04fd306cSNickeau     */
1016*04fd306cSNickeau    function toAbsoluteId(): string
1017*04fd306cSNickeau    {
1018*04fd306cSNickeau        return self::NAMESPACE_SEPARATOR_DOUBLE_POINT . $this->getWikiId();
1019*04fd306cSNickeau    }
1020*04fd306cSNickeau
1021*04fd306cSNickeau
1022*04fd306cSNickeau    function toAbsolutePath(): Path
1023*04fd306cSNickeau    {
1024*04fd306cSNickeau        return new WikiPath($this->absolutePath, $this->drive, $this->rev);
1025*04fd306cSNickeau    }
1026*04fd306cSNickeau
1027*04fd306cSNickeau    /**
1028*04fd306cSNickeau     * The parent path is a directory (namespace)
1029*04fd306cSNickeau     * The root path throw an errors
1030*04fd306cSNickeau     *
1031*04fd306cSNickeau     * @return WikiPath
1032*04fd306cSNickeau     * @throws ExceptionNotFound when the root
1033*04fd306cSNickeau     */
1034*04fd306cSNickeau    function getParent(): Path
1035*04fd306cSNickeau    {
1036*04fd306cSNickeau        /**
1037*04fd306cSNickeau         * Same as {@link getNS()}
1038*04fd306cSNickeau         */
1039*04fd306cSNickeau        $names = $this->getNames();
1040*04fd306cSNickeau        switch (sizeof($names)) {
1041*04fd306cSNickeau            case 0:
1042*04fd306cSNickeau                throw new ExceptionNotFound("The path `{$this}` does not have any parent");
1043*04fd306cSNickeau            case 1:
1044*04fd306cSNickeau                return new WikiPath(WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $this->drive, $this->rev);
1045*04fd306cSNickeau            default:
1046*04fd306cSNickeau                $names = array_slice($names, 0, sizeof($names) - 1);
1047*04fd306cSNickeau                $path = implode(WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $names);
1048*04fd306cSNickeau                /**
1049*04fd306cSNickeau                 * Because DokuPath does not have the notion of extension
1050*04fd306cSNickeau                 * if this is a page, we don't known if this is a directory
1051*04fd306cSNickeau                 * or a page. To make the difference, we add a separator at the end
1052*04fd306cSNickeau                 */
1053*04fd306cSNickeau                $sep = self::NAMESPACE_SEPARATOR_DOUBLE_POINT;
1054*04fd306cSNickeau                $path = "$sep$path$sep";
1055*04fd306cSNickeau                return new WikiPath($path, $this->drive, $this->rev);
1056*04fd306cSNickeau        }
1057*04fd306cSNickeau
1058*04fd306cSNickeau    }
1059*04fd306cSNickeau
1060*04fd306cSNickeau    /**
1061*04fd306cSNickeau     * @throws ExceptionNotFound
1062*04fd306cSNickeau     */
1063*04fd306cSNickeau    function getMime(): Mime
1064*04fd306cSNickeau    {
1065*04fd306cSNickeau        if ($this->drive === self::MARKUP_DRIVE) {
1066*04fd306cSNickeau            return new Mime(Mime::PLAIN_TEXT);
1067*04fd306cSNickeau        }
1068*04fd306cSNickeau        return FileSystems::getMime($this);
1069*04fd306cSNickeau
1070*04fd306cSNickeau    }
1071*04fd306cSNickeau
1072*04fd306cSNickeau
1073*04fd306cSNickeau    public
1074*04fd306cSNickeau    function getDrive(): string
1075*04fd306cSNickeau    {
1076*04fd306cSNickeau        return $this->drive;
1077*04fd306cSNickeau    }
1078*04fd306cSNickeau
1079*04fd306cSNickeau    public
1080*04fd306cSNickeau    function resolve(string $name): WikiPath
1081*04fd306cSNickeau    {
1082*04fd306cSNickeau
1083*04fd306cSNickeau        // Directory path have already separator at the end, don't add it
1084*04fd306cSNickeau        if ($this->absolutePath[strlen($this->absolutePath) - 1] !== WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
1085*04fd306cSNickeau            $path = $this->absolutePath . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $name;
1086*04fd306cSNickeau        } else {
1087*04fd306cSNickeau            $path = $this->absolutePath . $name;
1088*04fd306cSNickeau        }
1089*04fd306cSNickeau        return new WikiPath($path, $this->getDrive());
1090*04fd306cSNickeau
1091*04fd306cSNickeau    }
1092*04fd306cSNickeau
1093*04fd306cSNickeau
1094*04fd306cSNickeau    function toUriString(): string
1095*04fd306cSNickeau    {
1096*04fd306cSNickeau        $driveSep = self::DRIVE_SEPARATOR;
1097*04fd306cSNickeau        $absolutePath = self::removeRootSepIfPresent($this->absolutePath);
1098*04fd306cSNickeau        $uri = "{$this->getScheme()}://$this->drive$driveSep$absolutePath";
1099*04fd306cSNickeau        if (!empty($this->rev)) {
1100*04fd306cSNickeau            $uri = "$uri?rev={$this->rev}";
1101*04fd306cSNickeau        }
1102*04fd306cSNickeau        return $uri;
1103*04fd306cSNickeau
1104*04fd306cSNickeau    }
1105*04fd306cSNickeau
1106*04fd306cSNickeau    function getUrl(): Url
1107*04fd306cSNickeau    {
1108*04fd306cSNickeau        return $this->toLocalPath()->getUrl();
1109*04fd306cSNickeau    }
1110*04fd306cSNickeau
1111*04fd306cSNickeau    function getHost(): string
1112*04fd306cSNickeau    {
1113*04fd306cSNickeau        return "localhost";
1114*04fd306cSNickeau    }
1115*04fd306cSNickeau
1116*04fd306cSNickeau    public
1117*04fd306cSNickeau    function resolveId($markupId): WikiPath
1118*04fd306cSNickeau    {
1119*04fd306cSNickeau        if ($this->getDrive() !== self::MARKUP_DRIVE) {
1120*04fd306cSNickeau            return $this->resolve($markupId);
1121*04fd306cSNickeau        }
1122*04fd306cSNickeau        if (!WikiPath::isNamespacePath($this->absolutePath)) {
1123*04fd306cSNickeau            try {
1124*04fd306cSNickeau                $contextId = $this->getParent()->getWikiId() . self::NAMESPACE_SEPARATOR_DOUBLE_POINT;
1125*04fd306cSNickeau            } catch (ExceptionNotFound $e) {
1126*04fd306cSNickeau                $contextId = "";
1127*04fd306cSNickeau            }
1128*04fd306cSNickeau        } else {
1129*04fd306cSNickeau            $contextId = $this->getWikiId();
1130*04fd306cSNickeau        }
1131*04fd306cSNickeau        return WikiPath::createMarkupPathFromId($contextId . $markupId);
1132*04fd306cSNickeau
1133*04fd306cSNickeau    }
1134*04fd306cSNickeau
1135*04fd306cSNickeau    /**
1136*04fd306cSNickeau     * @return LocalPath
1137*04fd306cSNickeau     * TODO: change it for a constructor on LocalPath
1138*04fd306cSNickeau     * @throws ExceptionCast
1139*04fd306cSNickeau     */
1140*04fd306cSNickeau    public
1141*04fd306cSNickeau    function toLocalPath(): LocalPath
1142*04fd306cSNickeau    {
1143*04fd306cSNickeau        /**
1144*04fd306cSNickeau         * File path
1145*04fd306cSNickeau         */
1146*04fd306cSNickeau        $isNamespacePath = self::isNamespacePath($this->absolutePath);
1147*04fd306cSNickeau        if ($isNamespacePath) {
1148*04fd306cSNickeau            /**
1149*04fd306cSNickeau             * Namespace
1150*04fd306cSNickeau             * (Fucked up is fucked up)
1151*04fd306cSNickeau             * We qualify for the namespace here
1152*04fd306cSNickeau             * because there is no link or media for a namespace
1153*04fd306cSNickeau             */
1154*04fd306cSNickeau            global $conf;
1155*04fd306cSNickeau            switch ($this->drive) {
1156*04fd306cSNickeau                case self::MEDIA_DRIVE:
1157*04fd306cSNickeau                    $localPath = LocalPath::createFromPathString($conf['mediadir']);
1158*04fd306cSNickeau                    break;
1159*04fd306cSNickeau                case self::MARKUP_DRIVE:
1160*04fd306cSNickeau                    $localPath = LocalPath::createFromPathString($conf['datadir']);
1161*04fd306cSNickeau                    break;
1162*04fd306cSNickeau                default:
1163*04fd306cSNickeau                    $localPath = WikiPath::getDriveRoots()[$this->drive];
1164*04fd306cSNickeau                    break;
1165*04fd306cSNickeau            }
1166*04fd306cSNickeau
1167*04fd306cSNickeau            foreach ($this->getNames() as $name) {
1168*04fd306cSNickeau                $localPath = $localPath->resolve($name);
1169*04fd306cSNickeau            }
1170*04fd306cSNickeau            return $localPath;
1171*04fd306cSNickeau        }
1172*04fd306cSNickeau
1173*04fd306cSNickeau        // File
1174*04fd306cSNickeau        switch ($this->drive) {
1175*04fd306cSNickeau            case self::MEDIA_DRIVE:
1176*04fd306cSNickeau                if (!empty($rev)) {
1177*04fd306cSNickeau                    $filePathString = mediaFN($this->id, $rev);
1178*04fd306cSNickeau                } else {
1179*04fd306cSNickeau                    $filePathString = mediaFN($this->id);
1180*04fd306cSNickeau                }
1181*04fd306cSNickeau                break;
1182*04fd306cSNickeau            case self::MARKUP_DRIVE:
1183*04fd306cSNickeau                /**
1184*04fd306cSNickeau                 * Adaptation of {@link WikiFN}
1185*04fd306cSNickeau                 */
1186*04fd306cSNickeau                global $conf;
1187*04fd306cSNickeau                try {
1188*04fd306cSNickeau                    $extension = $this->getExtension();
1189*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
1190*04fd306cSNickeau                    LogUtility::internalError("For a markup path file, the extension should have been set. This is not the case for ($this)");
1191*04fd306cSNickeau                    $extension = self::MARKUP_DEFAULT_TXT_EXTENSION;
1192*04fd306cSNickeau                }
1193*04fd306cSNickeau                $idFileSystem = str_replace(':', '/', $this->id);
1194*04fd306cSNickeau                if (empty($this->rev)) {
1195*04fd306cSNickeau                    $filePathString = Site::getPageDirectory()->resolve(utf8_encodeFN($idFileSystem) . '.' . $extension)->toAbsoluteId();
1196*04fd306cSNickeau                } else {
1197*04fd306cSNickeau                    $filePathString = Site::getOldDirectory()->resolve(utf8_encodeFN($idFileSystem) . '.' . $this->rev . '.' . $extension)->toAbsoluteId();
1198*04fd306cSNickeau                    if ($conf['compression']) {
1199*04fd306cSNickeau                        //test for extensions here, we want to read both compressions
1200*04fd306cSNickeau                        if (file_exists($filePathString . '.gz')) {
1201*04fd306cSNickeau                            $filePathString .= '.gz';
1202*04fd306cSNickeau                        } elseif (file_exists($filePathString . '.bz2')) {
1203*04fd306cSNickeau                            $filePathString .= '.bz2';
1204*04fd306cSNickeau                        } else {
1205*04fd306cSNickeau                            // File doesnt exist yet, so we take the configured extension
1206*04fd306cSNickeau                            $filePathString .= '.' . $conf['compression'];
1207*04fd306cSNickeau                        }
1208*04fd306cSNickeau                    }
1209*04fd306cSNickeau                }
1210*04fd306cSNickeau
1211*04fd306cSNickeau                break;
1212*04fd306cSNickeau            default:
1213*04fd306cSNickeau                $baseDirectory = WikiPath::getDriveRoots()[$this->drive];
1214*04fd306cSNickeau                if ($baseDirectory === null) {
1215*04fd306cSNickeau                    // We don't throw, the file will just not exist
1216*04fd306cSNickeau                    // this is metadata
1217*04fd306cSNickeau                    throw new ExceptionCast("The drive ($this->drive) is unknown, the local file system path could not be found");
1218*04fd306cSNickeau                }
1219*04fd306cSNickeau                $filePath = $baseDirectory;
1220*04fd306cSNickeau                foreach ($this->getNames() as $name) {
1221*04fd306cSNickeau                    $filePath = $filePath->resolve($name);
1222*04fd306cSNickeau                }
1223*04fd306cSNickeau                $filePathString = $filePath->toAbsoluteId();
1224*04fd306cSNickeau                break;
1225*04fd306cSNickeau        }
1226*04fd306cSNickeau        return LocalPath::createFromPathString($filePathString);
1227*04fd306cSNickeau
1228*04fd306cSNickeau    }
1229*04fd306cSNickeau
1230*04fd306cSNickeau    public function hasRevision(): bool
1231*04fd306cSNickeau    {
1232*04fd306cSNickeau        try {
1233*04fd306cSNickeau            $this->getRevision();
1234*04fd306cSNickeau            return true;
1235*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
1236*04fd306cSNickeau            return false;
1237*04fd306cSNickeau        }
1238*04fd306cSNickeau    }
1239*04fd306cSNickeau
1240*04fd306cSNickeau}
1241