xref: /plugin/combo/action/router.php (revision 313de40a7a81adb8606d463d69a8d40c7499c8f8)
1c3437056SNickeau<?php
2c3437056SNickeau
3c3437056SNickeau
411d09b86Sgerardnicouse ComboStrap\DokuwikiId;
504fd306cSNickeauuse ComboStrap\ExceptionBadArgument;
604fd306cSNickeauuse ComboStrap\ExceptionBadSyntax;
704fd306cSNickeauuse ComboStrap\ExceptionCompile;
8b1aef534SNicouse ComboStrap\ExceptionNotFound;
904fd306cSNickeauuse ComboStrap\ExceptionSqliteNotAvailable;
1004fd306cSNickeauuse ComboStrap\ExecutionContext;
1104fd306cSNickeauuse ComboStrap\FileSystems;
12c3437056SNickeauuse ComboStrap\HttpResponse;
1304fd306cSNickeauuse ComboStrap\HttpResponseStatus;
14c3437056SNickeauuse ComboStrap\Identity;
15c3437056SNickeauuse ComboStrap\LogUtility;
1604fd306cSNickeauuse ComboStrap\MarkupPath;
17c3437056SNickeauuse ComboStrap\Mime;
1845a874f4SNicouse ComboStrap\Router;
1945a874f4SNicouse ComboStrap\RouterRedirection;
2045a874f4SNicouse ComboStrap\RouterRedirectionBuilder;
21c3437056SNickeauuse ComboStrap\Site;
2204fd306cSNickeauuse ComboStrap\SiteConfig;
23c3437056SNickeauuse ComboStrap\Sqlite;
2404fd306cSNickeauuse ComboStrap\Web\Url;
2511d09b86Sgerardnicouse ComboStrap\Web\UrlEndpoint;
2654743e42Sgerardnicouse ComboStrap\Web\UrlRewrite;
27c3437056SNickeau
2804fd306cSNickeaurequire_once(__DIR__ . '/../vendor/autoload.php');
29c3437056SNickeau
30c3437056SNickeau/**
31c3437056SNickeau * Class action_plugin_combo_url
32c3437056SNickeau *
33c3437056SNickeau * The actual URL manager
34c3437056SNickeau *
35c3437056SNickeau *
36c3437056SNickeau */
37c3437056SNickeauclass action_plugin_combo_router extends DokuWiki_Action_Plugin
38c3437056SNickeau{
39c3437056SNickeau
40c3437056SNickeau    /**
41c3437056SNickeau     * @deprecated
42c3437056SNickeau     */
43c3437056SNickeau    const URL_MANAGER_ENABLE_CONF = "enableUrlManager";
44c3437056SNickeau    const ROUTER_ENABLE_CONF = "enableRouter";
45c3437056SNickeau
46c3437056SNickeau
47c3437056SNickeau    // Where the target id value comes from
48c3437056SNickeau
49c3437056SNickeau
50c3437056SNickeau    // The constant parameters
51c3437056SNickeau
52c3437056SNickeau    /** @var string - a name used in log and other places */
53c3437056SNickeau    const NAME = 'Url Manager';
54c3437056SNickeau    const CANONICAL = 'router';
55c3437056SNickeau    const PAGE_404 = "<html lang=\"en\"><body></body></html>";
56c3437056SNickeau    const REFRESH_HEADER_NAME = "Refresh";
57c3437056SNickeau    const REFRESH_HEADER_PREFIX = self::REFRESH_HEADER_NAME . ': 0;url=';
5804fd306cSNickeau    const LOCATION_HEADER_PREFIX = HttpResponse::LOCATION_HEADER_NAME . ": ";
59c3437056SNickeau    public const URL_MANAGER_NAME = "Router";
60c3437056SNickeau
61c3437056SNickeau
62c3437056SNickeau    function __construct()
63c3437056SNickeau    {
64c3437056SNickeau        // enable direct access to language strings
65c3437056SNickeau        // ie $this->lang
66c3437056SNickeau        $this->setupLocale();
67c3437056SNickeau
68c3437056SNickeau    }
69c3437056SNickeau
70c3437056SNickeau    /**
7104fd306cSNickeau     * @param string $refreshHeader
72c3437056SNickeau     * @return false|string
73c3437056SNickeau     */
7404fd306cSNickeau    public static function getUrlFromRefresh(string $refreshHeader)
75c3437056SNickeau    {
76c3437056SNickeau        return substr($refreshHeader, strlen(action_plugin_combo_router::REFRESH_HEADER_PREFIX));
77c3437056SNickeau    }
78c3437056SNickeau
79c3437056SNickeau    public static function getUrlFromLocation($refreshHeader)
80c3437056SNickeau    {
81c3437056SNickeau        return substr($refreshHeader, strlen(action_plugin_combo_router::LOCATION_HEADER_PREFIX));
82c3437056SNickeau    }
83c3437056SNickeau
84c3437056SNickeau
85c3437056SNickeau    /**
86c3437056SNickeau     * Determine if the request should be banned based on the id
87c3437056SNickeau     *
88c3437056SNickeau     * @param string $id
89c3437056SNickeau     * @return bool
90c3437056SNickeau     *
91c3437056SNickeau     * See also {@link https://perishablepress.com/7g-firewall/#features}
92c3437056SNickeau     * for blocking rules on http request data such as:
93c3437056SNickeau     *   * query_string
94c3437056SNickeau     *   * user_agent,
95c3437056SNickeau     *   * remote host
96c3437056SNickeau     */
97c3437056SNickeau    public static function isShadowBanned(string $id): bool
98c3437056SNickeau    {
99c3437056SNickeau        /**
100c3437056SNickeau         * ie
101c3437056SNickeau         * wp-json:api:flutter_woo:config_file
102c3437056SNickeau         * wp-content:plugins:wpdiscuz:themes:default:style-rtl.css
103c3437056SNickeau         * wp-admin
104c3437056SNickeau         * 2020:wp-includes:wlwmanifest.xml
105c3437056SNickeau         * wp-content:start
106c3437056SNickeau         * wp-admin:css:start
107c3437056SNickeau         * sito:wp-includes:wlwmanifest.xml
108c3437056SNickeau         * site:wp-includes:wlwmanifest.xml
109c3437056SNickeau         * cms:wp-includes:wlwmanifest.xml
110c3437056SNickeau         * test:wp-includes:wlwmanifest.xml
111c3437056SNickeau         * media:wp-includes:wlwmanifest.xml
112c3437056SNickeau         * wp2:wp-includes:wlwmanifest.xml
113c3437056SNickeau         * 2019:wp-includes:wlwmanifest.xml
114c3437056SNickeau         * shop:wp-includes:wlwmanifest.xml
115c3437056SNickeau         * wp1:wp-includes:wlwmanifest.xml
116c3437056SNickeau         * news:wp-includes:wlwmanifest.xml
117c3437056SNickeau         * 2018:wp-includes:wlwmanifest.xml
118c3437056SNickeau         */
119c3437056SNickeau        if (strpos($id, 'wp-') !== false) {
120c3437056SNickeau            return true;
121c3437056SNickeau        }
122c3437056SNickeau
123c3437056SNickeau        /**
124c3437056SNickeau         * db:oracle:long_or_1_utl_inaddr.get_host_address_chr_33_chr_126_chr_33_chr_65_chr_66_chr_67_chr_49_chr_52_chr_53_chr_90_chr_81_chr_54_chr_50_chr_68_chr_87_chr_81_chr_65_chr_70_chr_80_chr_79_chr_73_chr_89_chr_67_chr_70_chr_68_chr_33_chr_126_chr_33
125c3437056SNickeau         * db:oracle:999999.9:union:all:select_null:from_dual
126c3437056SNickeau         * db:oracle:999999.9:union:all:select_null:from_dual_and_0_0
127c3437056SNickeau         */
128c3437056SNickeau        if (preg_match('/_chr_|_0_0/', $id) === 1) {
129c3437056SNickeau            return true;
130c3437056SNickeau        }
131c3437056SNickeau
132c3437056SNickeau
133c3437056SNickeau        /**
134c3437056SNickeau         * ie
135c3437056SNickeau         * git:objects:
136c3437056SNickeau         * git:refs:heads:stable
137c3437056SNickeau         * git:logs:refs:heads:main
138c3437056SNickeau         * git:logs:refs:heads:stable
139c3437056SNickeau         * git:hooks:pre-push.sample
140c3437056SNickeau         * git:hooks:pre-receive.sample
141c3437056SNickeau         */
142c3437056SNickeau        if (strpos($id, "git:") === 0) {
143c3437056SNickeau            return true;
144c3437056SNickeau        }
145c3437056SNickeau
146c3437056SNickeau        return false;
147c3437056SNickeau
148c3437056SNickeau    }
149c3437056SNickeau
150c3437056SNickeau    /**
151c3437056SNickeau     * @param string $id
152c3437056SNickeau     * @return bool
153c3437056SNickeau     * well-known:traffic-advice = https://github.com/buettner/private-prefetch-proxy/blob/main/traffic-advice.md
154c3437056SNickeau     * .well-known/security.txt, id=well-known:security.txt = https://securitytxt.org/
155c3437056SNickeau     * well-known:dnt-policy.txt
156c3437056SNickeau     */
157c3437056SNickeau    public static function isWellKnownFile(string $id): bool
158c3437056SNickeau    {
159c3437056SNickeau        return strpos($id, "well-known") === 0;
160c3437056SNickeau    }
161c3437056SNickeau
162c3437056SNickeau
163c3437056SNickeau    function register(Doku_Event_Handler $controller)
164c3437056SNickeau    {
165c3437056SNickeau
16604fd306cSNickeau        if (SiteConfig::getConfValue(self::ROUTER_ENABLE_CONF, 1)) {
16704fd306cSNickeau
168c3437056SNickeau            /**
169c3437056SNickeau             * This will call the function {@link action_plugin_combo_router::_router()}
170c3437056SNickeau             * The event is not DOKUWIKI_STARTED because this is not the first one
171c3437056SNickeau             *
172c3437056SNickeau             * https://www.dokuwiki.org/devel:event:init_lang_load
173c3437056SNickeau             */
174c3437056SNickeau            $controller->register_hook('DOKUWIKI_STARTED',
17504fd306cSNickeau                'BEFORE',
176c3437056SNickeau                $this,
177c3437056SNickeau                'router',
178c3437056SNickeau                array());
179c3437056SNickeau
180c3437056SNickeau            /**
1815187326aSNico             * Bot Ban functionality
182c3437056SNickeau             *
1835187326aSNico             * Because we make a redirection to the home page, we need to check
1845187326aSNico             * if the home is readable, for that, the AUTH plugin needs to be initialized
1855187326aSNico             * That's why we wait
1865187326aSNico             * https://www.dokuwiki.org/devel:event:dokuwiki_init_done
1875187326aSNico             *
1885187326aSNico             * and we can't use
189c3437056SNickeau             * https://www.dokuwiki.org/devel:event:init_lang_load
1905187326aSNico             * because there is no auth setup in {@link auth_aclcheck_cb()}
1915187326aSNico             * and the the line `if (!$auth instanceof AuthPlugin) return AUTH_NONE;` return none;
192c3437056SNickeau             */
1935187326aSNico            $controller->register_hook('DOKUWIKI_INIT_DONE', 'BEFORE', $this, 'ban', array());
194c3437056SNickeau
195c3437056SNickeau        }
196c3437056SNickeau
197c3437056SNickeau
198c3437056SNickeau    }
199c3437056SNickeau
200c3437056SNickeau    /**
201c3437056SNickeau     *
202c3437056SNickeau     * We have created a spacial ban function that is
203c3437056SNickeau     * called before the first function
204c3437056SNickeau     * {@link action_plugin_combo_metalang::load_lang()}
205c3437056SNickeau     * to spare CPU.
206c3437056SNickeau     *
207c3437056SNickeau     * @param $event
208c3437056SNickeau     * @throws Exception
209c3437056SNickeau     */
210c3437056SNickeau    function ban(&$event)
211c3437056SNickeau    {
212c3437056SNickeau
21345a874f4SNico        $id = Router::getOriginalIdFromRequest();
21406ecf9e7Sgerardnico        if ($id === null) {
21506ecf9e7Sgerardnico            return;
21606ecf9e7Sgerardnico        }
21704fd306cSNickeau        $page = MarkupPath::createMarkupFromId($id);
2185187326aSNico        if (FileSystems::exists($page)) {
2195187326aSNico            return;
2205187326aSNico        }
2215187326aSNico
222c3437056SNickeau        // Well known
223c3437056SNickeau        if (self::isWellKnownFile($id)) {
22445a874f4SNico            $redirection = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_WELL_KNOWN)
22545a874f4SNico                ->setType(RouterRedirection::REDIRECT_NOTFOUND_METHOD)
22645a874f4SNico                ->build();
22745a874f4SNico            $this->logRedirection($redirection);
22804fd306cSNickeau            ExecutionContext::getActualOrCreateFromEnv()
22904fd306cSNickeau                ->response()
23004fd306cSNickeau                ->setStatus(HttpResponseStatus::NOT_FOUND)
23104fd306cSNickeau                ->end();
232c3437056SNickeau            return;
233c3437056SNickeau        }
234c3437056SNickeau
235c3437056SNickeau        // Shadow banned
236c3437056SNickeau        if (self::isShadowBanned($id)) {
23745a874f4SNico            $webSiteHomePage = MarkupPath::createMarkupFromId(Site::getIndexPageName());
23845a874f4SNico            $redirection = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_SHADOW_BANNED)
23945a874f4SNico                ->setType(RouterRedirection::REDIRECT_TRANSPARENT_METHOD)
24045a874f4SNico                ->setTargetMarkupPath($webSiteHomePage)
24145a874f4SNico                ->build();
24245a874f4SNico            $this->executeTransparentRedirect($redirection);
243c3437056SNickeau        }
2445187326aSNico
245c3437056SNickeau    }
246c3437056SNickeau
247c3437056SNickeau    /**
248c3437056SNickeau     * @param $event Doku_Event
249c3437056SNickeau     * @param $param
250c3437056SNickeau     * @return void
251c3437056SNickeau     */
25245a874f4SNico    function router(Doku_Event &$event, $param)
253c3437056SNickeau    {
254c3437056SNickeau
25504fd306cSNickeau        /**
25604fd306cSNickeau         * Just the {@link ExecutionContext::SHOW_ACTION}
25704fd306cSNickeau         * may be redirected
25804fd306cSNickeau         */
25904fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv();
26004fd306cSNickeau        if ($executionContext->getExecutingAction() !== ExecutionContext::SHOW_ACTION) {
26104fd306cSNickeau            return;
26204fd306cSNickeau        }
263c3437056SNickeau
26445a874f4SNico
26545a874f4SNico        /**
266*ecf8d738SNico         * If the ID is a permalink, it is already the real id
267*ecf8d738SNico         * Why? because unfortunately, DOKUWIKI_STARTED is not the first event
268*ecf8d738SNico         * {@link action_plugin_combo_lang::load_lang()} may have already
269*ecf8d738SNico         * transformed a permalink into a real dokuwiki id
27045a874f4SNico         */
271*ecf8d738SNico        global $ID;
272*ecf8d738SNico        $page = MarkupPath::createMarkupFromId($ID);
27345a874f4SNico        if (FileSystems::exists($page)) {
27445a874f4SNico            return;
27545a874f4SNico        }
27645a874f4SNico
27745a874f4SNico        /**
27845a874f4SNico         * Doku Rewrite is not supported
27945a874f4SNico         */
28054743e42Sgerardnico        $urlRewrite = Site::getUrlRewrite();
28154743e42Sgerardnico        if ($urlRewrite == UrlRewrite::VALUE_DOKU_REWRITE) {
28254743e42Sgerardnico            UrlRewrite::sendErrorMessage();
28354743e42Sgerardnico            return;
28454743e42Sgerardnico        }
285c3437056SNickeau
286c3437056SNickeau        /**
28745a874f4SNico         * Try to find a redirection
288c3437056SNickeau         */
28945a874f4SNico        $router = new Router();
29004fd306cSNickeau        try {
29145a874f4SNico            $redirection = $router->getRedirection();
29204fd306cSNickeau        } catch (ExceptionSqliteNotAvailable $e) {
29345a874f4SNico            // no Sql Lite
294c3437056SNickeau            return;
295b1aef534SNico        } catch (ExceptionNotFound $e) {
29645a874f4SNico            // no redirection
297c3437056SNickeau            return;
29845a874f4SNico        } catch (Exception $e) {
29945a874f4SNico            // Error
30045a874f4SNico            LogUtility::error("An unexpected error has occurred while trying to get a redirection", LogUtility::SUPPORT_CANONICAL, $e);
301c3437056SNickeau            return;
302c3437056SNickeau        }
303c3437056SNickeau
304c3437056SNickeau
305c3437056SNickeau        /**
30645a874f4SNico         * Special Mode where the redirection is just a change of ACT
307c3437056SNickeau         */
30845a874f4SNico        if ($redirection->getOrigin() === Router::GO_TO_EDIT_MODE) {
309c3437056SNickeau            global $ACT;
310c3437056SNickeau            $ACT = 'edit';
31145a874f4SNico            return;
31245a874f4SNico        }
31345a874f4SNico
31445a874f4SNico        /**
31545a874f4SNico         * Other redirections
31645a874f4SNico         */
31745a874f4SNico        switch ($redirection->getType()) {
31845a874f4SNico            case RouterRedirection::REDIRECT_TRANSPARENT_METHOD:
31945a874f4SNico                try {
32045a874f4SNico                    $this->executeTransparentRedirect($redirection);
32145a874f4SNico                } catch (ExceptionCompile $e) {
32245a874f4SNico                    LogUtility::error("Internal Error: A transparent redirect errors has occurred", LogUtility::SUPPORT_CANONICAL, $e);
32345a874f4SNico                }
32445a874f4SNico                return;
32545a874f4SNico            default:
32645a874f4SNico                try {
32745a874f4SNico                    $this->executeHttpRedirect($redirection);
32845a874f4SNico                } catch (ExceptionCompile $e) {
32945a874f4SNico                    LogUtility::error("Internal Error: A http redirect errors has occurred", LogUtility::SUPPORT_CANONICAL, $e);
33045a874f4SNico                }
33145a874f4SNico        }
33245a874f4SNico
333c3437056SNickeau
334c3437056SNickeau    }
335c3437056SNickeau
336c3437056SNickeau
337c3437056SNickeau    /**
338c3437056SNickeau     * Redirect to an internal page ie:
339c3437056SNickeau     *   * on the same domain
340c3437056SNickeau     *   * no HTTP redirect
341c3437056SNickeau     *   * id rewrite
342*ecf8d738SNico     * It happens when we use the id in the URL
34345a874f4SNico     * @param RouterRedirection $redirection - target page id
34445a874f4SNico     * @return void - return true if the user has the permission and that the redirect was done
34545a874f4SNico     * @throws ExceptionCompile
346c3437056SNickeau     */
347c3437056SNickeau    private
34845a874f4SNico    function executeTransparentRedirect(RouterRedirection $redirection): void
349c3437056SNickeau    {
35045a874f4SNico        $markupPath = $redirection->getTargetMarkupPath();
35145a874f4SNico        if ($markupPath === null) {
35245a874f4SNico            throw new ExceptionCompile("A transparent redirect should have a wiki path. Origin {$redirection->getOrigin()}");
353c3437056SNickeau        }
35445a874f4SNico        $targetPageId = $redirection->getTargetMarkupPath()->toAbsoluteId();
355c3437056SNickeau
356c3437056SNickeau        // If the user does not have the right to see the target page
357c3437056SNickeau        // don't do anything
358c3437056SNickeau        if (!(Identity::isReader($targetPageId))) {
35945a874f4SNico            return;
360c3437056SNickeau        }
361c3437056SNickeau
362c3437056SNickeau        // Change the id
363c3437056SNickeau        global $ID;
364c3437056SNickeau        global $INFO;
365c3437056SNickeau        $sourceId = $ID;
366c3437056SNickeau        $ID = $targetPageId;
36704fd306cSNickeau        if (isset($_REQUEST["id"])) {
36804fd306cSNickeau            $_REQUEST["id"] = $targetPageId;
36904fd306cSNickeau        }
37004fd306cSNickeau        if (isset($_GET["id"])) {
37104fd306cSNickeau            $_GET["id"] = $targetPageId;
37204fd306cSNickeau        }
3734cadd4f8SNickeau
374c3437056SNickeau        /**
3754cadd4f8SNickeau         * Refresh the $INFO data
3764cadd4f8SNickeau         *
3774cadd4f8SNickeau         * the info attributes are used elsewhere
3784cadd4f8SNickeau         *   'id': for the sidebar
3794cadd4f8SNickeau         *   'exist' : for the meta robot = noindex,follow, see {@link tpl_metaheaders()}
3804cadd4f8SNickeau         *   'rev' : for the edit button to be sure that the page is still the same
381c3437056SNickeau         */
3824cadd4f8SNickeau        $INFO = pageinfo();
383c3437056SNickeau
384c3437056SNickeau        /**
385c3437056SNickeau         * Not compatible with
386c3437056SNickeau         * https://www.dokuwiki.org/config:send404 is enabled
387c3437056SNickeau         *
388c3437056SNickeau         * This check happens before that dokuwiki is started
389c3437056SNickeau         * and send an header in doku.php
390c3437056SNickeau         *
391c3437056SNickeau         * We send a warning
392c3437056SNickeau         */
393c3437056SNickeau        global $conf;
39445a874f4SNico        if ($conf['send404']) {
395c3437056SNickeau            LogUtility::msg("The <a href=\"https://www.dokuwiki.org/config:send404\">dokuwiki send404 configuration</a> is on and should be disabled when using the url manager", LogUtility::LVL_MSG_ERROR, self::CANONICAL);
396c3437056SNickeau        }
397c3437056SNickeau
398c3437056SNickeau        // Redirection
39945a874f4SNico        $this->logRedirection($redirection);
400c3437056SNickeau
401c3437056SNickeau    }
402c3437056SNickeau
403c3437056SNickeau
404c3437056SNickeau    /**
405c3437056SNickeau     * The general HTTP Redirect method to an internal page
406c3437056SNickeau     * where the redirection method decide which type of redirection
40745a874f4SNico     * @throws ExceptionCompile - if any error
408c3437056SNickeau     */
409c3437056SNickeau    private
41045a874f4SNico    function executeHttpRedirect(RouterRedirection $redirection): void
411c3437056SNickeau    {
412c3437056SNickeau
413c3437056SNickeau
414c3437056SNickeau        // Log the redirections
41545a874f4SNico        $this->logRedirection($redirection);
416c3437056SNickeau
417c3437056SNickeau
41845a874f4SNico        $targetUrl = $redirection->getTargetUrl();
41904fd306cSNickeau
42045a874f4SNico        if ($targetUrl !== null) {
421c3437056SNickeau
422c3437056SNickeau            // defend against HTTP Response Splitting
423c3437056SNickeau            // https://owasp.org/www-community/attacks/HTTP_Response_Splitting
42445a874f4SNico            $targetUrl = stripctl($targetUrl->toAbsoluteUrlString());
42545a874f4SNico
426c3437056SNickeau
427c3437056SNickeau        } else {
428c3437056SNickeau
42945a874f4SNico            global $ID;
430c3437056SNickeau
431c3437056SNickeau            // if this is search engine redirect
43245a874f4SNico            $url = UrlEndpoint::createDokuUrl();
43345a874f4SNico            switch ($redirection->getOrigin()) {
43445a874f4SNico                case RouterRedirection::TARGET_ORIGIN_SEARCH_ENGINE:
43545a874f4SNico                {
436c3437056SNickeau                    $replacementPart = array(':', '_', '-');
437c3437056SNickeau                    $query = str_replace($replacementPart, ' ', $ID);
43811d09b86Sgerardnico                    $url->setQueryParameter(ExecutionContext::DO_ATTRIBUTE, ExecutionContext::SEARCH_ACTION);
43911d09b86Sgerardnico                    $url->setQueryParameter("q", $query);
44045a874f4SNico                    $url->setQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $ID);
44145a874f4SNico                    break;
44245a874f4SNico                }
44345a874f4SNico                default:
44445a874f4SNico
44545a874f4SNico                    $markupPath = $redirection->getTargetMarkupPath();
44645a874f4SNico                    if ($markupPath == null) {
44745a874f4SNico                        // should not happen (Both may be null but only on edit mode)
44845a874f4SNico                        throw new ExceptionCompile("Internal Error When executing a http redirect, the URL or the wiki page should not be null");
44945a874f4SNico                    }
45045a874f4SNico                    $url->setQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $markupPath->toAbsoluteId());
45145a874f4SNico
45245a874f4SNico
453c3437056SNickeau            }
454c3437056SNickeau
4555b0932efSgerardnico            /**
4565b0932efSgerardnico             * Doing a permanent redirect with a added query string
4575b0932efSgerardnico             * create a new page url on the search engine
4585b0932efSgerardnico             *
4595b0932efSgerardnico             * ie
4605b0932efSgerardnico             * http://host/page
4615b0932efSgerardnico             * is not the same
4625b0932efSgerardnico             * than
4635b0932efSgerardnico             * http://host/page?whatever
4645b0932efSgerardnico             *
4655b0932efSgerardnico             * We can't pass query string otherwise, we get
46649b8fb24Sgerardnico             * the SEO warning / error
4675b0932efSgerardnico             * `Alternative page with proper canonical tag`
46811d09b86Sgerardnico             *
46911d09b86Sgerardnico             * Use HTTP X header for debug
4705b0932efSgerardnico             */
47145a874f4SNico            if ($redirection->getType() !== RouterRedirection::REDIRECT_PERMANENT_METHOD) {
47211d09b86Sgerardnico                $url->setQueryParameter(action_plugin_combo_routermessage::ORIGIN_PAGE, $ID);
47345a874f4SNico                $url->setQueryParameter(action_plugin_combo_routermessage::ORIGIN_TYPE, $redirection->getOrigin());
4745b0932efSgerardnico            }
4755b0932efSgerardnico
47645a874f4SNico
47711d09b86Sgerardnico            $targetUrl = $url->toAbsoluteUrlString();
478c3437056SNickeau
47945a874f4SNico
48045a874f4SNico        }
48145a874f4SNico
48245a874f4SNico
48345a874f4SNico        /**
48445a874f4SNico         * Check that we are not redirecting to the same URL
48545a874f4SNico         * to avoid the TOO_MANY_REDIRECT error
48645a874f4SNico         */
48745a874f4SNico        $requestURL = Url::createFromString($_SERVER['REQUEST_URI'])->toAbsoluteUrlString();
48845a874f4SNico        if ($requestURL === $targetUrl) {
48945a874f4SNico            throw new ExceptionCompile("A redirection should not redirect to the requested URL. Redirection Origin: {$redirection->getOrigin()}, Redirection URL:{$targetUrl} ");
490c3437056SNickeau        }
491c3437056SNickeau
492c3437056SNickeau        /**
493c3437056SNickeau         * The dokuwiki function {@link send_redirect()}
494c3437056SNickeau         * set the `Location header` and in php, the header function
495c3437056SNickeau         * in this case change the status code to 302 Arghhhh.
496c3437056SNickeau         * The code below is adapted from this function {@link send_redirect()}
497c3437056SNickeau         */
498c3437056SNickeau        global $MSG; // are there any undisplayed messages? keep them in session for display
499c3437056SNickeau        if (isset($MSG) && count($MSG) && !defined('NOSESSION')) {
500c3437056SNickeau            //reopen session, store data and close session again
501c3437056SNickeau            @session_start();
502c3437056SNickeau            $_SESSION[DOKU_COOKIE]['msg'] = $MSG;
503c3437056SNickeau        }
504c3437056SNickeau        session_write_close(); // always close the session
505c3437056SNickeau
50645a874f4SNico        switch ($redirection->getType()) {
5075b0932efSgerardnico
50845a874f4SNico            case RouterRedirection::REDIRECT_PERMANENT_METHOD:
50904fd306cSNickeau                ExecutionContext::getActualOrCreateFromEnv()
51004fd306cSNickeau                    ->response()
51104fd306cSNickeau                    ->setStatus(HttpResponseStatus::PERMANENT_REDIRECT)
512c3437056SNickeau                    ->addHeader(self::LOCATION_HEADER_PREFIX . $targetUrl)
51304fd306cSNickeau                    ->end();
51445a874f4SNico                return;
5155b0932efSgerardnico
51645a874f4SNico            case RouterRedirection::REDIRECT_NOTFOUND_METHOD:
5175b0932efSgerardnico
518c3437056SNickeau                // Empty 404 body to not get the standard 404 page of the browser
519c3437056SNickeau                // but a blank page to avoid a sort of FOUC.
520c3437056SNickeau                // ie the user see a page briefly
52104fd306cSNickeau                ExecutionContext::getActualOrCreateFromEnv()
52204fd306cSNickeau                    ->response()
52304fd306cSNickeau                    ->setStatus(HttpResponseStatus::NOT_FOUND)
524c3437056SNickeau                    ->addHeader(self::REFRESH_HEADER_PREFIX . $targetUrl)
52504fd306cSNickeau                    ->setBody(self::PAGE_404, Mime::getHtml())
52604fd306cSNickeau                    ->end();
52745a874f4SNico                return;
528c3437056SNickeau
529c3437056SNickeau            default:
53045a874f4SNico                throw new ExceptionCompile("The type ({$redirection->getType()}) is not an http redirection");
531c3437056SNickeau
532c3437056SNickeau        }
533c3437056SNickeau
534c3437056SNickeau
535c3437056SNickeau    }
536c3437056SNickeau
537c3437056SNickeau
538c3437056SNickeau    /**
539c3437056SNickeau     *
540c3437056SNickeau     *   * For a conf file, it will update the Redirection Action Data as Referrer, Count Of Redirection, Redirection Date
541c3437056SNickeau     *   * For a SQlite database, it will add a row into the log
542c3437056SNickeau     *
543c3437056SNickeau     * @param string $sourcePageId
544c3437056SNickeau     * @param $targetPageId
545c3437056SNickeau     * @param $algorithmic
546c3437056SNickeau     * @param $method - http or rewrite
547c3437056SNickeau     */
54845a874f4SNico    function logRedirection(RouterRedirection $redirection)
549c3437056SNickeau    {
55045a874f4SNico        global $ID;
551c3437056SNickeau
552c3437056SNickeau        $row = array(
553c3437056SNickeau            "TIMESTAMP" => date("c"),
55445a874f4SNico            "SOURCE" => $ID,
55545a874f4SNico            "TARGET" => $redirection->getTargetAsString(),
55670bbd7f1Sgerardnico            "REFERRER" => $_SERVER['HTTP_REFERER'] ?? null,
55745a874f4SNico            "TYPE" => $redirection->getOrigin(),
55845a874f4SNico            "METHOD" => $redirection->getType()
559c3437056SNickeau        );
56045a874f4SNico        try {
561c3437056SNickeau            $request = Sqlite::createOrGetBackendSqlite()
562c3437056SNickeau                ->createRequest()
563c3437056SNickeau                ->setTableRow('redirections_log', $row);
56445a874f4SNico        } catch (ExceptionSqliteNotAvailable $e) {
56545a874f4SNico            return;
56645a874f4SNico        }
567c3437056SNickeau        try {
568c3437056SNickeau            $request
569c3437056SNickeau                ->execute();
57004fd306cSNickeau        } catch (ExceptionCompile $e) {
571c3437056SNickeau            LogUtility::msg("Redirection Log Insert Error. {$e->getMessage()}");
572c3437056SNickeau        } finally {
573c3437056SNickeau            $request->close();
574c3437056SNickeau        }
575c3437056SNickeau
576c3437056SNickeau
577c3437056SNickeau    }
578c3437056SNickeau
579c3437056SNickeau
580c3437056SNickeau}
581