xref: /plugin/combo/action/router.php (revision 45a874f4355f8bee7459e5d3b79e86e68468b316)
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;
18c3437056SNickeauuse ComboStrap\PageRules;
19*45a874f4SNicouse ComboStrap\Router;
20*45a874f4SNicouse ComboStrap\RouterRedirection;
21*45a874f4SNicouse ComboStrap\RouterRedirectionBuilder;
22c3437056SNickeauuse ComboStrap\Site;
2304fd306cSNickeauuse ComboStrap\SiteConfig;
24c3437056SNickeauuse ComboStrap\Sqlite;
2504fd306cSNickeauuse ComboStrap\Web\Url;
2611d09b86Sgerardnicouse ComboStrap\Web\UrlEndpoint;
2754743e42Sgerardnicouse ComboStrap\Web\UrlRewrite;
28c3437056SNickeau
2904fd306cSNickeaurequire_once(__DIR__ . '/../vendor/autoload.php');
30c3437056SNickeau
31c3437056SNickeau/**
32c3437056SNickeau * Class action_plugin_combo_url
33c3437056SNickeau *
34c3437056SNickeau * The actual URL manager
35c3437056SNickeau *
36c3437056SNickeau *
37c3437056SNickeau */
38c3437056SNickeauclass action_plugin_combo_router extends DokuWiki_Action_Plugin
39c3437056SNickeau{
40c3437056SNickeau
41c3437056SNickeau    /**
42c3437056SNickeau     * @deprecated
43c3437056SNickeau     */
44c3437056SNickeau    const URL_MANAGER_ENABLE_CONF = "enableUrlManager";
45c3437056SNickeau    const ROUTER_ENABLE_CONF = "enableRouter";
46c3437056SNickeau
47c3437056SNickeau
48c3437056SNickeau    // Where the target id value comes from
49c3437056SNickeau
50c3437056SNickeau
51c3437056SNickeau    // The constant parameters
52c3437056SNickeau
53c3437056SNickeau    /** @var string - a name used in log and other places */
54c3437056SNickeau    const NAME = 'Url Manager';
55c3437056SNickeau    const CANONICAL = 'router';
56c3437056SNickeau    const PAGE_404 = "<html lang=\"en\"><body></body></html>";
57c3437056SNickeau    const REFRESH_HEADER_NAME = "Refresh";
58c3437056SNickeau    const REFRESH_HEADER_PREFIX = self::REFRESH_HEADER_NAME . ': 0;url=';
5904fd306cSNickeau    const LOCATION_HEADER_PREFIX = HttpResponse::LOCATION_HEADER_NAME . ": ";
60c3437056SNickeau    public const URL_MANAGER_NAME = "Router";
61c3437056SNickeau
62c3437056SNickeau
63c3437056SNickeau    /**
64c3437056SNickeau     * @var PageRules
65c3437056SNickeau     */
66c3437056SNickeau    private $pageRules;
67c3437056SNickeau
68c3437056SNickeau
69c3437056SNickeau    function __construct()
70c3437056SNickeau    {
71c3437056SNickeau        // enable direct access to language strings
72c3437056SNickeau        // ie $this->lang
73c3437056SNickeau        $this->setupLocale();
74c3437056SNickeau
75c3437056SNickeau    }
76c3437056SNickeau
77c3437056SNickeau    /**
7804fd306cSNickeau     * @param string $refreshHeader
79c3437056SNickeau     * @return false|string
80c3437056SNickeau     */
8104fd306cSNickeau    public static function getUrlFromRefresh(string $refreshHeader)
82c3437056SNickeau    {
83c3437056SNickeau        return substr($refreshHeader, strlen(action_plugin_combo_router::REFRESH_HEADER_PREFIX));
84c3437056SNickeau    }
85c3437056SNickeau
86c3437056SNickeau    public static function getUrlFromLocation($refreshHeader)
87c3437056SNickeau    {
88c3437056SNickeau        return substr($refreshHeader, strlen(action_plugin_combo_router::LOCATION_HEADER_PREFIX));
89c3437056SNickeau    }
90c3437056SNickeau
91c3437056SNickeau
92c3437056SNickeau    /**
93c3437056SNickeau     * Determine if the request should be banned based on the id
94c3437056SNickeau     *
95c3437056SNickeau     * @param string $id
96c3437056SNickeau     * @return bool
97c3437056SNickeau     *
98c3437056SNickeau     * See also {@link https://perishablepress.com/7g-firewall/#features}
99c3437056SNickeau     * for blocking rules on http request data such as:
100c3437056SNickeau     *   * query_string
101c3437056SNickeau     *   * user_agent,
102c3437056SNickeau     *   * remote host
103c3437056SNickeau     */
104c3437056SNickeau    public static function isShadowBanned(string $id): bool
105c3437056SNickeau    {
106c3437056SNickeau        /**
107c3437056SNickeau         * ie
108c3437056SNickeau         * wp-json:api:flutter_woo:config_file
109c3437056SNickeau         * wp-content:plugins:wpdiscuz:themes:default:style-rtl.css
110c3437056SNickeau         * wp-admin
111c3437056SNickeau         * 2020:wp-includes:wlwmanifest.xml
112c3437056SNickeau         * wp-content:start
113c3437056SNickeau         * wp-admin:css:start
114c3437056SNickeau         * sito:wp-includes:wlwmanifest.xml
115c3437056SNickeau         * site:wp-includes:wlwmanifest.xml
116c3437056SNickeau         * cms:wp-includes:wlwmanifest.xml
117c3437056SNickeau         * test:wp-includes:wlwmanifest.xml
118c3437056SNickeau         * media:wp-includes:wlwmanifest.xml
119c3437056SNickeau         * wp2:wp-includes:wlwmanifest.xml
120c3437056SNickeau         * 2019:wp-includes:wlwmanifest.xml
121c3437056SNickeau         * shop:wp-includes:wlwmanifest.xml
122c3437056SNickeau         * wp1:wp-includes:wlwmanifest.xml
123c3437056SNickeau         * news:wp-includes:wlwmanifest.xml
124c3437056SNickeau         * 2018:wp-includes:wlwmanifest.xml
125c3437056SNickeau         */
126c3437056SNickeau        if (strpos($id, 'wp-') !== false) {
127c3437056SNickeau            return true;
128c3437056SNickeau        }
129c3437056SNickeau
130c3437056SNickeau        /**
131c3437056SNickeau         * 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
132c3437056SNickeau         * db:oracle:999999.9:union:all:select_null:from_dual
133c3437056SNickeau         * db:oracle:999999.9:union:all:select_null:from_dual_and_0_0
134c3437056SNickeau         */
135c3437056SNickeau        if (preg_match('/_chr_|_0_0/', $id) === 1) {
136c3437056SNickeau            return true;
137c3437056SNickeau        }
138c3437056SNickeau
139c3437056SNickeau
140c3437056SNickeau        /**
141c3437056SNickeau         * ie
142c3437056SNickeau         * git:objects:
143c3437056SNickeau         * git:refs:heads:stable
144c3437056SNickeau         * git:logs:refs:heads:main
145c3437056SNickeau         * git:logs:refs:heads:stable
146c3437056SNickeau         * git:hooks:pre-push.sample
147c3437056SNickeau         * git:hooks:pre-receive.sample
148c3437056SNickeau         */
149c3437056SNickeau        if (strpos($id, "git:") === 0) {
150c3437056SNickeau            return true;
151c3437056SNickeau        }
152c3437056SNickeau
153c3437056SNickeau        return false;
154c3437056SNickeau
155c3437056SNickeau    }
156c3437056SNickeau
157c3437056SNickeau    /**
158c3437056SNickeau     * @param string $id
159c3437056SNickeau     * @return bool
160c3437056SNickeau     * well-known:traffic-advice = https://github.com/buettner/private-prefetch-proxy/blob/main/traffic-advice.md
161c3437056SNickeau     * .well-known/security.txt, id=well-known:security.txt = https://securitytxt.org/
162c3437056SNickeau     * well-known:dnt-policy.txt
163c3437056SNickeau     */
164c3437056SNickeau    public static function isWellKnownFile(string $id): bool
165c3437056SNickeau    {
166c3437056SNickeau        return strpos($id, "well-known") === 0;
167c3437056SNickeau    }
168c3437056SNickeau
169c3437056SNickeau
170c3437056SNickeau    function register(Doku_Event_Handler $controller)
171c3437056SNickeau    {
172c3437056SNickeau
17304fd306cSNickeau        if (SiteConfig::getConfValue(self::ROUTER_ENABLE_CONF, 1)) {
17404fd306cSNickeau
175c3437056SNickeau            /**
176c3437056SNickeau             * This will call the function {@link action_plugin_combo_router::_router()}
177c3437056SNickeau             * The event is not DOKUWIKI_STARTED because this is not the first one
178c3437056SNickeau             *
179c3437056SNickeau             * https://www.dokuwiki.org/devel:event:init_lang_load
180c3437056SNickeau             */
181c3437056SNickeau            $controller->register_hook('DOKUWIKI_STARTED',
18204fd306cSNickeau                'BEFORE',
183c3437056SNickeau                $this,
184c3437056SNickeau                'router',
185c3437056SNickeau                array());
186c3437056SNickeau
187c3437056SNickeau            /**
1885187326aSNico             * Bot Ban functionality
189c3437056SNickeau             *
1905187326aSNico             * Because we make a redirection to the home page, we need to check
1915187326aSNico             * if the home is readable, for that, the AUTH plugin needs to be initialized
1925187326aSNico             * That's why we wait
1935187326aSNico             * https://www.dokuwiki.org/devel:event:dokuwiki_init_done
1945187326aSNico             *
1955187326aSNico             * and we can't use
196c3437056SNickeau             * https://www.dokuwiki.org/devel:event:init_lang_load
1975187326aSNico             * because there is no auth setup in {@link auth_aclcheck_cb()}
1985187326aSNico             * and the the line `if (!$auth instanceof AuthPlugin) return AUTH_NONE;` return none;
199c3437056SNickeau             */
2005187326aSNico            $controller->register_hook('DOKUWIKI_INIT_DONE', 'BEFORE', $this, 'ban', array());
201c3437056SNickeau
202c3437056SNickeau        }
203c3437056SNickeau
204c3437056SNickeau
205c3437056SNickeau    }
206c3437056SNickeau
207c3437056SNickeau    /**
208c3437056SNickeau     *
209c3437056SNickeau     * We have created a spacial ban function that is
210c3437056SNickeau     * called before the first function
211c3437056SNickeau     * {@link action_plugin_combo_metalang::load_lang()}
212c3437056SNickeau     * to spare CPU.
213c3437056SNickeau     *
214c3437056SNickeau     * @param $event
215c3437056SNickeau     * @throws Exception
216c3437056SNickeau     */
217c3437056SNickeau    function ban(&$event)
218c3437056SNickeau    {
219c3437056SNickeau
220*45a874f4SNico        $id = Router::getOriginalIdFromRequest();
22106ecf9e7Sgerardnico        if ($id === null) {
22206ecf9e7Sgerardnico            return;
22306ecf9e7Sgerardnico        }
22404fd306cSNickeau        $page = MarkupPath::createMarkupFromId($id);
2255187326aSNico        if (FileSystems::exists($page)) {
2265187326aSNico            return;
2275187326aSNico        }
2285187326aSNico
229c3437056SNickeau        // Well known
230c3437056SNickeau        if (self::isWellKnownFile($id)) {
231*45a874f4SNico            $redirection = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_WELL_KNOWN)
232*45a874f4SNico                ->setType(RouterRedirection::REDIRECT_NOTFOUND_METHOD)
233*45a874f4SNico                ->build();
234*45a874f4SNico            $this->logRedirection($redirection);
23504fd306cSNickeau            ExecutionContext::getActualOrCreateFromEnv()
23604fd306cSNickeau                ->response()
23704fd306cSNickeau                ->setStatus(HttpResponseStatus::NOT_FOUND)
23804fd306cSNickeau                ->end();
239c3437056SNickeau            return;
240c3437056SNickeau        }
241c3437056SNickeau
242c3437056SNickeau        // Shadow banned
243c3437056SNickeau        if (self::isShadowBanned($id)) {
244*45a874f4SNico            $webSiteHomePage = MarkupPath::createMarkupFromId(Site::getIndexPageName());
245*45a874f4SNico            $redirection = RouterRedirectionBuilder::createFromOrigin(RouterRedirection::TARGET_ORIGIN_SHADOW_BANNED)
246*45a874f4SNico                ->setType(RouterRedirection::REDIRECT_TRANSPARENT_METHOD)
247*45a874f4SNico                ->setTargetMarkupPath($webSiteHomePage)
248*45a874f4SNico                ->build();
249*45a874f4SNico            $this->executeTransparentRedirect($redirection);
250c3437056SNickeau        }
2515187326aSNico
252c3437056SNickeau    }
253c3437056SNickeau
254c3437056SNickeau    /**
255c3437056SNickeau     * @param $event Doku_Event
256c3437056SNickeau     * @param $param
257c3437056SNickeau     * @return void
258c3437056SNickeau     */
259*45a874f4SNico    function router(Doku_Event &$event, $param)
260c3437056SNickeau    {
261c3437056SNickeau
26204fd306cSNickeau        /**
26304fd306cSNickeau         * Just the {@link ExecutionContext::SHOW_ACTION}
26404fd306cSNickeau         * may be redirected
26504fd306cSNickeau         */
26604fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv();
26704fd306cSNickeau        if ($executionContext->getExecutingAction() !== ExecutionContext::SHOW_ACTION) {
26804fd306cSNickeau            return;
26904fd306cSNickeau        }
270c3437056SNickeau
271*45a874f4SNico
272*45a874f4SNico        /**
273*45a874f4SNico         * Redirect only if the page is not found
274*45a874f4SNico         */
275*45a874f4SNico        $id = Router::getOriginalIdFromRequest();
276*45a874f4SNico        if ($id === null) {
277*45a874f4SNico            return;
278*45a874f4SNico        }
279*45a874f4SNico        $page = MarkupPath::createMarkupFromId($id);
280*45a874f4SNico        if (FileSystems::exists($page)) {
281*45a874f4SNico            return;
282*45a874f4SNico        }
283*45a874f4SNico
284*45a874f4SNico
285*45a874f4SNico        /**
286*45a874f4SNico         * Doku Rewrite is not supported
287*45a874f4SNico         */
28854743e42Sgerardnico        $urlRewrite = Site::getUrlRewrite();
28954743e42Sgerardnico        if ($urlRewrite == UrlRewrite::VALUE_DOKU_REWRITE) {
29054743e42Sgerardnico            UrlRewrite::sendErrorMessage();
29154743e42Sgerardnico            return;
29254743e42Sgerardnico        }
293c3437056SNickeau
294c3437056SNickeau        /**
295*45a874f4SNico         * Try to find a redirection
296c3437056SNickeau         */
297*45a874f4SNico        $router = new Router();
29804fd306cSNickeau        try {
299*45a874f4SNico            $redirection = $router->getRedirection();
30004fd306cSNickeau        } catch (ExceptionSqliteNotAvailable $e) {
301*45a874f4SNico            // no Sql Lite
302c3437056SNickeau            return;
303b1aef534SNico        } catch (ExceptionNotFound $e) {
304*45a874f4SNico            // no redirection
305c3437056SNickeau            return;
306*45a874f4SNico        } catch (Exception $e) {
307*45a874f4SNico            // Error
308*45a874f4SNico            LogUtility::error("An unexpected error has occurred while trying to get a redirection", LogUtility::SUPPORT_CANONICAL, $e);
309c3437056SNickeau            return;
310c3437056SNickeau        }
311c3437056SNickeau
312c3437056SNickeau
313c3437056SNickeau        /**
314*45a874f4SNico         * Special Mode where the redirection is just a change of ACT
315c3437056SNickeau         */
316*45a874f4SNico        if ($redirection->getOrigin() === Router::GO_TO_EDIT_MODE) {
317c3437056SNickeau            global $ACT;
318c3437056SNickeau            $ACT = 'edit';
319*45a874f4SNico            return;
320*45a874f4SNico        }
321*45a874f4SNico
322*45a874f4SNico        /**
323*45a874f4SNico         * Other redirections
324*45a874f4SNico         */
325*45a874f4SNico        switch ($redirection->getType()) {
326*45a874f4SNico            case RouterRedirection::REDIRECT_TRANSPARENT_METHOD:
327*45a874f4SNico                try {
328*45a874f4SNico                    $this->executeTransparentRedirect($redirection);
329*45a874f4SNico                } catch (ExceptionCompile $e) {
330*45a874f4SNico                    LogUtility::error("Internal Error: A transparent redirect errors has occurred", LogUtility::SUPPORT_CANONICAL, $e);
331*45a874f4SNico                }
332*45a874f4SNico                return;
333*45a874f4SNico            default:
334*45a874f4SNico                try {
335*45a874f4SNico                    $this->executeHttpRedirect($redirection);
336*45a874f4SNico                } catch (ExceptionCompile $e) {
337*45a874f4SNico                    LogUtility::error("Internal Error: A http redirect errors has occurred", LogUtility::SUPPORT_CANONICAL, $e);
338*45a874f4SNico                }
339*45a874f4SNico        }
340*45a874f4SNico
341c3437056SNickeau
342c3437056SNickeau    }
343c3437056SNickeau
344c3437056SNickeau
345c3437056SNickeau    /**
346c3437056SNickeau     * Redirect to an internal page ie:
347c3437056SNickeau     *   * on the same domain
348c3437056SNickeau     *   * no HTTP redirect
349c3437056SNickeau     *   * id rewrite
350*45a874f4SNico     * @param RouterRedirection $redirection - target page id
351*45a874f4SNico     * @return void - return true if the user has the permission and that the redirect was done
352*45a874f4SNico     * @throws ExceptionCompile
353c3437056SNickeau     */
354c3437056SNickeau    private
355*45a874f4SNico    function executeTransparentRedirect(RouterRedirection $redirection): void
356c3437056SNickeau    {
357*45a874f4SNico        $markupPath = $redirection->getTargetMarkupPath();
358*45a874f4SNico        if ($markupPath === null) {
359*45a874f4SNico            throw new ExceptionCompile("A transparent redirect should have a wiki path. Origin {$redirection->getOrigin()}");
360c3437056SNickeau        }
361*45a874f4SNico        $targetPageId = $redirection->getTargetMarkupPath()->toAbsoluteId();
362c3437056SNickeau
363c3437056SNickeau        // If the user does not have the right to see the target page
364c3437056SNickeau        // don't do anything
365c3437056SNickeau        if (!(Identity::isReader($targetPageId))) {
366*45a874f4SNico            return;
367c3437056SNickeau        }
368c3437056SNickeau
369c3437056SNickeau        // Change the id
370c3437056SNickeau        global $ID;
371c3437056SNickeau        global $INFO;
372c3437056SNickeau        $sourceId = $ID;
373c3437056SNickeau        $ID = $targetPageId;
37404fd306cSNickeau        if (isset($_REQUEST["id"])) {
37504fd306cSNickeau            $_REQUEST["id"] = $targetPageId;
37604fd306cSNickeau        }
37704fd306cSNickeau        if (isset($_GET["id"])) {
37804fd306cSNickeau            $_GET["id"] = $targetPageId;
37904fd306cSNickeau        }
3804cadd4f8SNickeau
381c3437056SNickeau        /**
3824cadd4f8SNickeau         * Refresh the $INFO data
3834cadd4f8SNickeau         *
3844cadd4f8SNickeau         * the info attributes are used elsewhere
3854cadd4f8SNickeau         *   'id': for the sidebar
3864cadd4f8SNickeau         *   'exist' : for the meta robot = noindex,follow, see {@link tpl_metaheaders()}
3874cadd4f8SNickeau         *   'rev' : for the edit button to be sure that the page is still the same
388c3437056SNickeau         */
3894cadd4f8SNickeau        $INFO = pageinfo();
390c3437056SNickeau
391c3437056SNickeau        /**
392c3437056SNickeau         * Not compatible with
393c3437056SNickeau         * https://www.dokuwiki.org/config:send404 is enabled
394c3437056SNickeau         *
395c3437056SNickeau         * This check happens before that dokuwiki is started
396c3437056SNickeau         * and send an header in doku.php
397c3437056SNickeau         *
398c3437056SNickeau         * We send a warning
399c3437056SNickeau         */
400c3437056SNickeau        global $conf;
401*45a874f4SNico        if ($conf['send404']) {
402c3437056SNickeau            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);
403c3437056SNickeau        }
404c3437056SNickeau
405c3437056SNickeau        // Redirection
406*45a874f4SNico        $this->logRedirection($redirection);
407c3437056SNickeau
408c3437056SNickeau    }
409c3437056SNickeau
410c3437056SNickeau
411c3437056SNickeau    /**
412c3437056SNickeau     * The general HTTP Redirect method to an internal page
413c3437056SNickeau     * where the redirection method decide which type of redirection
414*45a874f4SNico     * @throws ExceptionCompile - if any error
415c3437056SNickeau     */
416c3437056SNickeau    private
417*45a874f4SNico    function executeHttpRedirect(RouterRedirection $redirection): void
418c3437056SNickeau    {
419c3437056SNickeau
420c3437056SNickeau
421c3437056SNickeau        // Log the redirections
422*45a874f4SNico        $this->logRedirection($redirection);
423c3437056SNickeau
424c3437056SNickeau
425*45a874f4SNico        $targetUrl = $redirection->getTargetUrl();
42604fd306cSNickeau
427*45a874f4SNico        if ($targetUrl !== null) {
428c3437056SNickeau
429c3437056SNickeau            // defend against HTTP Response Splitting
430c3437056SNickeau            // https://owasp.org/www-community/attacks/HTTP_Response_Splitting
431*45a874f4SNico            $targetUrl = stripctl($targetUrl->toAbsoluteUrlString());
432*45a874f4SNico
433c3437056SNickeau
434c3437056SNickeau        } else {
435c3437056SNickeau
436*45a874f4SNico            global $ID;
437c3437056SNickeau
438c3437056SNickeau            // if this is search engine redirect
439*45a874f4SNico            $url = UrlEndpoint::createDokuUrl();
440*45a874f4SNico            switch ($redirection->getOrigin()) {
441*45a874f4SNico                case RouterRedirection::TARGET_ORIGIN_SEARCH_ENGINE:
442*45a874f4SNico                {
443c3437056SNickeau                    $replacementPart = array(':', '_', '-');
444c3437056SNickeau                    $query = str_replace($replacementPart, ' ', $ID);
44511d09b86Sgerardnico                    $url->setQueryParameter(ExecutionContext::DO_ATTRIBUTE, ExecutionContext::SEARCH_ACTION);
44611d09b86Sgerardnico                    $url->setQueryParameter("q", $query);
447*45a874f4SNico                    $url->setQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $ID);
448*45a874f4SNico                    break;
449*45a874f4SNico                }
450*45a874f4SNico                default:
451*45a874f4SNico
452*45a874f4SNico                    $markupPath = $redirection->getTargetMarkupPath();
453*45a874f4SNico                    if ($markupPath == null) {
454*45a874f4SNico                        // should not happen (Both may be null but only on edit mode)
455*45a874f4SNico                        throw new ExceptionCompile("Internal Error When executing a http redirect, the URL or the wiki page should not be null");
456*45a874f4SNico                    }
457*45a874f4SNico                    $url->setQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $markupPath->toAbsoluteId());
458*45a874f4SNico
459*45a874f4SNico
460c3437056SNickeau            }
461c3437056SNickeau
4625b0932efSgerardnico            /**
4635b0932efSgerardnico             * Doing a permanent redirect with a added query string
4645b0932efSgerardnico             * create a new page url on the search engine
4655b0932efSgerardnico             *
4665b0932efSgerardnico             * ie
4675b0932efSgerardnico             * http://host/page
4685b0932efSgerardnico             * is not the same
4695b0932efSgerardnico             * than
4705b0932efSgerardnico             * http://host/page?whatever
4715b0932efSgerardnico             *
4725b0932efSgerardnico             * We can't pass query string otherwise, we get
47349b8fb24Sgerardnico             * the SEO warning / error
4745b0932efSgerardnico             * `Alternative page with proper canonical tag`
47511d09b86Sgerardnico             *
47611d09b86Sgerardnico             * Use HTTP X header for debug
4775b0932efSgerardnico             */
478*45a874f4SNico            if ($redirection->getType() !== RouterRedirection::REDIRECT_PERMANENT_METHOD) {
47911d09b86Sgerardnico                $url->setQueryParameter(action_plugin_combo_routermessage::ORIGIN_PAGE, $ID);
480*45a874f4SNico                $url->setQueryParameter(action_plugin_combo_routermessage::ORIGIN_TYPE, $redirection->getOrigin());
4815b0932efSgerardnico            }
4825b0932efSgerardnico
483*45a874f4SNico
48411d09b86Sgerardnico            $targetUrl = $url->toAbsoluteUrlString();
485c3437056SNickeau
486*45a874f4SNico
487*45a874f4SNico        }
488*45a874f4SNico
489*45a874f4SNico
490*45a874f4SNico        /**
491*45a874f4SNico         * Check that we are not redirecting to the same URL
492*45a874f4SNico         * to avoid the TOO_MANY_REDIRECT error
493*45a874f4SNico         */
494*45a874f4SNico        $requestURL = Url::createFromString($_SERVER['REQUEST_URI'])->toAbsoluteUrlString();
495*45a874f4SNico        if ($requestURL === $targetUrl) {
496*45a874f4SNico            throw new ExceptionCompile("A redirection should not redirect to the requested URL. Redirection Origin: {$redirection->getOrigin()}, Redirection URL:{$targetUrl} ");
497c3437056SNickeau        }
498c3437056SNickeau
499c3437056SNickeau        /**
500c3437056SNickeau         * The dokuwiki function {@link send_redirect()}
501c3437056SNickeau         * set the `Location header` and in php, the header function
502c3437056SNickeau         * in this case change the status code to 302 Arghhhh.
503c3437056SNickeau         * The code below is adapted from this function {@link send_redirect()}
504c3437056SNickeau         */
505c3437056SNickeau        global $MSG; // are there any undisplayed messages? keep them in session for display
506c3437056SNickeau        if (isset($MSG) && count($MSG) && !defined('NOSESSION')) {
507c3437056SNickeau            //reopen session, store data and close session again
508c3437056SNickeau            @session_start();
509c3437056SNickeau            $_SESSION[DOKU_COOKIE]['msg'] = $MSG;
510c3437056SNickeau        }
511c3437056SNickeau        session_write_close(); // always close the session
512c3437056SNickeau
513*45a874f4SNico        switch ($redirection->getType()) {
5145b0932efSgerardnico
515*45a874f4SNico            case RouterRedirection::REDIRECT_PERMANENT_METHOD:
51604fd306cSNickeau                ExecutionContext::getActualOrCreateFromEnv()
51704fd306cSNickeau                    ->response()
51804fd306cSNickeau                    ->setStatus(HttpResponseStatus::PERMANENT_REDIRECT)
519c3437056SNickeau                    ->addHeader(self::LOCATION_HEADER_PREFIX . $targetUrl)
52004fd306cSNickeau                    ->end();
521*45a874f4SNico                return;
5225b0932efSgerardnico
523*45a874f4SNico            case RouterRedirection::REDIRECT_NOTFOUND_METHOD:
5245b0932efSgerardnico
525c3437056SNickeau                // Empty 404 body to not get the standard 404 page of the browser
526c3437056SNickeau                // but a blank page to avoid a sort of FOUC.
527c3437056SNickeau                // ie the user see a page briefly
52804fd306cSNickeau                ExecutionContext::getActualOrCreateFromEnv()
52904fd306cSNickeau                    ->response()
53004fd306cSNickeau                    ->setStatus(HttpResponseStatus::NOT_FOUND)
531c3437056SNickeau                    ->addHeader(self::REFRESH_HEADER_PREFIX . $targetUrl)
53204fd306cSNickeau                    ->setBody(self::PAGE_404, Mime::getHtml())
53304fd306cSNickeau                    ->end();
534*45a874f4SNico                return;
535c3437056SNickeau
536c3437056SNickeau            default:
537*45a874f4SNico                throw new ExceptionCompile("The type ({$redirection->getType()}) is not an http redirection");
538c3437056SNickeau
539c3437056SNickeau        }
540c3437056SNickeau
541c3437056SNickeau
542c3437056SNickeau    }
543c3437056SNickeau
544c3437056SNickeau
545c3437056SNickeau    /**
546c3437056SNickeau     *
547c3437056SNickeau     *   * For a conf file, it will update the Redirection Action Data as Referrer, Count Of Redirection, Redirection Date
548c3437056SNickeau     *   * For a SQlite database, it will add a row into the log
549c3437056SNickeau     *
550c3437056SNickeau     * @param string $sourcePageId
551c3437056SNickeau     * @param $targetPageId
552c3437056SNickeau     * @param $algorithmic
553c3437056SNickeau     * @param $method - http or rewrite
554c3437056SNickeau     */
555*45a874f4SNico    function logRedirection(RouterRedirection $redirection)
556c3437056SNickeau    {
557*45a874f4SNico        global $ID;
558c3437056SNickeau
559c3437056SNickeau        $row = array(
560c3437056SNickeau            "TIMESTAMP" => date("c"),
561*45a874f4SNico            "SOURCE" => $ID,
562*45a874f4SNico            "TARGET" => $redirection->getTargetAsString(),
56370bbd7f1Sgerardnico            "REFERRER" => $_SERVER['HTTP_REFERER'] ?? null,
564*45a874f4SNico            "TYPE" => $redirection->getOrigin(),
565*45a874f4SNico            "METHOD" => $redirection->getType()
566c3437056SNickeau        );
567*45a874f4SNico        try {
568c3437056SNickeau            $request = Sqlite::createOrGetBackendSqlite()
569c3437056SNickeau                ->createRequest()
570c3437056SNickeau                ->setTableRow('redirections_log', $row);
571*45a874f4SNico        } catch (ExceptionSqliteNotAvailable $e) {
572*45a874f4SNico            return;
573*45a874f4SNico        }
574c3437056SNickeau        try {
575c3437056SNickeau            $request
576c3437056SNickeau                ->execute();
57704fd306cSNickeau        } catch (ExceptionCompile $e) {
578c3437056SNickeau            LogUtility::msg("Redirection Log Insert Error. {$e->getMessage()}");
579c3437056SNickeau        } finally {
580c3437056SNickeau            $request->close();
581c3437056SNickeau        }
582c3437056SNickeau
583c3437056SNickeau
584c3437056SNickeau    }
585c3437056SNickeau
586c3437056SNickeau
587c3437056SNickeau}
588