1<?php 2 3 4namespace ComboStrap; 5 6 7use ComboStrap\Meta\Api\Metadata; 8use ComboStrap\Meta\Api\MetadataText; 9use ComboStrap\Meta\Api\MetadataWikiPath; 10use ComboStrap\Web\UrlRewrite; 11 12/** 13 * Class PageUrlPath 14 * @package ComboStrap 15 * 16 * 17 * The path (ie id attribute in the url) in a absolute format (ie with root) 18 * 19 * This is used in the {@link UrlRewrite} module where the path is rewritten 20 * 21 * url path: name for ns + slug (title) + page id 22 * or 23 * url path: canonical path + page id 24 * or 25 * url path: page path + page id 26 * 27 * 28 * - slug 29 * - hierarchical slug 30 * - permanent canonical path (page id) 31 * - canonical path 32 * - permanent page path (page id) 33 * - page path 34 * 35 * This is not the URL of the page but of the generated HTML web page (Ie {@link MarkupPath}) with all pages (slots) 36 */ 37class PageUrlPath extends MetadataText 38{ 39 40 /** 41 * 42 * The page id is separated in the URL with a "-" 43 * and not the standard "/" 44 * because in the devtool or any other tool, they takes 45 * the last part of the path as name. 46 * 47 * The name would be the short page id `22h2s2j4` 48 * and would have therefore no signification 49 * 50 * Instead the name is `metadata-manager-22h2s2j4` 51 * we can see then a page description, even order on it 52 */ 53 public const PAGE_ID_URL_SEPARATOR = "-"; 54 /** 55 * The canonical page for the page url 56 */ 57 public const CANONICAL = "page:url"; 58 const PROPERTY_NAME = "page-url-path"; 59 60 public static function createForPage(MarkupPath $page): PageUrlPath 61 { 62 return (new PageUrlPath()) 63 ->setResource($page); 64 } 65 66 public static function getShortEncodedPageIdFromUrlId($lastPartName) 67 { 68 $lastPosition = strrpos($lastPartName, PageUrlPath::PAGE_ID_URL_SEPARATOR); 69 if ($lastPosition === false) { 70 return null; 71 } 72 return substr($lastPartName, $lastPosition + 1); 73 } 74 75 static public function getTab(): string 76 { 77 return MetaManagerForm::TAB_REDIRECTION_VALUE; 78 } 79 80 public function getValue(): string 81 { 82 83 $page = $this->getResource(); 84 if (!($page instanceof MarkupPath)) { 85 throw new ExceptionNotFound("The Url Path is not implemented for the resource type (" . $page->getType() . ")"); 86 } 87 88 /** 89 * Type of Url 90 */ 91 $pageUrlType = PageUrlType::createFromPage($page); 92 $urlType = $pageUrlType->getValue(); 93 $urlTypeDefault = $pageUrlType->getDefaultValue(); 94 if ($urlType === $urlTypeDefault) { 95 // not sure why ? may be to not store the value if it has the same default 96 throw new ExceptionNotFound("Same value as default"); 97 } 98 return $this->getUrlPathFromType($urlType); 99 100 } 101 102 /** 103 * @return string 104 * 105 */ 106 public function getValueOrDefault(): string 107 { 108 try { 109 return $this->getValue(); 110 } catch (ExceptionNotFound $e) { 111 return $this->getDefaultValue(); 112 } 113 114 } 115 116 117 static public function getDescription(): string 118 { 119 return "The path used in the page url"; 120 } 121 122 static public function getLabel(): string 123 { 124 return "Url Path"; 125 } 126 127 static public function getName(): string 128 { 129 return self::PROPERTY_NAME; 130 } 131 132 static public function getPersistenceType(): string 133 { 134 return Metadata::DERIVED_METADATA; 135 } 136 137 static public function isMutable(): bool 138 { 139 return false; 140 } 141 142 /** 143 * @return string 144 */ 145 public function getDefaultValue(): string 146 { 147 148 $urlTypeDefault = PageUrlType::createFromPage($this->getResource())->getDefaultValue(); 149 return $this->getUrlPathFromType($urlTypeDefault); 150 151 } 152 153 static public function getCanonical(): string 154 { 155 return self::CANONICAL; 156 } 157 158 159 private 160 function toPermanentUrlPath(string $id): string 161 { 162 return $id . self::PAGE_ID_URL_SEPARATOR . $this->getPageIdAbbrUrlEncoded(); 163 } 164 165 /** 166 * Add a one letter checksum 167 * to verify that this is a page id abbr 168 * ( and not to hit the index for nothing ) 169 * @return string 170 */ 171 public 172 function getPageIdAbbrUrlEncoded(): ?string 173 { 174 $page = $this->getPage(); 175 if ($page->getPageIdAbbr() == null) return null; 176 $abbr = $page->getPageIdAbbr(); 177 return self::encodePageId($abbr); 178 } 179 180 /** 181 * Add a checksum character to the page id 182 * to check if it's a page id that we get in the url 183 * @param string $pageId 184 * @return string 185 */ 186 public static function encodePageId(string $pageId): string 187 { 188 return self::getPageIdChecksumCharacter($pageId) . $pageId; 189 } 190 191 /** 192 * @param string $encodedPageId 193 * @return string|null return the decoded page id or null if it's not an encoded page id 194 */ 195 public static function decodePageId(string $encodedPageId): ?string 196 { 197 if (empty($encodedPageId)) return null; 198 $checkSum = $encodedPageId[0]; 199 $extractedEncodedPageId = substr($encodedPageId, 1); 200 $calculatedCheckSum = self::getPageIdChecksumCharacter($extractedEncodedPageId); 201 if ($calculatedCheckSum == null) return null; 202 if ($calculatedCheckSum != $checkSum) return null; 203 return $extractedEncodedPageId; 204 } 205 206 /** 207 * @param string $pageId 208 * @return string|null - the checksum letter or null if this is not a page id 209 */ 210 public static function getPageIdChecksumCharacter(string $pageId): ?string 211 { 212 $total = 0; 213 for ($i = 0; $i < strlen($pageId); $i++) { 214 $letter = $pageId[$i]; 215 $pos = strpos(PageId::PAGE_ID_ALPHABET, $letter); 216 if ($pos === false) { 217 return null; 218 } 219 $total += $pos; 220 } 221 $checkSum = $total % strlen(PageId::PAGE_ID_ALPHABET); 222 return PageId::PAGE_ID_ALPHABET[$checkSum]; 223 } 224 225 /** 226 * Utility to change the type of the resource 227 * @return MarkupPath|null 228 */ 229 private function getPage(): ?MarkupPath 230 { 231 $resource = $this->getResource(); 232 if ($resource instanceof MarkupPath) { 233 return $resource; 234 } 235 return null; 236 } 237 238 /** 239 * In case of internal error, the path is returned 240 */ 241 public function getUrlPathFromType(string $urlType): string 242 { 243 244 $page = $this->getResource(); 245 $pagePath = $page->getPathObject()->toAbsoluteId(); 246 if ((!$page instanceof MarkupPath)) { 247 $message = "The url path is only for page resources"; 248 LogUtility::internalError($message, $this->getCanonical()); 249 return $pagePath; 250 } 251 252 253 switch ($urlType) { 254 case PageUrlType::CONF_VALUE_PAGE_PATH: 255 // the default 256 return $pagePath; 257 case PageUrlType::CONF_VALUE_PERMANENT_PAGE_PATH: 258 return $this->toPermanentUrlPath($pagePath); 259 case PageUrlType::CONF_VALUE_CANONICAL_PATH: 260 try { 261 return Canonical::createForPage($page)->getValueOrDefault()->toAbsoluteId(); 262 } catch (ExceptionNotFound $e) { 263 // no canonical, path as default 264 return $pagePath; 265 } 266 case PageUrlType::CONF_VALUE_PERMANENT_CANONICAL_PATH: 267 return $this->toPermanentUrlPath($page->getCanonicalOrDefault()); 268 case PageUrlType::CONF_VALUE_SLUG: 269 return $this->toPermanentUrlPath($page->getSlugOrDefault()); 270 case PageUrlType::CONF_VALUE_HIERARCHICAL_SLUG: 271 $urlPath = $page->getSlugOrDefault(); 272 $parentPage = $page; 273 while (true) { 274 try { 275 $parentPage = $parentPage->getParent(); 276 } catch (ExceptionNotFound $e) { 277 break; 278 } 279 if (!$parentPage->isRootHomePage()) { 280 $urlPath = Slug::toSlugPath($parentPage->getNameOrDefault()) . $urlPath; 281 } 282 } 283 return $this->toPermanentUrlPath($urlPath); 284 case PageUrlType::CONF_VALUE_HOMED_SLUG: 285 $urlPath = $page->getSlugOrDefault(); 286 try { 287 $parentPage = $page->getParent(); 288 if (!$parentPage->isRootHomePage()) { 289 $urlPath = Slug::toSlugPath($parentPage->getNameOrDefault()) . $urlPath; 290 } 291 } catch (ExceptionNotFound $e) { 292 // no parent page 293 } 294 return $this->toPermanentUrlPath($urlPath); 295 default: 296 $message = "The url type ($urlType) is unknown and was unexpected"; 297 LogUtility::internalError($message, self::PROPERTY_NAME); 298 return $pagePath; 299 300 } 301 } 302 303 static public function isOnForm(): bool 304 { 305 return true; 306 } 307 308 public function getValueOrDefaultAsWikiId(): string 309 { 310 return WikiPath::removeRootSepIfPresent($this->getValueOrDefault()); 311 } 312 313} 314