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