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