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