xref: /plugin/combo/ComboStrap/Router.php (revision 45a874f4355f8bee7459e5d3b79e86e68468b316)
1*45a874f4SNico<?php
2*45a874f4SNico
3*45a874f4SNiconamespace ComboStrap;
4*45a874f4SNico
5*45a874f4SNicouse ComboStrap\Meta\Field\AliasType;
6*45a874f4SNicouse ComboStrap\Web\Url;
7*45a874f4SNico
8*45a874f4SNicoclass Router
9*45a874f4SNico{
10*45a874f4SNico
11*45a874f4SNico
12*45a874f4SNico    public const GO_TO_SEARCH_ENGINE = 'GoToSearchEngine';
13*45a874f4SNico    public const GO_TO_NS_START_PAGE = 'GoToNsStartPage';
14*45a874f4SNico    public const GO_TO_EDIT_MODE = 'GoToEditMode';
15*45a874f4SNico    public const GO_TO_BEST_END_PAGE_NAME = 'GoToBestEndPageName';
16*45a874f4SNico    public const GO_TO_BEST_NAMESPACE = 'GoToBestNamespace';
17*45a874f4SNico    public const NOTHING = 'Nothing';
18*45a874f4SNico    public const GO_TO_BEST_PAGE_NAME = 'GoToBestPageName';
19*45a874f4SNico    private PageRules $pageRules;
20*45a874f4SNico
21*45a874f4SNico    /**
22*45a874f4SNico     * @throws ExceptionSqliteNotAvailable
23*45a874f4SNico     * @throws ExceptionNotFound - no redirection found
24*45a874f4SNico     */
25*45a874f4SNico    public function getRedirection(): RouterRedirection
26*45a874f4SNico    {
27*45a874f4SNico
28*45a874f4SNico        /**
29*45a874f4SNico         * Without SQLite, this module does not work further
30*45a874f4SNico         * It throws
31*45a874f4SNico         */
32*45a874f4SNico        Sqlite::createOrGetSqlite();
33*45a874f4SNico
34*45a874f4SNico        /**
35*45a874f4SNico         * Initiate Page Rules
36*45a874f4SNico         */
37*45a874f4SNico        $this->pageRules = new PageRules();
38*45a874f4SNico
39*45a874f4SNico
40*45a874f4SNico        /**
41*45a874f4SNico         * Unfortunately, DOKUWIKI_STARTED is not the first event
42*45a874f4SNico         * The id may have been changed by
43*45a874f4SNico         * {@link action_plugin_combo_lang::load_lang()}
44*45a874f4SNico         * function, that's why we check against the {@link $_REQUEST}
45*45a874f4SNico         * and not the global ID
46*45a874f4SNico         */
47*45a874f4SNico        $originalId = self::getOriginalIdFromRequest();
48*45a874f4SNico
49*45a874f4SNico        /**
50*45a874f4SNico         * Page is an existing id
51*45a874f4SNico         * in the database ?
52*45a874f4SNico         */
53*45a874f4SNico        global $ID;
54*45a874f4SNico        $requestedMarkupPath = MarkupPath::createMarkupFromId($ID);
55*45a874f4SNico        if (FileSystems::exists($requestedMarkupPath)) {
56*45a874f4SNico
57*45a874f4SNico            /**
58*45a874f4SNico             * If this is not the root home page
59*45a874f4SNico             * and if the canonical id is the not the same (the id has changed)
60*45a874f4SNico             * and if this is not a historical page (revision)
61*45a874f4SNico             * redirect
62*45a874f4SNico             */
63*45a874f4SNico            if (
64*45a874f4SNico                $originalId !== $requestedMarkupPath->getUrlId() // The id may have been changed
65*45a874f4SNico                && $ID != Site::getIndexPageName()
66*45a874f4SNico                && !isset($_REQUEST["rev"])
67*45a874f4SNico            ) {
68*45a874f4SNico                /**
69*45a874f4SNico                 * TODO: When saving for the first time, the page is not stored in the database
70*45a874f4SNico                 *   but that's not the case actually
71*45a874f4SNico                 */
72*45a874f4SNico                $databasePageRow = $requestedMarkupPath->getDatabasePage();
73*45a874f4SNico                if ($databasePageRow->exists()) {
74*45a874f4SNico                    /**
75*45a874f4SNico                     * A move may leave the database in a bad state,
76*45a874f4SNico                     * unfortunately (ie page is not in index, unable to update, ...)
77*45a874f4SNico                     * We test therefore if the database page id exists
78*45a874f4SNico                     */
79*45a874f4SNico                    $targetPageId = $databasePageRow->getFromRow("id");
80*45a874f4SNico                    $targetPath = MarkupPath::createMarkupFromId($targetPageId);
81*45a874f4SNico                    if (FileSystems::exists($targetPath)) {
82*45a874f4SNico                        return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_PERMALINK_EXTENDED)
83*45a874f4SNico                            ->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)
84*45a874f4SNico                            ->setTargetMarkupPath($targetPath)
85*45a874f4SNico                            ->build();
86*45a874f4SNico                    }
87*45a874f4SNico
88*45a874f4SNico                }
89*45a874f4SNico            }
90*45a874f4SNico        }
91*45a874f4SNico
92*45a874f4SNico        $identifier = $ID;
93*45a874f4SNico
94*45a874f4SNico        /**
95*45a874f4SNico         * Page Id in the url
96*45a874f4SNico         */
97*45a874f4SNico        $shortPageId = PageUrlPath::getShortEncodedPageIdFromUrlId($requestedMarkupPath->getPathObject()->getLastNameWithoutExtension());
98*45a874f4SNico        if ($shortPageId != null) {
99*45a874f4SNico            $pageId = PageUrlPath::decodePageId($shortPageId);
100*45a874f4SNico        } else {
101*45a874f4SNico            /**
102*45a874f4SNico             * Permalink with id
103*45a874f4SNico             */
104*45a874f4SNico            $pageId = PageUrlPath::decodePageId($identifier);
105*45a874f4SNico        }
106*45a874f4SNico        if ($pageId !== null) {
107*45a874f4SNico
108*45a874f4SNico            if ($requestedMarkupPath->getParent() === null) {
109*45a874f4SNico                $page = DatabasePageRow::createFromPageId($pageId)->getMarkupPath();
110*45a874f4SNico                if ($page !== null && $page->exists()) {
111*45a874f4SNico                    return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_PERMALINK)
112*45a874f4SNico                        ->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)
113*45a874f4SNico                        ->setTargetMarkupPath($page)
114*45a874f4SNico                        ->build();
115*45a874f4SNico                }
116*45a874f4SNico            }
117*45a874f4SNico
118*45a874f4SNico            /**
119*45a874f4SNico             * Page Id Abbr ?
120*45a874f4SNico             * {@link PageUrlType::CONF_CANONICAL_URL_TYPE}
121*45a874f4SNico             */
122*45a874f4SNico            $page = DatabasePageRow::createFromPageIdAbbr($pageId)->getMarkupPath();
123*45a874f4SNico            if ($page === null) {
124*45a874f4SNico                // or the length of the abbr has changed
125*45a874f4SNico                $canonicalDatabasePage = new DatabasePageRow();
126*45a874f4SNico                try {
127*45a874f4SNico                    $row = $canonicalDatabasePage->getDatabaseRowFromAttribute("substr(" . PageId::PROPERTY_NAME . ", 1, " . strlen($pageId) . ")", $pageId);
128*45a874f4SNico                    $canonicalDatabasePage->setRow($row);
129*45a874f4SNico                    $page = $canonicalDatabasePage->getMarkupPath();
130*45a874f4SNico                } catch (ExceptionNotFound $e) {
131*45a874f4SNico                    // nothing to do
132*45a874f4SNico                }
133*45a874f4SNico            }
134*45a874f4SNico            if ($page !== null && $page->exists()) {
135*45a874f4SNico                /**
136*45a874f4SNico                 * If the url canonical id has changed, we show it
137*45a874f4SNico                 * to the writer by performing a permanent redirect
138*45a874f4SNico                 */
139*45a874f4SNico                if ($identifier != $page->getUrlId()) {
140*45a874f4SNico                    // Google asks for a redirect
141*45a874f4SNico                    // https://developers.google.com/search/docs/advanced/crawling/301-redirects
142*45a874f4SNico                    // People access your site through several different URLs.
143*45a874f4SNico                    // If, for example, your home page can be reached in multiple ways
144*45a874f4SNico                    // (for instance, http://example.com/home, http://home.example.com, or http://www.example.com),
145*45a874f4SNico                    // it's a good idea to pick one of those URLs as your preferred (canonical) destination,
146*45a874f4SNico                    // and use redirects to send traffic from the other URLs to your preferred URL.
147*45a874f4SNico                    return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_PERMALINK_EXTENDED)
148*45a874f4SNico                        ->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)
149*45a874f4SNico                        ->setTargetMarkupPath($page)
150*45a874f4SNico                        ->build();
151*45a874f4SNico
152*45a874f4SNico                }
153*45a874f4SNico
154*45a874f4SNico                return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_PERMALINK_EXTENDED)
155*45a874f4SNico                    ->setType(RouterRedirection::REDIRECT_TRANSPARENT_METHOD)
156*45a874f4SNico                    ->setTargetMarkupPath($page)
157*45a874f4SNico                    ->build();
158*45a874f4SNico
159*45a874f4SNico            }
160*45a874f4SNico            // permanent url not yet in the database
161*45a874f4SNico            // Other permanent such as permanent canonical ?
162*45a874f4SNico            // We let the process go with the new identifier
163*45a874f4SNico
164*45a874f4SNico        }
165*45a874f4SNico
166*45a874f4SNico        /**
167*45a874f4SNico         * Identifier is a Canonical ?
168*45a874f4SNico         */
169*45a874f4SNico        $canonicalDatabasePage = DatabasePageRow::createFromCanonical($identifier);
170*45a874f4SNico        $canonicalPage = $canonicalDatabasePage->getMarkupPath();
171*45a874f4SNico        if ($canonicalPage !== null && $canonicalPage->exists()) {
172*45a874f4SNico            $builder = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_CANONICAL)
173*45a874f4SNico                ->setTargetMarkupPath($canonicalPage);
174*45a874f4SNico            /**
175*45a874f4SNico             * Does the canonical url is canonical name based
176*45a874f4SNico             * ie {@link  PageUrlType::CONF_VALUE_CANONICAL_PATH}
177*45a874f4SNico             */
178*45a874f4SNico            if ($canonicalPage->getUrlId() === $identifier) {
179*45a874f4SNico                $builder->setType(RouterRedirection::REDIRECT_TRANSPARENT_METHOD);
180*45a874f4SNico            } else {
181*45a874f4SNico                $builder->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD);
182*45a874f4SNico            }
183*45a874f4SNico            return $builder->build();
184*45a874f4SNico
185*45a874f4SNico        }
186*45a874f4SNico
187*45a874f4SNico        /**
188*45a874f4SNico         * Identifier is an alias
189*45a874f4SNico         */
190*45a874f4SNico        $aliasRequestedPage = DatabasePageRow::createFromAlias($identifier)->getMarkupPath();
191*45a874f4SNico        if (
192*45a874f4SNico            $aliasRequestedPage !== null
193*45a874f4SNico            && $aliasRequestedPage->exists()
194*45a874f4SNico            // The build alias is the file system metadata alias
195*45a874f4SNico            // it may be null if the replication in the database was not successful
196*45a874f4SNico            && $aliasRequestedPage->getBuildAlias() !== null
197*45a874f4SNico        ) {
198*45a874f4SNico            $buildAlias = $aliasRequestedPage->getBuildAlias();
199*45a874f4SNico            $builder = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_ALIAS)
200*45a874f4SNico                ->setTargetMarkupPath($aliasRequestedPage);
201*45a874f4SNico            switch ($buildAlias->getType()) {
202*45a874f4SNico                case AliasType::REDIRECT:
203*45a874f4SNico                    return $builder->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)->build();
204*45a874f4SNico                case AliasType::SYNONYM:
205*45a874f4SNico                    return $builder->setType(RouterRedirection::REDIRECT_TRANSPARENT_METHOD)->build();
206*45a874f4SNico                default:
207*45a874f4SNico                    LogUtility::msg("The alias type ({$buildAlias->getType()}) is unknown. A permanent redirect was performed for the alias $identifier");
208*45a874f4SNico                    return $builder->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)->build();
209*45a874f4SNico            }
210*45a874f4SNico        }
211*45a874f4SNico
212*45a874f4SNico        /**
213*45a874f4SNico         * Do we have a page rules
214*45a874f4SNico         * If there is a redirection defined in the page rules
215*45a874f4SNico         */
216*45a874f4SNico        try {
217*45a874f4SNico            return $this->getRedirectionFromPageRules();
218*45a874f4SNico        } catch (ExceptionNotFound $e) {
219*45a874f4SNico            // no pages rules redirection
220*45a874f4SNico        }
221*45a874f4SNico
222*45a874f4SNico        /**
223*45a874f4SNico         * No redirection found in the database by id
224*45a874f4SNico         */
225*45a874f4SNico
226*45a874f4SNico        /**
227*45a874f4SNico         * Edit mode
228*45a874f4SNico         */
229*45a874f4SNico        $conf = ExecutionContext::getActualOrCreateFromEnv()->getConfig();
230*45a874f4SNico        if (Identity::isWriter() && $conf->getBooleanValue(self::GO_TO_EDIT_MODE, true)) {
231*45a874f4SNico
232*45a874f4SNico            // Stop here
233*45a874f4SNico            return RouterRedirectionBuilder::createFromOrigin(self::GO_TO_EDIT_MODE)
234*45a874f4SNico                ->build();
235*45a874f4SNico
236*45a874f4SNico        }
237*45a874f4SNico
238*45a874f4SNico        /**
239*45a874f4SNico         *  We are still a reader, the redirection does not exist the user is not allowed to edit the page (public of other)
240*45a874f4SNico         */
241*45a874f4SNico        $actionReaderFirst = $conf->getValue('ActionReaderFirst');
242*45a874f4SNico        if ($actionReaderFirst == self::NOTHING) {
243*45a874f4SNico            throw new ExceptionNotFound();
244*45a874f4SNico        }
245*45a874f4SNico
246*45a874f4SNico        // We are reader and their is no redirection set, we apply the algorithm
247*45a874f4SNico        $readerAlgorithms = array();
248*45a874f4SNico        $readerAlgorithms[0] = $actionReaderFirst;
249*45a874f4SNico        $readerAlgorithms[1] = $conf->getValue('ActionReaderSecond');
250*45a874f4SNico        $readerAlgorithms[2] = $conf->getValue('ActionReaderThird');
251*45a874f4SNico
252*45a874f4SNico        while (
253*45a874f4SNico            ($algorithm = array_shift($readerAlgorithms)) != null
254*45a874f4SNico        ) {
255*45a874f4SNico
256*45a874f4SNico            switch ($algorithm) {
257*45a874f4SNico
258*45a874f4SNico                case self::NOTHING:
259*45a874f4SNico                    throw new ExceptionNotFound();
260*45a874f4SNico
261*45a874f4SNico                case self::GO_TO_BEST_END_PAGE_NAME:
262*45a874f4SNico
263*45a874f4SNico                    /**
264*45a874f4SNico                     * @var MarkupPath $bestEndPage
265*45a874f4SNico                     */
266*45a874f4SNico                    list($bestEndPage, $method) = RouterBestEndPage::process($requestedMarkupPath);
267*45a874f4SNico                    if ($bestEndPage != null) {
268*45a874f4SNico                        try {
269*45a874f4SNico                            $notSamePage = $bestEndPage->getWikiId() !== $requestedMarkupPath->getWikiId();
270*45a874f4SNico                        } catch (ExceptionBadArgument $e) {
271*45a874f4SNico                            LogUtility::error("The path should be wiki markup path", LogUtility::SUPPORT_CANONICAL, $e);
272*45a874f4SNico                            $notSamePage = false;
273*45a874f4SNico                        }
274*45a874f4SNico                        if ($notSamePage) {
275*45a874f4SNico                            $redirectionBuilder = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_BEST_END_PAGE_NAME)
276*45a874f4SNico                                ->setTargetMarkupPath($bestEndPage);
277*45a874f4SNico                            switch ($method) {
278*45a874f4SNico                                case RouterRedirection::REDIRECT_PERMANENT_METHOD:
279*45a874f4SNico                                    return $redirectionBuilder
280*45a874f4SNico                                        ->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)
281*45a874f4SNico                                        ->build();
282*45a874f4SNico                                case RouterRedirection::REDIRECT_NOTFOUND_METHOD:
283*45a874f4SNico                                    return $redirectionBuilder
284*45a874f4SNico                                        ->setType(RouterRedirection::REDIRECT_NOTFOUND_METHOD)
285*45a874f4SNico                                        ->build();
286*45a874f4SNico                                default:
287*45a874f4SNico                                    LogUtility::error("This redirection method ($method) was not expected for the redirection algorithm ($algorithm)");
288*45a874f4SNico                            }
289*45a874f4SNico                        }
290*45a874f4SNico
291*45a874f4SNico                    }
292*45a874f4SNico                    break;
293*45a874f4SNico
294*45a874f4SNico                case self::GO_TO_NS_START_PAGE:
295*45a874f4SNico
296*45a874f4SNico                    $redirectBuilder = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_START_PAGE)
297*45a874f4SNico                        ->setType(RouterRedirection::REDIRECT_NOTFOUND_METHOD);
298*45a874f4SNico
299*45a874f4SNico                    // Start page with the conf['start'] parameter
300*45a874f4SNico                    $startPage = getNS($identifier) . ':' . $conf['start'];
301*45a874f4SNico                    $startPath = MarkupPath::createMarkupFromId($startPage);
302*45a874f4SNico                    if (FileSystems::exists($startPath)) {
303*45a874f4SNico                        return $redirectBuilder->setTargetMarkupPath($startPath)->build();
304*45a874f4SNico                    }
305*45a874f4SNico
306*45a874f4SNico                    // Start page with the same name than the namespace
307*45a874f4SNico                    $startPage = getNS($identifier) . ':' . curNS($identifier);
308*45a874f4SNico                    $startPath = MarkupPath::createMarkupFromId($startPage);
309*45a874f4SNico                    if (FileSystems::exists($startPath)) {
310*45a874f4SNico                        return $redirectBuilder->setTargetMarkupPath($startPath)->build();
311*45a874f4SNico                    }
312*45a874f4SNico
313*45a874f4SNico                    break;
314*45a874f4SNico
315*45a874f4SNico                case self::GO_TO_BEST_PAGE_NAME:
316*45a874f4SNico
317*45a874f4SNico                    $bestPageId = null;
318*45a874f4SNico
319*45a874f4SNico                    $bestPage = $this->getBestPage($identifier);
320*45a874f4SNico                    $bestPageId = $bestPage['id'];
321*45a874f4SNico                    $scorePageName = $bestPage['score'];
322*45a874f4SNico
323*45a874f4SNico                    // Get Score from a Namespace
324*45a874f4SNico                    $bestNamespace = $this->scoreBestNamespace($identifier);
325*45a874f4SNico                    $bestNamespaceId = $bestNamespace['namespace'];
326*45a874f4SNico                    $namespaceScore = $bestNamespace['score'];
327*45a874f4SNico
328*45a874f4SNico                    // Compare the two score
329*45a874f4SNico                    if ($scorePageName > 0 or $namespaceScore > 0) {
330*45a874f4SNico                        $redirectionBuilder = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_BEST_PAGE_NAME)
331*45a874f4SNico                            ->setType(RouterRedirection::REDIRECT_NOTFOUND_METHOD);
332*45a874f4SNico                        if ($scorePageName > $namespaceScore) {
333*45a874f4SNico                            return $redirectionBuilder
334*45a874f4SNico                                ->setTargetMarkupPath(MarkupPath::createMarkupFromId($bestPageId))
335*45a874f4SNico                                ->build();
336*45a874f4SNico                        }
337*45a874f4SNico                        return $redirectionBuilder
338*45a874f4SNico                            ->setTargetMarkupPath(MarkupPath::createMarkupFromId($bestNamespaceId))
339*45a874f4SNico                            ->build();
340*45a874f4SNico                    }
341*45a874f4SNico                    break;
342*45a874f4SNico
343*45a874f4SNico                case self::GO_TO_BEST_NAMESPACE:
344*45a874f4SNico
345*45a874f4SNico                    $scoreNamespace = $this->scoreBestNamespace($identifier);
346*45a874f4SNico                    $bestNamespaceId = $scoreNamespace['namespace'];
347*45a874f4SNico                    $score = $scoreNamespace['score'];
348*45a874f4SNico
349*45a874f4SNico                    if ($score > 0) {
350*45a874f4SNico                        return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_BEST_NAMESPACE)
351*45a874f4SNico                            ->setType(RouterRedirection::REDIRECT_NOTFOUND_METHOD)
352*45a874f4SNico                            ->setTargetMarkupPath(MarkupPath::createMarkupFromId($bestNamespaceId))
353*45a874f4SNico                            ->build();
354*45a874f4SNico                    }
355*45a874f4SNico                    break;
356*45a874f4SNico
357*45a874f4SNico                case self::GO_TO_SEARCH_ENGINE:
358*45a874f4SNico
359*45a874f4SNico                    return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_SEARCH_ENGINE)
360*45a874f4SNico                        ->setType(RouterRedirection::REDIRECT_NOTFOUND_METHOD)
361*45a874f4SNico                        ->build();
362*45a874f4SNico
363*45a874f4SNico            }
364*45a874f4SNico
365*45a874f4SNico        }
366*45a874f4SNico
367*45a874f4SNico        throw new ExceptionNotFound();
368*45a874f4SNico
369*45a874f4SNico    }
370*45a874f4SNico
371*45a874f4SNico
372*45a874f4SNico    /**
373*45a874f4SNico     * @return string|null
374*45a874f4SNico     *
375*45a874f4SNico     * Return the original id from the request
376*45a874f4SNico     * ie `howto:how-to-get-started-with-combostrap-m3i8vga8`
377*45a874f4SNico     * if `/howto/how-to-get-started-with-combostrap-m3i8vga8`
378*45a874f4SNico     *
379*45a874f4SNico     * Unfortunately, DOKUWIKI_STARTED is not the first event
380*45a874f4SNico     * The id may have been changed by
381*45a874f4SNico     * {@link action_plugin_combo_lang::load_lang()}
382*45a874f4SNico     * function, that's why we have this function
383*45a874f4SNico     * to get the original requested id
384*45a874f4SNico     */
385*45a874f4SNico    static function getOriginalIdFromRequest(): ?string
386*45a874f4SNico    {
387*45a874f4SNico        $originalId = $_GET["id"] ?? null;
388*45a874f4SNico        if ($originalId === null) {
389*45a874f4SNico            return null;
390*45a874f4SNico        }
391*45a874f4SNico        // We may get a `/` as first character
392*45a874f4SNico        // because we return an id, we need to delete it
393*45a874f4SNico        if (substr($originalId, 0, 1) === "/") {
394*45a874f4SNico            $originalId = substr($originalId, 1);
395*45a874f4SNico        }
396*45a874f4SNico        // transform / to :
397*45a874f4SNico        return str_replace("/", WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $originalId);
398*45a874f4SNico    }
399*45a874f4SNico
400*45a874f4SNico    /**
401*45a874f4SNico     * Return a redirection declared in the redirection table or throw if not found
402*45a874f4SNico     * @throws ExceptionNotFound
403*45a874f4SNico     */
404*45a874f4SNico    private function getRedirectionFromPageRules(): RouterRedirection
405*45a874f4SNico    {
406*45a874f4SNico        global $ID;
407*45a874f4SNico
408*45a874f4SNico        $calculatedTarget = null;
409*45a874f4SNico        $ruleMatcher = null; // Used in a warning message if the target page does not exist
410*45a874f4SNico        // Known redirection in the table
411*45a874f4SNico        // Get the page from redirection data
412*45a874f4SNico        $rules = $this->pageRules->getRules();
413*45a874f4SNico        foreach ($rules as $rule) {
414*45a874f4SNico
415*45a874f4SNico            $ruleMatcher = strtolower($rule[PageRules::MATCHER_NAME]);
416*45a874f4SNico            $ruleTarget = $rule[PageRules::TARGET_NAME];
417*45a874f4SNico
418*45a874f4SNico            // Glob to Rexgexp
419*45a874f4SNico            $regexpPattern = '/' . str_replace("*", "(.*)", $ruleMatcher) . '/i';
420*45a874f4SNico
421*45a874f4SNico            // Match ?
422*45a874f4SNico            // https://www.php.net/manual/en/function.preg-match.php
423*45a874f4SNico            $pregMatchResult = @preg_match($regexpPattern, $ID, $matches);
424*45a874f4SNico            if ($pregMatchResult === false) {
425*45a874f4SNico                // The `if` to take into account this problem
426*45a874f4SNico                // PHP Warning:  preg_match(): Unknown modifier 'd' in /opt/www/datacadamia.com/lib/plugins/combo/action/router.php on line 972
427*45a874f4SNico                LogUtility::log2file("processing Page Rules An error occurred with the pattern ($regexpPattern)", LogUtility::LVL_MSG_WARNING);
428*45a874f4SNico                throw new ExceptionNotFound();
429*45a874f4SNico            }
430*45a874f4SNico            if ($pregMatchResult) {
431*45a874f4SNico                $calculatedTarget = $ruleTarget;
432*45a874f4SNico                foreach ($matches as $key => $match) {
433*45a874f4SNico                    if ($key == 0) {
434*45a874f4SNico                        continue;
435*45a874f4SNico                    } else {
436*45a874f4SNico                        $calculatedTarget = str_replace('$' . $key, $match, $calculatedTarget);
437*45a874f4SNico                    }
438*45a874f4SNico                }
439*45a874f4SNico                break;
440*45a874f4SNico            }
441*45a874f4SNico        }
442*45a874f4SNico
443*45a874f4SNico        if ($calculatedTarget == null) {
444*45a874f4SNico            throw new ExceptionNotFound();
445*45a874f4SNico        }
446*45a874f4SNico
447*45a874f4SNico        // If this is an external redirect (other domain)
448*45a874f4SNico        try {
449*45a874f4SNico            $url = Url::createFromString($calculatedTarget);
450*45a874f4SNico            return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_PAGE_RULES)
451*45a874f4SNico                ->setTargetUrl($url)
452*45a874f4SNico                ->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)
453*45a874f4SNico                ->build();
454*45a874f4SNico        } catch (ExceptionBadSyntax|ExceptionBadArgument $e) {
455*45a874f4SNico            // not an URL
456*45a874f4SNico        }
457*45a874f4SNico
458*45a874f4SNico
459*45a874f4SNico        // If the page exist
460*45a874f4SNico        // This is DokuWiki Id and should always be lowercase
461*45a874f4SNico        // The page rule may have change that
462*45a874f4SNico        $calculatedTarget = strtolower($calculatedTarget);
463*45a874f4SNico        $markupPath = MarkupPath::createMarkupFromId($calculatedTarget);
464*45a874f4SNico        if (FileSystems::exists($markupPath)) {
465*45a874f4SNico
466*45a874f4SNico            return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_PAGE_RULES)
467*45a874f4SNico                ->setTargetMarkupPath($markupPath)
468*45a874f4SNico                ->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)
469*45a874f4SNico                ->build();
470*45a874f4SNico
471*45a874f4SNico        }
472*45a874f4SNico
473*45a874f4SNico        LogUtility::error("The calculated target page ($calculatedTarget) (for the non-existing page `$ID` with the matcher `$ruleMatcher`) does not exist");
474*45a874f4SNico        throw new ExceptionNotFound();
475*45a874f4SNico
476*45a874f4SNico    }
477*45a874f4SNico
478*45a874f4SNico
479*45a874f4SNico    /**
480*45a874f4SNico     * @param $id
481*45a874f4SNico     * @return array
482*45a874f4SNico     */
483*45a874f4SNico    private
484*45a874f4SNico    function getBestPage($id): array
485*45a874f4SNico    {
486*45a874f4SNico
487*45a874f4SNico        // The return parameters
488*45a874f4SNico        $bestPageId = null;
489*45a874f4SNico        $scorePageName = null;
490*45a874f4SNico
491*45a874f4SNico        // Get Score from a page
492*45a874f4SNico        $pageName = noNS($id);
493*45a874f4SNico        $pagesWithSameName = ft_pageLookup($pageName);
494*45a874f4SNico        if (count($pagesWithSameName) > 0) {
495*45a874f4SNico
496*45a874f4SNico            // Search same namespace in the page found than in the Id page asked.
497*45a874f4SNico            $bestNbWordFound = 0;
498*45a874f4SNico
499*45a874f4SNico
500*45a874f4SNico            $wordsInPageSourceId = explode(':', $id);
501*45a874f4SNico            foreach ($pagesWithSameName as $targetPageId => $title) {
502*45a874f4SNico
503*45a874f4SNico                // Nb of word found in the target page id
504*45a874f4SNico                // that are in the source page id
505*45a874f4SNico                $nbWordFound = 0;
506*45a874f4SNico                foreach ($wordsInPageSourceId as $word) {
507*45a874f4SNico                    $nbWordFound = $nbWordFound + substr_count($targetPageId, $word);
508*45a874f4SNico                }
509*45a874f4SNico
510*45a874f4SNico                if ($bestPageId == null) {
511*45a874f4SNico
512*45a874f4SNico                    $bestNbWordFound = $nbWordFound;
513*45a874f4SNico                    $bestPageId = $targetPageId;
514*45a874f4SNico
515*45a874f4SNico                } else {
516*45a874f4SNico
517*45a874f4SNico                    if ($nbWordFound >= $bestNbWordFound && strlen($bestPageId) > strlen($targetPageId)) {
518*45a874f4SNico
519*45a874f4SNico                        $bestNbWordFound = $nbWordFound;
520*45a874f4SNico                        $bestPageId = $targetPageId;
521*45a874f4SNico
522*45a874f4SNico                    }
523*45a874f4SNico
524*45a874f4SNico                }
525*45a874f4SNico
526*45a874f4SNico            }
527*45a874f4SNico            $config = ExecutionContext::getActualOrCreateFromEnv()->getConfig();
528*45a874f4SNico            $weightFactorForSamePageName = $config->getValue('WeightFactorForSamePageName');
529*45a874f4SNico            $weightFactorForSameNamespace = $config->getValue('WeightFactorForSameNamespace');
530*45a874f4SNico            $scorePageName = $weightFactorForSamePageName + ($bestNbWordFound - 1) * $weightFactorForSameNamespace;
531*45a874f4SNico            return array(
532*45a874f4SNico                'id' => $bestPageId,
533*45a874f4SNico                'score' => $scorePageName);
534*45a874f4SNico        }
535*45a874f4SNico        return array(
536*45a874f4SNico            'id' => $bestPageId,
537*45a874f4SNico            'score' => $scorePageName
538*45a874f4SNico        );
539*45a874f4SNico
540*45a874f4SNico    }
541*45a874f4SNico
542*45a874f4SNico    /**
543*45a874f4SNico     * getBestNamespace
544*45a874f4SNico     * Return a list with 'BestNamespaceId Score'
545*45a874f4SNico     * @param $id
546*45a874f4SNico     * @return array
547*45a874f4SNico     */
548*45a874f4SNico    private
549*45a874f4SNico    function scoreBestNamespace($id): array
550*45a874f4SNico    {
551*45a874f4SNico
552*45a874f4SNico        $nameSpaces = array();
553*45a874f4SNico        $pathNames = array();
554*45a874f4SNico
555*45a874f4SNico        // Parameters
556*45a874f4SNico        $requestedPath = MarkupPath::createMarkupFromId($id);
557*45a874f4SNico        try {
558*45a874f4SNico            $pageNameSpace = $requestedPath->getParent();
559*45a874f4SNico            $pathNames = array_slice($pageNameSpace->getNames(), 0, -1);
560*45a874f4SNico            if (FileSystems::exists($pageNameSpace)) {
561*45a874f4SNico                $nameSpaces = array($pageNameSpace->toAbsoluteId());
562*45a874f4SNico            } else {
563*45a874f4SNico                global $conf;
564*45a874f4SNico                $nameSpaces = ft_pageLookup($conf['start']);
565*45a874f4SNico            }
566*45a874f4SNico        } catch (ExceptionNotFound $e) {
567*45a874f4SNico            // no parent, root
568*45a874f4SNico        }
569*45a874f4SNico
570*45a874f4SNico        // Parameters and search the best namespace
571*45a874f4SNico        $bestNbWordFound = 0;
572*45a874f4SNico        $bestNamespaceId = null;
573*45a874f4SNico        foreach ($nameSpaces as $nameSpace) {
574*45a874f4SNico
575*45a874f4SNico            $nbWordFound = 0;
576*45a874f4SNico            foreach ($pathNames as $pathName) {
577*45a874f4SNico                if (strlen($pathName) > 2) {
578*45a874f4SNico                    $nbWordFound = $nbWordFound + substr_count($nameSpace, $pathName);
579*45a874f4SNico                }
580*45a874f4SNico            }
581*45a874f4SNico            if ($nbWordFound > $bestNbWordFound) {
582*45a874f4SNico                // Take only the smallest namespace
583*45a874f4SNico                if ($bestNbWordFound == null || strlen($nameSpace) < strlen($bestNamespaceId)) {
584*45a874f4SNico                    $bestNbWordFound = $nbWordFound;
585*45a874f4SNico                    $bestNamespaceId = $nameSpace;
586*45a874f4SNico                }
587*45a874f4SNico            }
588*45a874f4SNico        }
589*45a874f4SNico        $config = ExecutionContext::getActualOrCreateFromEnv()->getConfig();
590*45a874f4SNico        $startPageFactor = $config->getValue('WeightFactorForStartPage');
591*45a874f4SNico        $nameSpaceFactor = $config->getValue('WeightFactorForSameNamespace');
592*45a874f4SNico        if ($bestNbWordFound > 0) {
593*45a874f4SNico            $bestNamespaceScore = $bestNbWordFound * $nameSpaceFactor + $startPageFactor;
594*45a874f4SNico        } else {
595*45a874f4SNico            $bestNamespaceScore = 0;
596*45a874f4SNico        }
597*45a874f4SNico
598*45a874f4SNico
599*45a874f4SNico        return array(
600*45a874f4SNico            'namespace' => $bestNamespaceId,
601*45a874f4SNico            'score' => $bestNamespaceScore
602*45a874f4SNico        );
603*45a874f4SNico
604*45a874f4SNico    }
605*45a874f4SNico
606*45a874f4SNico
607*45a874f4SNico}
608