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