xref: /plugin/combo/ComboStrap/Router.php (revision 313de40a7a81adb8606d463d69a8d40c7499c8f8)
145a874f4SNico<?php
245a874f4SNico
345a874f4SNiconamespace ComboStrap;
445a874f4SNico
545a874f4SNicouse ComboStrap\Meta\Field\AliasType;
645a874f4SNicouse ComboStrap\Web\Url;
745a874f4SNico
845a874f4SNicoclass Router
945a874f4SNico{
1045a874f4SNico
1145a874f4SNico
1245a874f4SNico    public const GO_TO_SEARCH_ENGINE = 'GoToSearchEngine';
1345a874f4SNico    public const GO_TO_NS_START_PAGE = 'GoToNsStartPage';
1445a874f4SNico    public const GO_TO_EDIT_MODE = 'GoToEditMode';
1545a874f4SNico    public const GO_TO_BEST_END_PAGE_NAME = 'GoToBestEndPageName';
1645a874f4SNico    public const GO_TO_BEST_NAMESPACE = 'GoToBestNamespace';
1745a874f4SNico    public const NOTHING = 'Nothing';
1845a874f4SNico    public const GO_TO_BEST_PAGE_NAME = 'GoToBestPageName';
1945a874f4SNico    private PageRules $pageRules;
2045a874f4SNico
2145a874f4SNico    /**
2245a874f4SNico     * @throws ExceptionSqliteNotAvailable
2345a874f4SNico     * @throws ExceptionNotFound - no redirection found
2445a874f4SNico     */
2545a874f4SNico    public function getRedirection(): RouterRedirection
2645a874f4SNico    {
2745a874f4SNico
2845a874f4SNico        /**
2945a874f4SNico         * Without SQLite, this module does not work further
3045a874f4SNico         * It throws
3145a874f4SNico         */
3245a874f4SNico        Sqlite::createOrGetSqlite();
3345a874f4SNico
3445a874f4SNico        /**
3545a874f4SNico         * Initiate Page Rules
3645a874f4SNico         */
3745a874f4SNico        $this->pageRules = new PageRules();
3845a874f4SNico
3945a874f4SNico
4045a874f4SNico        /**
4145a874f4SNico         * Unfortunately, DOKUWIKI_STARTED is not the first event
4245a874f4SNico         * The id may have been changed by
4345a874f4SNico         * {@link action_plugin_combo_lang::load_lang()}
4445a874f4SNico         * function, that's why we check against the {@link $_REQUEST}
4545a874f4SNico         * and not the global ID
4645a874f4SNico         */
4745a874f4SNico        $originalId = self::getOriginalIdFromRequest();
4845a874f4SNico
4945a874f4SNico        /**
5045a874f4SNico         * Page is an existing id
5145a874f4SNico         * in the database ?
5245a874f4SNico         */
5345a874f4SNico        global $ID;
5445a874f4SNico        $requestedMarkupPath = MarkupPath::createMarkupFromId($ID);
5545a874f4SNico        if (FileSystems::exists($requestedMarkupPath)) {
5645a874f4SNico
5745a874f4SNico            /**
5845a874f4SNico             * If this is not the root home page
5945a874f4SNico             * and if the canonical id is the not the same (the id has changed)
6045a874f4SNico             * and if this is not a historical page (revision)
6145a874f4SNico             * redirect
6245a874f4SNico             */
6345a874f4SNico            if (
6445a874f4SNico                $originalId !== $requestedMarkupPath->getUrlId() // The id may have been changed
6545a874f4SNico                && $ID != Site::getIndexPageName()
6645a874f4SNico                && !isset($_REQUEST["rev"])
6745a874f4SNico            ) {
6845a874f4SNico                /**
6945a874f4SNico                 * TODO: When saving for the first time, the page is not stored in the database
7045a874f4SNico                 *   but that's not the case actually
7145a874f4SNico                 */
7245a874f4SNico                $databasePageRow = $requestedMarkupPath->getDatabasePage();
7345a874f4SNico                if ($databasePageRow->exists()) {
7445a874f4SNico                    /**
7545a874f4SNico                     * A move may leave the database in a bad state,
7645a874f4SNico                     * unfortunately (ie page is not in index, unable to update, ...)
7745a874f4SNico                     * We test therefore if the database page id exists
7845a874f4SNico                     */
7945a874f4SNico                    $targetPageId = $databasePageRow->getFromRow("id");
8045a874f4SNico                    $targetPath = MarkupPath::createMarkupFromId($targetPageId);
8145a874f4SNico                    if (FileSystems::exists($targetPath)) {
8245a874f4SNico                        return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_PERMALINK_EXTENDED)
8345a874f4SNico                            ->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)
8445a874f4SNico                            ->setTargetMarkupPath($targetPath)
8545a874f4SNico                            ->build();
8645a874f4SNico                    }
8745a874f4SNico
8845a874f4SNico                }
8945a874f4SNico            }
9045a874f4SNico        }
9145a874f4SNico
9245a874f4SNico        $identifier = $ID;
9345a874f4SNico
9445a874f4SNico        /**
9545a874f4SNico         * Page Id in the url
96ecf8d738SNico         * Note that if the ID is a permalink, global $ID has already the real id
97ecf8d738SNico         * Why? because unfortunately, DOKUWIKI_STARTED is not the first event
98ecf8d738SNico         * {@link action_plugin_combo_lang::load_lang()} may have already
99ecf8d738SNico         * transformed a permalink into a real dokuwiki id
100ecf8d738SNico         *
101ecf8d738SNico         * We let it here because we don't know for sure that it will stay this way
102ecf8d738SNico         * What fucked up is fucked up
10345a874f4SNico         */
10445a874f4SNico        $shortPageId = PageUrlPath::getShortEncodedPageIdFromUrlId($requestedMarkupPath->getPathObject()->getLastNameWithoutExtension());
10545a874f4SNico        if ($shortPageId != null) {
10645a874f4SNico            $pageId = PageUrlPath::decodePageId($shortPageId);
10745a874f4SNico        } else {
10845a874f4SNico            /**
10945a874f4SNico             * Permalink with id
11045a874f4SNico             */
11145a874f4SNico            $pageId = PageUrlPath::decodePageId($identifier);
11245a874f4SNico        }
11345a874f4SNico        if ($pageId !== null) {
11445a874f4SNico
11545a874f4SNico            if ($requestedMarkupPath->getParent() === null) {
11645a874f4SNico                $page = DatabasePageRow::createFromPageId($pageId)->getMarkupPath();
11745a874f4SNico                if ($page !== null && $page->exists()) {
11845a874f4SNico                    return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_PERMALINK)
11945a874f4SNico                        ->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)
12045a874f4SNico                        ->setTargetMarkupPath($page)
12145a874f4SNico                        ->build();
12245a874f4SNico                }
12345a874f4SNico            }
12445a874f4SNico
12545a874f4SNico            /**
12645a874f4SNico             * Page Id Abbr ?
12745a874f4SNico             * {@link PageUrlType::CONF_CANONICAL_URL_TYPE}
12845a874f4SNico             */
12945a874f4SNico            $page = DatabasePageRow::createFromPageIdAbbr($pageId)->getMarkupPath();
13045a874f4SNico            if ($page === null) {
13145a874f4SNico                // or the length of the abbr has changed
13245a874f4SNico                $canonicalDatabasePage = new DatabasePageRow();
13345a874f4SNico                try {
13445a874f4SNico                    $row = $canonicalDatabasePage->getDatabaseRowFromAttribute("substr(" . PageId::PROPERTY_NAME . ", 1, " . strlen($pageId) . ")", $pageId);
13545a874f4SNico                    $canonicalDatabasePage->setRow($row);
13645a874f4SNico                    $page = $canonicalDatabasePage->getMarkupPath();
13745a874f4SNico                } catch (ExceptionNotFound $e) {
13845a874f4SNico                    // nothing to do
13945a874f4SNico                }
14045a874f4SNico            }
14145a874f4SNico            if ($page !== null && $page->exists()) {
14245a874f4SNico                /**
14345a874f4SNico                 * If the url canonical id has changed, we show it
14445a874f4SNico                 * to the writer by performing a permanent redirect
14545a874f4SNico                 */
14645a874f4SNico                if ($identifier != $page->getUrlId()) {
14745a874f4SNico                    // Google asks for a redirect
14845a874f4SNico                    // https://developers.google.com/search/docs/advanced/crawling/301-redirects
14945a874f4SNico                    // People access your site through several different URLs.
15045a874f4SNico                    // If, for example, your home page can be reached in multiple ways
15145a874f4SNico                    // (for instance, http://example.com/home, http://home.example.com, or http://www.example.com),
15245a874f4SNico                    // it's a good idea to pick one of those URLs as your preferred (canonical) destination,
15345a874f4SNico                    // and use redirects to send traffic from the other URLs to your preferred URL.
15445a874f4SNico                    return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_PERMALINK_EXTENDED)
15545a874f4SNico                        ->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)
15645a874f4SNico                        ->setTargetMarkupPath($page)
15745a874f4SNico                        ->build();
15845a874f4SNico
15945a874f4SNico                }
16045a874f4SNico
16145a874f4SNico                return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_PERMALINK_EXTENDED)
16245a874f4SNico                    ->setType(RouterRedirection::REDIRECT_TRANSPARENT_METHOD)
16345a874f4SNico                    ->setTargetMarkupPath($page)
16445a874f4SNico                    ->build();
16545a874f4SNico
16645a874f4SNico            }
16745a874f4SNico            // permanent url not yet in the database
16845a874f4SNico            // Other permanent such as permanent canonical ?
16945a874f4SNico            // We let the process go with the new identifier
17045a874f4SNico
17145a874f4SNico        }
17245a874f4SNico
17345a874f4SNico        /**
17445a874f4SNico         * Identifier is a Canonical ?
17545a874f4SNico         */
17645a874f4SNico        $canonicalDatabasePage = DatabasePageRow::createFromCanonical($identifier);
17745a874f4SNico        $canonicalPage = $canonicalDatabasePage->getMarkupPath();
17845a874f4SNico        if ($canonicalPage !== null && $canonicalPage->exists()) {
17945a874f4SNico            $builder = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_CANONICAL)
18045a874f4SNico                ->setTargetMarkupPath($canonicalPage);
18145a874f4SNico            /**
18245a874f4SNico             * Does the canonical url is canonical name based
18345a874f4SNico             * ie {@link  PageUrlType::CONF_VALUE_CANONICAL_PATH}
18445a874f4SNico             */
18545a874f4SNico            if ($canonicalPage->getUrlId() === $identifier) {
18645a874f4SNico                $builder->setType(RouterRedirection::REDIRECT_TRANSPARENT_METHOD);
18745a874f4SNico            } else {
18845a874f4SNico                $builder->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD);
18945a874f4SNico            }
19045a874f4SNico            return $builder->build();
19145a874f4SNico
19245a874f4SNico        }
19345a874f4SNico
19445a874f4SNico        /**
19545a874f4SNico         * Identifier is an alias
19645a874f4SNico         */
19745a874f4SNico        $aliasRequestedPage = DatabasePageRow::createFromAlias($identifier)->getMarkupPath();
19845a874f4SNico        if (
19945a874f4SNico            $aliasRequestedPage !== null
20045a874f4SNico            && $aliasRequestedPage->exists()
20145a874f4SNico            // The build alias is the file system metadata alias
20245a874f4SNico            // it may be null if the replication in the database was not successful
20345a874f4SNico            && $aliasRequestedPage->getBuildAlias() !== null
20445a874f4SNico        ) {
20545a874f4SNico            $buildAlias = $aliasRequestedPage->getBuildAlias();
20645a874f4SNico            $builder = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_ALIAS)
20745a874f4SNico                ->setTargetMarkupPath($aliasRequestedPage);
20845a874f4SNico            switch ($buildAlias->getType()) {
20945a874f4SNico                case AliasType::REDIRECT:
21045a874f4SNico                    return $builder->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)->build();
21145a874f4SNico                case AliasType::SYNONYM:
21245a874f4SNico                    return $builder->setType(RouterRedirection::REDIRECT_TRANSPARENT_METHOD)->build();
21345a874f4SNico                default:
21445a874f4SNico                    LogUtility::msg("The alias type ({$buildAlias->getType()}) is unknown. A permanent redirect was performed for the alias $identifier");
21545a874f4SNico                    return $builder->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)->build();
21645a874f4SNico            }
21745a874f4SNico        }
21845a874f4SNico
21945a874f4SNico        /**
22045a874f4SNico         * Do we have a page rules
22145a874f4SNico         * If there is a redirection defined in the page rules
22245a874f4SNico         */
22345a874f4SNico        try {
22445a874f4SNico            return $this->getRedirectionFromPageRules();
22545a874f4SNico        } catch (ExceptionNotFound $e) {
22645a874f4SNico            // no pages rules redirection
22745a874f4SNico        }
22845a874f4SNico
22945a874f4SNico        /**
23045a874f4SNico         * No redirection found in the database by id
23145a874f4SNico         */
23245a874f4SNico
23345a874f4SNico        /**
23445a874f4SNico         * Edit mode
23545a874f4SNico         */
23645a874f4SNico        $conf = ExecutionContext::getActualOrCreateFromEnv()->getConfig();
23745a874f4SNico        if (Identity::isWriter() && $conf->getBooleanValue(self::GO_TO_EDIT_MODE, true)) {
23845a874f4SNico
23945a874f4SNico            // Stop here
24045a874f4SNico            return RouterRedirectionBuilder::createFromOrigin(self::GO_TO_EDIT_MODE)
24145a874f4SNico                ->build();
24245a874f4SNico
24345a874f4SNico        }
24445a874f4SNico
24545a874f4SNico        /**
24645a874f4SNico         *  We are still a reader, the redirection does not exist the user is not allowed to edit the page (public of other)
24745a874f4SNico         */
24845a874f4SNico        $actionReaderFirst = $conf->getValue('ActionReaderFirst');
24945a874f4SNico        if ($actionReaderFirst == self::NOTHING) {
25045a874f4SNico            throw new ExceptionNotFound();
25145a874f4SNico        }
25245a874f4SNico
25345a874f4SNico        // We are reader and their is no redirection set, we apply the algorithm
25445a874f4SNico        $readerAlgorithms = array();
25545a874f4SNico        $readerAlgorithms[0] = $actionReaderFirst;
25645a874f4SNico        $readerAlgorithms[1] = $conf->getValue('ActionReaderSecond');
25745a874f4SNico        $readerAlgorithms[2] = $conf->getValue('ActionReaderThird');
25845a874f4SNico
25945a874f4SNico        while (
26045a874f4SNico            ($algorithm = array_shift($readerAlgorithms)) != null
26145a874f4SNico        ) {
26245a874f4SNico
26345a874f4SNico            switch ($algorithm) {
26445a874f4SNico
26545a874f4SNico                case self::NOTHING:
26645a874f4SNico                    throw new ExceptionNotFound();
26745a874f4SNico
26845a874f4SNico                case self::GO_TO_BEST_END_PAGE_NAME:
26945a874f4SNico
27045a874f4SNico                    /**
27145a874f4SNico                     * @var MarkupPath $bestEndPage
27245a874f4SNico                     */
27345a874f4SNico                    list($bestEndPage, $method) = RouterBestEndPage::process($requestedMarkupPath);
27445a874f4SNico                    if ($bestEndPage != null) {
27545a874f4SNico                        try {
27645a874f4SNico                            $notSamePage = $bestEndPage->getWikiId() !== $requestedMarkupPath->getWikiId();
27745a874f4SNico                        } catch (ExceptionBadArgument $e) {
27845a874f4SNico                            LogUtility::error("The path should be wiki markup path", LogUtility::SUPPORT_CANONICAL, $e);
27945a874f4SNico                            $notSamePage = false;
28045a874f4SNico                        }
28145a874f4SNico                        if ($notSamePage) {
28245a874f4SNico                            $redirectionBuilder = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_BEST_END_PAGE_NAME)
28345a874f4SNico                                ->setTargetMarkupPath($bestEndPage);
28445a874f4SNico                            switch ($method) {
28545a874f4SNico                                case RouterRedirection::REDIRECT_PERMANENT_METHOD:
28645a874f4SNico                                    return $redirectionBuilder
28745a874f4SNico                                        ->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)
28845a874f4SNico                                        ->build();
28945a874f4SNico                                case RouterRedirection::REDIRECT_NOTFOUND_METHOD:
29045a874f4SNico                                    return $redirectionBuilder
29145a874f4SNico                                        ->setType(RouterRedirection::REDIRECT_NOTFOUND_METHOD)
29245a874f4SNico                                        ->build();
29345a874f4SNico                                default:
29445a874f4SNico                                    LogUtility::error("This redirection method ($method) was not expected for the redirection algorithm ($algorithm)");
29545a874f4SNico                            }
29645a874f4SNico                        }
29745a874f4SNico
29845a874f4SNico                    }
29945a874f4SNico                    break;
30045a874f4SNico
30145a874f4SNico                case self::GO_TO_NS_START_PAGE:
30245a874f4SNico
30345a874f4SNico                    $redirectBuilder = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_START_PAGE)
30445a874f4SNico                        ->setType(RouterRedirection::REDIRECT_NOTFOUND_METHOD);
30545a874f4SNico
30645a874f4SNico                    // Start page with the conf['start'] parameter
30745a874f4SNico                    $startPage = getNS($identifier) . ':' . $conf['start'];
30845a874f4SNico                    $startPath = MarkupPath::createMarkupFromId($startPage);
30945a874f4SNico                    if (FileSystems::exists($startPath)) {
31045a874f4SNico                        return $redirectBuilder->setTargetMarkupPath($startPath)->build();
31145a874f4SNico                    }
31245a874f4SNico
31345a874f4SNico                    // Start page with the same name than the namespace
31445a874f4SNico                    $startPage = getNS($identifier) . ':' . curNS($identifier);
31545a874f4SNico                    $startPath = MarkupPath::createMarkupFromId($startPage);
31645a874f4SNico                    if (FileSystems::exists($startPath)) {
31745a874f4SNico                        return $redirectBuilder->setTargetMarkupPath($startPath)->build();
31845a874f4SNico                    }
31945a874f4SNico
32045a874f4SNico                    break;
32145a874f4SNico
32245a874f4SNico                case self::GO_TO_BEST_PAGE_NAME:
32345a874f4SNico
32445a874f4SNico                    $bestPageId = null;
32545a874f4SNico
32645a874f4SNico                    $bestPage = $this->getBestPage($identifier);
32745a874f4SNico                    $bestPageId = $bestPage['id'];
32845a874f4SNico                    $scorePageName = $bestPage['score'];
32945a874f4SNico
33045a874f4SNico                    // Get Score from a Namespace
33145a874f4SNico                    $bestNamespace = $this->scoreBestNamespace($identifier);
33245a874f4SNico                    $bestNamespaceId = $bestNamespace['namespace'];
33345a874f4SNico                    $namespaceScore = $bestNamespace['score'];
33445a874f4SNico
33545a874f4SNico                    // Compare the two score
33645a874f4SNico                    if ($scorePageName > 0 or $namespaceScore > 0) {
33745a874f4SNico                        $redirectionBuilder = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_BEST_PAGE_NAME)
33845a874f4SNico                            ->setType(RouterRedirection::REDIRECT_NOTFOUND_METHOD);
33945a874f4SNico                        if ($scorePageName > $namespaceScore) {
34045a874f4SNico                            return $redirectionBuilder
34145a874f4SNico                                ->setTargetMarkupPath(MarkupPath::createMarkupFromId($bestPageId))
34245a874f4SNico                                ->build();
34345a874f4SNico                        }
34445a874f4SNico                        return $redirectionBuilder
34545a874f4SNico                            ->setTargetMarkupPath(MarkupPath::createMarkupFromId($bestNamespaceId))
34645a874f4SNico                            ->build();
34745a874f4SNico                    }
34845a874f4SNico                    break;
34945a874f4SNico
35045a874f4SNico                case self::GO_TO_BEST_NAMESPACE:
35145a874f4SNico
35245a874f4SNico                    $scoreNamespace = $this->scoreBestNamespace($identifier);
35345a874f4SNico                    $bestNamespaceId = $scoreNamespace['namespace'];
35445a874f4SNico                    $score = $scoreNamespace['score'];
35545a874f4SNico
35645a874f4SNico                    if ($score > 0) {
35745a874f4SNico                        return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_BEST_NAMESPACE)
35845a874f4SNico                            ->setType(RouterRedirection::REDIRECT_NOTFOUND_METHOD)
35945a874f4SNico                            ->setTargetMarkupPath(MarkupPath::createMarkupFromId($bestNamespaceId))
36045a874f4SNico                            ->build();
36145a874f4SNico                    }
36245a874f4SNico                    break;
36345a874f4SNico
36445a874f4SNico                case self::GO_TO_SEARCH_ENGINE:
36545a874f4SNico
36645a874f4SNico                    return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_SEARCH_ENGINE)
36745a874f4SNico                        ->setType(RouterRedirection::REDIRECT_NOTFOUND_METHOD)
36845a874f4SNico                        ->build();
36945a874f4SNico
37045a874f4SNico            }
37145a874f4SNico
37245a874f4SNico        }
37345a874f4SNico
37445a874f4SNico        throw new ExceptionNotFound();
37545a874f4SNico
37645a874f4SNico    }
37745a874f4SNico
37845a874f4SNico
37945a874f4SNico    /**
38045a874f4SNico     * @return string|null
38145a874f4SNico     *
38245a874f4SNico     * Return the original id from the request
38345a874f4SNico     * ie `howto:how-to-get-started-with-combostrap-m3i8vga8`
38445a874f4SNico     * if `/howto/how-to-get-started-with-combostrap-m3i8vga8`
38545a874f4SNico     *
38645a874f4SNico     * Unfortunately, DOKUWIKI_STARTED is not the first event
38745a874f4SNico     * The id may have been changed by
38845a874f4SNico     * {@link action_plugin_combo_lang::load_lang()}
38945a874f4SNico     * function, that's why we have this function
39045a874f4SNico     * to get the original requested id
39145a874f4SNico     */
39245a874f4SNico    static function getOriginalIdFromRequest(): ?string
39345a874f4SNico    {
39445a874f4SNico        $originalId = $_GET["id"] ?? null;
39545a874f4SNico        if ($originalId === null) {
39645a874f4SNico            return null;
39745a874f4SNico        }
39845a874f4SNico        // We may get a `/` as first character
39945a874f4SNico        // because we return an id, we need to delete it
40045a874f4SNico        if (substr($originalId, 0, 1) === "/") {
40145a874f4SNico            $originalId = substr($originalId, 1);
40245a874f4SNico        }
40345a874f4SNico        // transform / to :
40445a874f4SNico        return str_replace("/", WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $originalId);
40545a874f4SNico    }
40645a874f4SNico
40745a874f4SNico    /**
40845a874f4SNico     * Return a redirection declared in the redirection table or throw if not found
40945a874f4SNico     * @throws ExceptionNotFound
41045a874f4SNico     */
41145a874f4SNico    private function getRedirectionFromPageRules(): RouterRedirection
41245a874f4SNico    {
41345a874f4SNico        global $ID;
41445a874f4SNico
41545a874f4SNico        $calculatedTarget = null;
41645a874f4SNico        $ruleMatcher = null; // Used in a warning message if the target page does not exist
41745a874f4SNico        // Known redirection in the table
41845a874f4SNico        // Get the page from redirection data
41945a874f4SNico        $rules = $this->pageRules->getRules();
42045a874f4SNico        foreach ($rules as $rule) {
42145a874f4SNico
42245a874f4SNico            $ruleMatcher = strtolower($rule[PageRules::MATCHER_NAME]);
42345a874f4SNico            $ruleTarget = $rule[PageRules::TARGET_NAME];
42445a874f4SNico
42545a874f4SNico            // Glob to Rexgexp
42645a874f4SNico            $regexpPattern = '/' . str_replace("*", "(.*)", $ruleMatcher) . '/i';
42745a874f4SNico
42845a874f4SNico            // Match ?
42945a874f4SNico            // https://www.php.net/manual/en/function.preg-match.php
43045a874f4SNico            $pregMatchResult = @preg_match($regexpPattern, $ID, $matches);
43145a874f4SNico            if ($pregMatchResult === false) {
43245a874f4SNico                // The `if` to take into account this problem
43345a874f4SNico                // PHP Warning:  preg_match(): Unknown modifier 'd' in /opt/www/datacadamia.com/lib/plugins/combo/action/router.php on line 972
43445a874f4SNico                LogUtility::log2file("processing Page Rules An error occurred with the pattern ($regexpPattern)", LogUtility::LVL_MSG_WARNING);
43545a874f4SNico                throw new ExceptionNotFound();
43645a874f4SNico            }
43745a874f4SNico            if ($pregMatchResult) {
43845a874f4SNico                $calculatedTarget = $ruleTarget;
43945a874f4SNico                foreach ($matches as $key => $match) {
44045a874f4SNico                    if ($key == 0) {
44145a874f4SNico                        continue;
44245a874f4SNico                    } else {
44345a874f4SNico                        $calculatedTarget = str_replace('$' . $key, $match, $calculatedTarget);
44445a874f4SNico                    }
44545a874f4SNico                }
44645a874f4SNico                break;
44745a874f4SNico            }
44845a874f4SNico        }
44945a874f4SNico
45045a874f4SNico        if ($calculatedTarget == null) {
45145a874f4SNico            throw new ExceptionNotFound();
45245a874f4SNico        }
45345a874f4SNico
45445a874f4SNico        // If this is an external redirect (other domain)
45545a874f4SNico        try {
45645a874f4SNico            $url = Url::createFromString($calculatedTarget);
457*313de40aSNicolas GERARD            // The page id `my:page` is a valid url after parsing with the scheme `my`
458*313de40aSNicolas GERARD            if (strpos($url->getScheme(), "http") === 0) {
45945a874f4SNico                return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_PAGE_RULES)
46045a874f4SNico                    ->setTargetUrl($url)
46145a874f4SNico                    ->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)
46245a874f4SNico                    ->build();
463*313de40aSNicolas GERARD            }
46445a874f4SNico        } catch (ExceptionBadSyntax|ExceptionBadArgument $e) {
46545a874f4SNico            // not an URL
46645a874f4SNico        }
46745a874f4SNico
46845a874f4SNico
46945a874f4SNico        // If the page exist
47045a874f4SNico        // This is DokuWiki Id and should always be lowercase
47145a874f4SNico        // The page rule may have change that
47245a874f4SNico        $calculatedTarget = strtolower($calculatedTarget);
47345a874f4SNico        $markupPath = MarkupPath::createMarkupFromId($calculatedTarget);
47445a874f4SNico        if (FileSystems::exists($markupPath)) {
47545a874f4SNico
47645a874f4SNico            return RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_PAGE_RULES)
47745a874f4SNico                ->setTargetMarkupPath($markupPath)
47845a874f4SNico                ->setType(RouterRedirection::REDIRECT_PERMANENT_METHOD)
47945a874f4SNico                ->build();
48045a874f4SNico
48145a874f4SNico        }
48245a874f4SNico
48345a874f4SNico        LogUtility::error("The calculated target page ($calculatedTarget) (for the non-existing page `$ID` with the matcher `$ruleMatcher`) does not exist");
48445a874f4SNico        throw new ExceptionNotFound();
48545a874f4SNico
48645a874f4SNico    }
48745a874f4SNico
48845a874f4SNico
48945a874f4SNico    /**
49045a874f4SNico     * @param $id
49145a874f4SNico     * @return array
49245a874f4SNico     */
49345a874f4SNico    private
49445a874f4SNico    function getBestPage($id): array
49545a874f4SNico    {
49645a874f4SNico
49745a874f4SNico        // The return parameters
49845a874f4SNico        $bestPageId = null;
49945a874f4SNico        $scorePageName = null;
50045a874f4SNico
50145a874f4SNico        // Get Score from a page
50245a874f4SNico        $pageName = noNS($id);
50345a874f4SNico        $pagesWithSameName = ft_pageLookup($pageName);
50445a874f4SNico        if (count($pagesWithSameName) > 0) {
50545a874f4SNico
50645a874f4SNico            // Search same namespace in the page found than in the Id page asked.
50745a874f4SNico            $bestNbWordFound = 0;
50845a874f4SNico
50945a874f4SNico
51045a874f4SNico            $wordsInPageSourceId = explode(':', $id);
51145a874f4SNico            foreach ($pagesWithSameName as $targetPageId => $title) {
51245a874f4SNico
51345a874f4SNico                // Nb of word found in the target page id
51445a874f4SNico                // that are in the source page id
51545a874f4SNico                $nbWordFound = 0;
51645a874f4SNico                foreach ($wordsInPageSourceId as $word) {
51745a874f4SNico                    $nbWordFound = $nbWordFound + substr_count($targetPageId, $word);
51845a874f4SNico                }
51945a874f4SNico
52045a874f4SNico                if ($bestPageId == null) {
52145a874f4SNico
52245a874f4SNico                    $bestNbWordFound = $nbWordFound;
52345a874f4SNico                    $bestPageId = $targetPageId;
52445a874f4SNico
52545a874f4SNico                } else {
52645a874f4SNico
52745a874f4SNico                    if ($nbWordFound >= $bestNbWordFound && strlen($bestPageId) > strlen($targetPageId)) {
52845a874f4SNico
52945a874f4SNico                        $bestNbWordFound = $nbWordFound;
53045a874f4SNico                        $bestPageId = $targetPageId;
53145a874f4SNico
53245a874f4SNico                    }
53345a874f4SNico
53445a874f4SNico                }
53545a874f4SNico
53645a874f4SNico            }
53745a874f4SNico            $config = ExecutionContext::getActualOrCreateFromEnv()->getConfig();
53845a874f4SNico            $weightFactorForSamePageName = $config->getValue('WeightFactorForSamePageName');
53945a874f4SNico            $weightFactorForSameNamespace = $config->getValue('WeightFactorForSameNamespace');
54045a874f4SNico            $scorePageName = $weightFactorForSamePageName + ($bestNbWordFound - 1) * $weightFactorForSameNamespace;
54145a874f4SNico            return array(
54245a874f4SNico                'id' => $bestPageId,
54345a874f4SNico                'score' => $scorePageName);
54445a874f4SNico        }
54545a874f4SNico        return array(
54645a874f4SNico            'id' => $bestPageId,
54745a874f4SNico            'score' => $scorePageName
54845a874f4SNico        );
54945a874f4SNico
55045a874f4SNico    }
55145a874f4SNico
55245a874f4SNico    /**
55345a874f4SNico     * getBestNamespace
55445a874f4SNico     * Return a list with 'BestNamespaceId Score'
55545a874f4SNico     * @param $id
55645a874f4SNico     * @return array
55745a874f4SNico     */
55845a874f4SNico    private
55945a874f4SNico    function scoreBestNamespace($id): array
56045a874f4SNico    {
56145a874f4SNico
56245a874f4SNico        $nameSpaces = array();
56345a874f4SNico        $pathNames = array();
56445a874f4SNico
56545a874f4SNico        // Parameters
56645a874f4SNico        $requestedPath = MarkupPath::createMarkupFromId($id);
56745a874f4SNico        try {
56845a874f4SNico            $pageNameSpace = $requestedPath->getParent();
56945a874f4SNico            $pathNames = array_slice($pageNameSpace->getNames(), 0, -1);
57045a874f4SNico            if (FileSystems::exists($pageNameSpace)) {
57145a874f4SNico                $nameSpaces = array($pageNameSpace->toAbsoluteId());
57245a874f4SNico            } else {
57345a874f4SNico                global $conf;
57445a874f4SNico                $nameSpaces = ft_pageLookup($conf['start']);
57545a874f4SNico            }
57645a874f4SNico        } catch (ExceptionNotFound $e) {
57745a874f4SNico            // no parent, root
57845a874f4SNico        }
57945a874f4SNico
58045a874f4SNico        // Parameters and search the best namespace
58145a874f4SNico        $bestNbWordFound = 0;
58245a874f4SNico        $bestNamespaceId = null;
58345a874f4SNico        foreach ($nameSpaces as $nameSpace) {
58445a874f4SNico
58545a874f4SNico            $nbWordFound = 0;
58645a874f4SNico            foreach ($pathNames as $pathName) {
58745a874f4SNico                if (strlen($pathName) > 2) {
58845a874f4SNico                    $nbWordFound = $nbWordFound + substr_count($nameSpace, $pathName);
58945a874f4SNico                }
59045a874f4SNico            }
59145a874f4SNico            if ($nbWordFound > $bestNbWordFound) {
59245a874f4SNico                // Take only the smallest namespace
59345a874f4SNico                if ($bestNbWordFound == null || strlen($nameSpace) < strlen($bestNamespaceId)) {
59445a874f4SNico                    $bestNbWordFound = $nbWordFound;
59545a874f4SNico                    $bestNamespaceId = $nameSpace;
59645a874f4SNico                }
59745a874f4SNico            }
59845a874f4SNico        }
59945a874f4SNico        $config = ExecutionContext::getActualOrCreateFromEnv()->getConfig();
60045a874f4SNico        $startPageFactor = $config->getValue('WeightFactorForStartPage');
60145a874f4SNico        $nameSpaceFactor = $config->getValue('WeightFactorForSameNamespace');
60245a874f4SNico        if ($bestNbWordFound > 0) {
60345a874f4SNico            $bestNamespaceScore = $bestNbWordFound * $nameSpaceFactor + $startPageFactor;
60445a874f4SNico        } else {
60545a874f4SNico            $bestNamespaceScore = 0;
60645a874f4SNico        }
60745a874f4SNico
60845a874f4SNico
60945a874f4SNico        return array(
61045a874f4SNico            'namespace' => $bestNamespaceId,
61145a874f4SNico            'score' => $bestNamespaceScore
61245a874f4SNico        );
61345a874f4SNico
61445a874f4SNico    }
61545a874f4SNico
61645a874f4SNico
61745a874f4SNico}
618