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