xref: /plugin/combo/action/router.php (revision 868673977bbaa373851cc7aa60bcec1f03ef97d4)
1c3437056SNickeau<?php
2c3437056SNickeau
3c3437056SNickeaurequire_once(__DIR__ . '/../ComboStrap/PluginUtility.php');
4c3437056SNickeau
5c3437056SNickeau
6c3437056SNickeauuse ComboStrap\Alias;
7c3437056SNickeauuse ComboStrap\AliasType;
8c3437056SNickeauuse ComboStrap\DatabasePageRow;
9c3437056SNickeauuse ComboStrap\DokuPath;
10c3437056SNickeauuse ComboStrap\ExceptionCombo;
11c3437056SNickeauuse ComboStrap\HttpResponse;
12c3437056SNickeauuse ComboStrap\Identity;
13c3437056SNickeauuse ComboStrap\LogUtility;
14c3437056SNickeauuse ComboStrap\Mime;
15c3437056SNickeauuse ComboStrap\Page;
16c3437056SNickeauuse ComboStrap\PageId;
17c3437056SNickeauuse ComboStrap\PageRules;
18c3437056SNickeauuse ComboStrap\PageUrlPath;
19c3437056SNickeauuse ComboStrap\PluginUtility;
20c3437056SNickeauuse ComboStrap\Site;
21c3437056SNickeauuse ComboStrap\Sqlite;
22c3437056SNickeauuse ComboStrap\Url;
23c3437056SNickeauuse ComboStrap\UrlManagerBestEndPage;
24c3437056SNickeauuse ComboStrap\PageUrlType;
25c3437056SNickeau
26c3437056SNickeau
27c3437056SNickeau/**
28c3437056SNickeau * Class action_plugin_combo_url
29c3437056SNickeau *
30c3437056SNickeau * The actual URL manager
31c3437056SNickeau *
32c3437056SNickeau *
33c3437056SNickeau */
34c3437056SNickeauclass action_plugin_combo_router extends DokuWiki_Action_Plugin
35c3437056SNickeau{
36c3437056SNickeau
37c3437056SNickeau    /**
38c3437056SNickeau     * @deprecated
39c3437056SNickeau     */
40c3437056SNickeau    const URL_MANAGER_ENABLE_CONF = "enableUrlManager";
41c3437056SNickeau    const ROUTER_ENABLE_CONF = "enableRouter";
42c3437056SNickeau
43c3437056SNickeau    // The redirect type
44c3437056SNickeau    const REDIRECT_TRANSPARENT_METHOD = 'transparent'; // was (Id)
45c3437056SNickeau    // For permanent, see https://developers.google.com/search/docs/advanced/crawling/301-redirects
46c3437056SNickeau    const REDIRECT_PERMANENT_METHOD = 'permanent'; // was `Http` (301)
47c3437056SNickeau    const REDIRECT_NOTFOUND_METHOD = "notfound"; // 404 (See other) (when best page name is calculated)
48c3437056SNickeau
49c3437056SNickeau    public const PERMANENT_REDIRECT_CANONICAL = "permanent:redirect";
50c3437056SNickeau
51c3437056SNickeau    // Where the target id value comes from
52c3437056SNickeau    const TARGET_ORIGIN_WELL_KNOWN = 'well-known';
53c3437056SNickeau    const TARGET_ORIGIN_PAGE_RULES = 'pageRules';
54c3437056SNickeau    /**
55c3437056SNickeau     * Named Permalink (canonical)
56c3437056SNickeau     */
57c3437056SNickeau    const TARGET_ORIGIN_CANONICAL = 'canonical';
58c3437056SNickeau    const TARGET_ORIGIN_ALIAS = 'alias';
59c3437056SNickeau    /**
60c3437056SNickeau     * Identifier Permalink (full page id)
61c3437056SNickeau     */
62c3437056SNickeau    const TARGET_ORIGIN_PERMALINK = "permalink";
63c3437056SNickeau    /**
64c3437056SNickeau     * Extended Permalink (abbreviated page id at the end)
65c3437056SNickeau     */
66c3437056SNickeau    const TARGET_ORIGIN_PERMALINK_EXTENDED = "extendedPermalink";
67c3437056SNickeau    const TARGET_ORIGIN_START_PAGE = 'startPage';
68c3437056SNickeau    const TARGET_ORIGIN_BEST_PAGE_NAME = 'bestPageName';
69c3437056SNickeau    const TARGET_ORIGIN_BEST_NAMESPACE = 'bestNamespace';
70c3437056SNickeau    const TARGET_ORIGIN_SEARCH_ENGINE = 'searchEngine';
71c3437056SNickeau    const TARGET_ORIGIN_BEST_END_PAGE_NAME = 'bestEndPageName';
72c3437056SNickeau    const TARGET_ORIGIN_SHADOW_BANNED = "shadowBanned";
73c3437056SNickeau
74c3437056SNickeau
75c3437056SNickeau    // The constant parameters
76c3437056SNickeau    const GO_TO_SEARCH_ENGINE = 'GoToSearchEngine';
77c3437056SNickeau    const GO_TO_BEST_NAMESPACE = 'GoToBestNamespace';
78c3437056SNickeau    const GO_TO_BEST_PAGE_NAME = 'GoToBestPageName';
79c3437056SNickeau    const GO_TO_BEST_END_PAGE_NAME = 'GoToBestEndPageName';
80c3437056SNickeau    const GO_TO_NS_START_PAGE = 'GoToNsStartPage';
81c3437056SNickeau    const GO_TO_EDIT_MODE = 'GoToEditMode';
82c3437056SNickeau    const NOTHING = 'Nothing';
83c3437056SNickeau
84c3437056SNickeau    /** @var string - a name used in log and other places */
85c3437056SNickeau    const NAME = 'Url Manager';
86c3437056SNickeau    const CANONICAL = 'router';
87c3437056SNickeau    const PAGE_404 = "<html lang=\"en\"><body></body></html>";
88c3437056SNickeau    const REFRESH_HEADER_NAME = "Refresh";
89c3437056SNickeau    const REFRESH_HEADER_PREFIX = self::REFRESH_HEADER_NAME . ': 0;url=';
90c3437056SNickeau    const LOCATION_HEADER_NAME = "Location";
91c3437056SNickeau    const LOCATION_HEADER_PREFIX = self::LOCATION_HEADER_NAME . ": ";
92c3437056SNickeau    public const URL_MANAGER_NAME = "Router";
93c3437056SNickeau
94c3437056SNickeau
95c3437056SNickeau    /**
96c3437056SNickeau     * @var PageRules
97c3437056SNickeau     */
98c3437056SNickeau    private $pageRules;
99c3437056SNickeau
100c3437056SNickeau
101c3437056SNickeau    function __construct()
102c3437056SNickeau    {
103c3437056SNickeau        // enable direct access to language strings
104c3437056SNickeau        // ie $this->lang
105c3437056SNickeau        $this->setupLocale();
106c3437056SNickeau
107c3437056SNickeau    }
108c3437056SNickeau
109c3437056SNickeau    /**
110c3437056SNickeau     * @param $refreshHeader
111c3437056SNickeau     * @return false|string
112c3437056SNickeau     */
113c3437056SNickeau    public static function getUrlFromRefresh($refreshHeader)
114c3437056SNickeau    {
115c3437056SNickeau        return substr($refreshHeader, strlen(action_plugin_combo_router::REFRESH_HEADER_PREFIX));
116c3437056SNickeau    }
117c3437056SNickeau
118c3437056SNickeau    public static function getUrlFromLocation($refreshHeader)
119c3437056SNickeau    {
120c3437056SNickeau        return substr($refreshHeader, strlen(action_plugin_combo_router::LOCATION_HEADER_PREFIX));
121c3437056SNickeau    }
122c3437056SNickeau
123c3437056SNickeau    /**
124c3437056SNickeau     * @return array|mixed|string|string[]
125c3437056SNickeau     *
126c3437056SNickeau     * Unfortunately, DOKUWIKI_STARTED is not the first event
127c3437056SNickeau     * The id may have been changed by
128c3437056SNickeau     * {@link action_plugin_combo_metalang::load_lang()}
129c3437056SNickeau     * function, that's why we have this function
130c3437056SNickeau     * to get the original requested id
131c3437056SNickeau     */
132c3437056SNickeau    private static function getOriginalIdFromRequest()
133c3437056SNickeau    {
134c3437056SNickeau        $originalId = $_GET["id"];
135c3437056SNickeau        return str_replace("/", DokuPath::PATH_SEPARATOR, $originalId);
136c3437056SNickeau    }
137c3437056SNickeau
138c3437056SNickeau    /**
139c3437056SNickeau     * Determine if the request should be banned based on the id
140c3437056SNickeau     *
141c3437056SNickeau     * @param string $id
142c3437056SNickeau     * @return bool
143c3437056SNickeau     *
144c3437056SNickeau     * See also {@link https://perishablepress.com/7g-firewall/#features}
145c3437056SNickeau     * for blocking rules on http request data such as:
146c3437056SNickeau     *   * query_string
147c3437056SNickeau     *   * user_agent,
148c3437056SNickeau     *   * remote host
149c3437056SNickeau     */
150c3437056SNickeau    public static function isShadowBanned(string $id): bool
151c3437056SNickeau    {
152c3437056SNickeau        /**
153c3437056SNickeau         * ie
154c3437056SNickeau         * wp-json:api:flutter_woo:config_file
155c3437056SNickeau         * wp-content:plugins:wpdiscuz:themes:default:style-rtl.css
156c3437056SNickeau         * wp-admin
157c3437056SNickeau         * 2020:wp-includes:wlwmanifest.xml
158c3437056SNickeau         * wp-content:start
159c3437056SNickeau         * wp-admin:css:start
160c3437056SNickeau         * sito:wp-includes:wlwmanifest.xml
161c3437056SNickeau         * site:wp-includes:wlwmanifest.xml
162c3437056SNickeau         * cms:wp-includes:wlwmanifest.xml
163c3437056SNickeau         * test:wp-includes:wlwmanifest.xml
164c3437056SNickeau         * media:wp-includes:wlwmanifest.xml
165c3437056SNickeau         * wp2:wp-includes:wlwmanifest.xml
166c3437056SNickeau         * 2019:wp-includes:wlwmanifest.xml
167c3437056SNickeau         * shop:wp-includes:wlwmanifest.xml
168c3437056SNickeau         * wp1:wp-includes:wlwmanifest.xml
169c3437056SNickeau         * news:wp-includes:wlwmanifest.xml
170c3437056SNickeau         * 2018:wp-includes:wlwmanifest.xml
171c3437056SNickeau         */
172c3437056SNickeau        if (strpos($id, 'wp-') !== false) {
173c3437056SNickeau            return true;
174c3437056SNickeau        }
175c3437056SNickeau
176c3437056SNickeau        /**
177c3437056SNickeau         * 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
178c3437056SNickeau         * db:oracle:999999.9:union:all:select_null:from_dual
179c3437056SNickeau         * db:oracle:999999.9:union:all:select_null:from_dual_and_0_0
180c3437056SNickeau         */
181c3437056SNickeau        if (preg_match('/_chr_|_0_0/', $id) === 1) {
182c3437056SNickeau            return true;
183c3437056SNickeau        }
184c3437056SNickeau
185c3437056SNickeau
186c3437056SNickeau        /**
187c3437056SNickeau         * ie
188c3437056SNickeau         * git:objects:
189c3437056SNickeau         * git:refs:heads:stable
190c3437056SNickeau         * git:logs:refs:heads:main
191c3437056SNickeau         * git:logs:refs:heads:stable
192c3437056SNickeau         * git:hooks:pre-push.sample
193c3437056SNickeau         * git:hooks:pre-receive.sample
194c3437056SNickeau         */
195c3437056SNickeau        if (strpos($id, "git:") === 0) {
196c3437056SNickeau            return true;
197c3437056SNickeau        }
198c3437056SNickeau
199c3437056SNickeau        return false;
200c3437056SNickeau
201c3437056SNickeau    }
202c3437056SNickeau
203c3437056SNickeau    /**
204c3437056SNickeau     * @param string $id
205c3437056SNickeau     * @return bool
206c3437056SNickeau     * well-known:traffic-advice = https://github.com/buettner/private-prefetch-proxy/blob/main/traffic-advice.md
207c3437056SNickeau     * .well-known/security.txt, id=well-known:security.txt = https://securitytxt.org/
208c3437056SNickeau     * well-known:dnt-policy.txt
209c3437056SNickeau     */
210c3437056SNickeau    public static function isWellKnownFile(string $id): bool
211c3437056SNickeau    {
212c3437056SNickeau        return strpos($id, "well-known") === 0;
213c3437056SNickeau    }
214c3437056SNickeau
215c3437056SNickeau
216c3437056SNickeau    function register(Doku_Event_Handler $controller)
217c3437056SNickeau    {
218c3437056SNickeau
219c3437056SNickeau        if (PluginUtility::getConfValue(self::ROUTER_ENABLE_CONF, 1)) {
220c3437056SNickeau            /**
221c3437056SNickeau             * This will call the function {@link action_plugin_combo_router::_router()}
222c3437056SNickeau             * The event is not DOKUWIKI_STARTED because this is not the first one
223c3437056SNickeau             *
224c3437056SNickeau             * https://www.dokuwiki.org/devel:event:init_lang_load
225c3437056SNickeau             */
226c3437056SNickeau            $controller->register_hook('DOKUWIKI_STARTED',
227c3437056SNickeau                'AFTER',
228c3437056SNickeau                $this,
229c3437056SNickeau                'router',
230c3437056SNickeau                array());
231c3437056SNickeau
232c3437056SNickeau            /**
233c3437056SNickeau             * This is the real first call of Dokuwiki
234c3437056SNickeau             * Unfortunately, it does not create the environment
235c3437056SNickeau             * We just ban to spare server resources
236c3437056SNickeau             *
237c3437056SNickeau             * https://www.dokuwiki.org/devel:event:init_lang_load
238c3437056SNickeau             */
239c3437056SNickeau            $controller->register_hook('INIT_LANG_LOAD', 'BEFORE', $this, 'ban', array());
240c3437056SNickeau
241c3437056SNickeau        }
242c3437056SNickeau
243c3437056SNickeau
244c3437056SNickeau    }
245c3437056SNickeau
246c3437056SNickeau    /**
247c3437056SNickeau     *
248c3437056SNickeau     * We have created a spacial ban function that is
249c3437056SNickeau     * called before the first function
250c3437056SNickeau     * {@link action_plugin_combo_metalang::load_lang()}
251c3437056SNickeau     * to spare CPU.
252c3437056SNickeau     *
253c3437056SNickeau     * @param $event
254c3437056SNickeau     * @throws Exception
255c3437056SNickeau     */
256c3437056SNickeau    function ban(&$event)
257c3437056SNickeau    {
258c3437056SNickeau
259c3437056SNickeau        $id = self::getOriginalIdFromRequest();
260c3437056SNickeau        $page = Page::createPageFromId($id);
261c3437056SNickeau        if (!$page->exists()) {
262c3437056SNickeau            // Well known
263c3437056SNickeau            if (self::isWellKnownFile($id)) {
264c3437056SNickeau                $this->logRedirection($id, "", self::TARGET_ORIGIN_WELL_KNOWN, self::REDIRECT_NOTFOUND_METHOD);
265c3437056SNickeau                HttpResponse::create(HttpResponse::STATUS_NOT_FOUND)
266c3437056SNickeau                    ->send();
267c3437056SNickeau                return;
268c3437056SNickeau            }
269c3437056SNickeau
270c3437056SNickeau            // Shadow banned
271c3437056SNickeau            if (self::isShadowBanned($id)) {
272c3437056SNickeau                $webSiteHomePage = Site::getHomePageName();
273c3437056SNickeau                $this->executeTransparentRedirect($webSiteHomePage, self::TARGET_ORIGIN_SHADOW_BANNED);
274c3437056SNickeau            }
275c3437056SNickeau        }
276c3437056SNickeau    }
277c3437056SNickeau
278c3437056SNickeau    /**
279c3437056SNickeau     * @param $event Doku_Event
280c3437056SNickeau     * @param $param
281c3437056SNickeau     * @return void
282c3437056SNickeau     * @throws Exception
283c3437056SNickeau     */
284c3437056SNickeau    function router(&$event, $param)
285c3437056SNickeau    {
286c3437056SNickeau
287c3437056SNickeau        global $ACT;
288c3437056SNickeau        if ($ACT !== 'show') return;
289c3437056SNickeau
290c3437056SNickeau
291c3437056SNickeau        global $ID;
292c3437056SNickeau
293c3437056SNickeau        /**
294c3437056SNickeau         * Without SQLite, this module does not work further
295c3437056SNickeau         */
296c3437056SNickeau        $sqlite = Sqlite::createOrGetSqlite();
297c3437056SNickeau        if ($sqlite == null) {
298c3437056SNickeau            return;
299c3437056SNickeau        } else {
300c3437056SNickeau            $this->pageRules = new PageRules();
301c3437056SNickeau        }
302c3437056SNickeau
303c3437056SNickeau        /**
304c3437056SNickeau         * Unfortunately, DOKUWIKI_STARTED is not the first event
305c3437056SNickeau         * The id may have been changed by
306c3437056SNickeau         * {@link action_plugin_combo_metalang::load_lang()}
307c3437056SNickeau         * function, that's why we check against the {@link $_REQUEST}
308c3437056SNickeau         * and not the global ID
309c3437056SNickeau         */
310c3437056SNickeau        $originalId = self::getOriginalIdFromRequest();
311c3437056SNickeau
312c3437056SNickeau        /**
313c3437056SNickeau         * Page is an existing id ?
314c3437056SNickeau         */
315c3437056SNickeau        $targetPage = Page::createPageFromId($ID);
316c3437056SNickeau        if ($targetPage->exists()) {
317c3437056SNickeau
318c3437056SNickeau            /**
319c3437056SNickeau             * If this is not the root home page
320c3437056SNickeau             * and if the canonical id is the not the same,
321c3437056SNickeau             * and if this is not a historical page (revision)
322c3437056SNickeau             * redirect
323c3437056SNickeau             */
324c3437056SNickeau            if (
325c3437056SNickeau                $originalId !== $targetPage->getUrlId() // The id may have been changed
326c3437056SNickeau                && $ID != Site::getHomePageName()
327c3437056SNickeau                && !isset($_REQUEST["rev"])
328c3437056SNickeau            ) {
329c3437056SNickeau                $this->executePermanentRedirect(
330c3437056SNickeau                    $targetPage->getCanonicalUrl([], true),
331c3437056SNickeau                    self::TARGET_ORIGIN_PERMALINK_EXTENDED
332c3437056SNickeau                );
333c3437056SNickeau            }
334c3437056SNickeau            return;
335c3437056SNickeau        }
336c3437056SNickeau
337c3437056SNickeau
338c3437056SNickeau        $identifier = $ID;
339c3437056SNickeau
340c3437056SNickeau
341c3437056SNickeau        /**
342c3437056SNickeau         * Page Id Website / root Permalink ?
343c3437056SNickeau         */
344c3437056SNickeau        $shortPageId = PageUrlPath::getShortEncodedPageIdFromUrlId($targetPage->getPath()->getLastName());
345c3437056SNickeau        if ($shortPageId !== null) {
346c3437056SNickeau            $pageId = PageUrlPath::decodePageId($shortPageId);
347c3437056SNickeau            if ($targetPage->getParentPage() === null && $pageId !== null) {
348c3437056SNickeau                $page = DatabasePageRow::createFromPageId($pageId)->getPage();
349c3437056SNickeau                if ($page !== null && $page->exists()) {
350c3437056SNickeau                    $this->executePermanentRedirect($page->getCanonicalUrl(), self::TARGET_ORIGIN_PERMALINK);
351c3437056SNickeau                }
352c3437056SNickeau            }
353c3437056SNickeau
354c3437056SNickeau            /**
355c3437056SNickeau             * Page Id Abbr ?
356c3437056SNickeau             * {@link PageUrlType::CONF_CANONICAL_URL_TYPE}
357c3437056SNickeau             */
358c3437056SNickeau            if (
359c3437056SNickeau                $pageId !== null
360c3437056SNickeau            ) {
361c3437056SNickeau                $page = DatabasePageRow::createFromPageIdAbbr($pageId)->getPage();
362c3437056SNickeau                if ($page === null) {
363c3437056SNickeau                    // or the length of the abbr has changed
364c3437056SNickeau                    $databasePage = new DatabasePageRow();
365c3437056SNickeau                    $row = $databasePage->getDatabaseRowFromAttribute("substr(" . PageId::PROPERTY_NAME . ", 1, " . strlen($pageId) . ")", $pageId);
366c3437056SNickeau                    if ($row != null) {
367c3437056SNickeau                        $databasePage->setRow($row);
368c3437056SNickeau                        $page = $databasePage->getPage();
369c3437056SNickeau                    }
370c3437056SNickeau                }
371c3437056SNickeau                if ($page !== null && $page->exists()) {
372c3437056SNickeau                    /**
373c3437056SNickeau                     * If the url canonical id has changed, we show it
374c3437056SNickeau                     * to the writer by performing a permanent redirect
375c3437056SNickeau                     */
376c3437056SNickeau                    if ($identifier != $page->getUrlId()) {
377c3437056SNickeau                        // Google asks for a redirect
378c3437056SNickeau                        // https://developers.google.com/search/docs/advanced/crawling/301-redirects
379c3437056SNickeau                        // People access your site through several different URLs.
380c3437056SNickeau                        // If, for example, your home page can be reached in multiple ways
381c3437056SNickeau                        // (for instance, http://example.com/home, http://home.example.com, or http://www.example.com),
382c3437056SNickeau                        // it's a good idea to pick one of those URLs as your preferred (canonical) destination,
383c3437056SNickeau                        // and use redirects to send traffic from the other URLs to your preferred URL.
384c3437056SNickeau                        $this->executePermanentRedirect(
385c3437056SNickeau                            $page->getCanonicalUrl([], true),
386c3437056SNickeau                            self::TARGET_ORIGIN_PERMALINK_EXTENDED
387c3437056SNickeau                        );
388c3437056SNickeau                        return;
389c3437056SNickeau                    }
390c3437056SNickeau                    $this->executeTransparentRedirect($page->getDokuwikiId(), self::TARGET_ORIGIN_PERMALINK_EXTENDED);
391c3437056SNickeau                    return;
392c3437056SNickeau
393c3437056SNickeau                }
394c3437056SNickeau                // permanent url not yet in the database
395c3437056SNickeau                // Other permanent such as permanent canonical ?
396c3437056SNickeau                // We let the process go with the new identifier
397c3437056SNickeau
398c3437056SNickeau            }
399c3437056SNickeau
400c3437056SNickeau        }
401c3437056SNickeau
402c3437056SNickeau        // Global variable needed in the process
403c3437056SNickeau        global $conf;
404c3437056SNickeau
405c3437056SNickeau        /**
406c3437056SNickeau         * Identifier is a Canonical ?
407c3437056SNickeau         */
408c3437056SNickeau        $databasePage = DatabasePageRow::createFromCanonical($identifier);
409c3437056SNickeau        $targetPage = $databasePage->getPage();
410c3437056SNickeau        if ($targetPage !== null && $targetPage->exists()) {
411c3437056SNickeau            $res = $this->executePermanentRedirect($targetPage->getDokuwikiId(), self::TARGET_ORIGIN_CANONICAL);
412c3437056SNickeau            if ($res) {
413c3437056SNickeau                return;
414c3437056SNickeau            }
415c3437056SNickeau        }
416c3437056SNickeau
417c3437056SNickeau        /**
418c3437056SNickeau         * Identifier is an alias
419c3437056SNickeau         */
420c3437056SNickeau        $targetPage = DatabasePageRow::createFromAlias($identifier)->getPage();
421c3437056SNickeau        if (
422c3437056SNickeau            $targetPage !== null
423c3437056SNickeau            && $targetPage->exists()
424c3437056SNickeau            // The build alias is the file system metadata alias
425c3437056SNickeau            // it may be null if the replication in the database was not successful
426c3437056SNickeau            && $targetPage->getBuildAlias() !== null
427c3437056SNickeau        ) {
428c3437056SNickeau            $buildAlias = $targetPage->getBuildAlias();
429c3437056SNickeau            switch ($buildAlias->getType()) {
430c3437056SNickeau                case AliasType::REDIRECT:
431c3437056SNickeau                    $res = $this->executePermanentRedirect($targetPage->getCanonicalUrl(), self::TARGET_ORIGIN_ALIAS);
432c3437056SNickeau                    if ($res) {
433c3437056SNickeau                        return;
434c3437056SNickeau                    }
435c3437056SNickeau                    break;
436c3437056SNickeau                case AliasType::SYNONYM:
437c3437056SNickeau                    $res = $this->executeTransparentRedirect($targetPage->getDokuwikiId(), self::TARGET_ORIGIN_ALIAS);
438c3437056SNickeau                    if ($res) {
439c3437056SNickeau                        return;
440c3437056SNickeau                    }
441c3437056SNickeau                    break;
442c3437056SNickeau                default:
443c3437056SNickeau                    LogUtility::msg("The alias type ({$buildAlias->getType()}) is unknown. A permanent redirect was performed for the alias $identifier");
444c3437056SNickeau                    $res = $this->executePermanentRedirect($targetPage->getCanonicalUrl(), self::TARGET_ORIGIN_ALIAS);
445c3437056SNickeau                    if ($res) {
446c3437056SNickeau                        return;
447c3437056SNickeau                    }
448c3437056SNickeau                    break;
449c3437056SNickeau            }
450c3437056SNickeau        }
451c3437056SNickeau
452c3437056SNickeau
453c3437056SNickeau        // If there is a redirection defined in the page rules
454c3437056SNickeau        $result = $this->processingPageRules();
455c3437056SNickeau        if ($result) {
456c3437056SNickeau            // A redirection has occurred
457c3437056SNickeau            // finish the process
458c3437056SNickeau            return;
459c3437056SNickeau        }
460c3437056SNickeau
461c3437056SNickeau        /**
462c3437056SNickeau         *
463c3437056SNickeau         * There was no redirection found, redirect to edit mode if writer
464c3437056SNickeau         *
465c3437056SNickeau         */
466c3437056SNickeau        if (Identity::isWriter() && $this->getConf(self::GO_TO_EDIT_MODE) == 1) {
467c3437056SNickeau
468c3437056SNickeau            $this->gotToEditMode($event);
469c3437056SNickeau            // Stop here
470c3437056SNickeau            return;
471c3437056SNickeau
472c3437056SNickeau        }
473c3437056SNickeau
474c3437056SNickeau        /*
475c3437056SNickeau         *  We are still a reader, the redirection does not exist the user is not allowed to edit the page (public of other)
476c3437056SNickeau         */
477c3437056SNickeau        if ($this->getConf('ActionReaderFirst') == self::NOTHING) {
478c3437056SNickeau            return;
479c3437056SNickeau        }
480c3437056SNickeau
481c3437056SNickeau        // We are reader and their is no redirection set, we apply the algorithm
482c3437056SNickeau        $readerAlgorithms = array();
483c3437056SNickeau        $readerAlgorithms[0] = $this->getConf('ActionReaderFirst');
484c3437056SNickeau        $readerAlgorithms[1] = $this->getConf('ActionReaderSecond');
485c3437056SNickeau        $readerAlgorithms[2] = $this->getConf('ActionReaderThird');
486c3437056SNickeau
487c3437056SNickeau        while (
488c3437056SNickeau            ($algorithm = array_shift($readerAlgorithms)) != null
489c3437056SNickeau        ) {
490c3437056SNickeau
491c3437056SNickeau            switch ($algorithm) {
492c3437056SNickeau
493c3437056SNickeau                case self::NOTHING:
494c3437056SNickeau                    return;
495c3437056SNickeau
496c3437056SNickeau                case self::GO_TO_BEST_END_PAGE_NAME:
497c3437056SNickeau
498c3437056SNickeau                    list($targetPage, $method) = UrlManagerBestEndPage::process($identifier);
499c3437056SNickeau                    if ($targetPage != null) {
500c3437056SNickeau                        $res = false;
501c3437056SNickeau                        switch ($method) {
502c3437056SNickeau                            case self::REDIRECT_PERMANENT_METHOD:
503c3437056SNickeau                                $res = $this->executePermanentRedirect($targetPage, self::TARGET_ORIGIN_BEST_END_PAGE_NAME);
504c3437056SNickeau                                break;
505c3437056SNickeau                            case self::REDIRECT_NOTFOUND_METHOD:
506c3437056SNickeau                                $res = $this->performNotFoundRedirect($targetPage, self::TARGET_ORIGIN_BEST_END_PAGE_NAME);
507c3437056SNickeau                                break;
508c3437056SNickeau                            default:
509c3437056SNickeau                                LogUtility::msg("This redirection method ($method) was not expected for the redirection algorithm ($algorithm)");
510c3437056SNickeau                        }
511c3437056SNickeau                        if ($res) {
512c3437056SNickeau                            // Redirection has succeeded
513c3437056SNickeau                            return;
514c3437056SNickeau                        }
515c3437056SNickeau                    }
516c3437056SNickeau                    break;
517c3437056SNickeau
518c3437056SNickeau                case self::GO_TO_NS_START_PAGE:
519c3437056SNickeau
520c3437056SNickeau                    // Start page with the conf['start'] parameter
521c3437056SNickeau                    $startPage = getNS($identifier) . ':' . $conf['start'];
522c3437056SNickeau                    if (page_exists($startPage)) {
523c3437056SNickeau                        $res = $this->performNotFoundRedirect($startPage, self::TARGET_ORIGIN_START_PAGE);
524c3437056SNickeau                        if ($res) {
525c3437056SNickeau                            return;
526c3437056SNickeau                        }
527c3437056SNickeau                    }
528c3437056SNickeau
529c3437056SNickeau                    // Start page with the same name than the namespace
530c3437056SNickeau                    $startPage = getNS($identifier) . ':' . curNS($identifier);
531c3437056SNickeau                    if (page_exists($startPage)) {
532c3437056SNickeau                        $res = $this->performNotFoundRedirect($startPage, self::TARGET_ORIGIN_START_PAGE);
533c3437056SNickeau                        if ($res) {
534c3437056SNickeau                            return;
535c3437056SNickeau                        }
536c3437056SNickeau                    }
537c3437056SNickeau                    break;
538c3437056SNickeau
539c3437056SNickeau                case self::GO_TO_BEST_PAGE_NAME:
540c3437056SNickeau
541c3437056SNickeau                    $bestPageId = null;
542c3437056SNickeau
543c3437056SNickeau                    $bestPage = $this->getBestPage($identifier);
544c3437056SNickeau                    $bestPageId = $bestPage['id'];
545c3437056SNickeau                    $scorePageName = $bestPage['score'];
546c3437056SNickeau
547c3437056SNickeau                    // Get Score from a Namespace
548c3437056SNickeau                    $bestNamespace = $this->scoreBestNamespace($identifier);
549c3437056SNickeau                    $bestNamespaceId = $bestNamespace['namespace'];
550c3437056SNickeau                    $namespaceScore = $bestNamespace['score'];
551c3437056SNickeau
552c3437056SNickeau                    // Compare the two score
553c3437056SNickeau                    if ($scorePageName > 0 or $namespaceScore > 0) {
554c3437056SNickeau                        if ($scorePageName > $namespaceScore) {
555c3437056SNickeau                            $this->performNotFoundRedirect($bestPageId, self::TARGET_ORIGIN_BEST_PAGE_NAME);
556c3437056SNickeau                        } else {
557c3437056SNickeau                            $this->performNotFoundRedirect($bestNamespaceId, self::TARGET_ORIGIN_BEST_PAGE_NAME);
558c3437056SNickeau                        }
559c3437056SNickeau                        return;
560c3437056SNickeau                    }
561c3437056SNickeau                    break;
562c3437056SNickeau
563c3437056SNickeau                case self::GO_TO_BEST_NAMESPACE:
564c3437056SNickeau
565c3437056SNickeau                    $scoreNamespace = $this->scoreBestNamespace($identifier);
566c3437056SNickeau                    $bestNamespaceId = $scoreNamespace['namespace'];
567c3437056SNickeau                    $score = $scoreNamespace['score'];
568c3437056SNickeau
569c3437056SNickeau                    if ($score > 0) {
570c3437056SNickeau                        $this->performNotFoundRedirect($bestNamespaceId, self::TARGET_ORIGIN_BEST_NAMESPACE);
571c3437056SNickeau                        return;
572c3437056SNickeau                    }
573c3437056SNickeau                    break;
574c3437056SNickeau
575c3437056SNickeau                case self::GO_TO_SEARCH_ENGINE:
576c3437056SNickeau
577c3437056SNickeau                    $this->redirectToSearchEngine();
578c3437056SNickeau
579c3437056SNickeau                    return;
580c3437056SNickeau                    break;
581c3437056SNickeau
582c3437056SNickeau                // End Switch Action
583c3437056SNickeau            }
584c3437056SNickeau
585c3437056SNickeau            // End While Action
586c3437056SNickeau        }
587c3437056SNickeau
588c3437056SNickeau
589c3437056SNickeau    }
590c3437056SNickeau
591c3437056SNickeau
592c3437056SNickeau    /**
593c3437056SNickeau     * getBestNamespace
594c3437056SNickeau     * Return a list with 'BestNamespaceId Score'
595c3437056SNickeau     * @param $id
596c3437056SNickeau     * @return array
597c3437056SNickeau     */
598c3437056SNickeau    private
599c3437056SNickeau    function scoreBestNamespace($id)
600c3437056SNickeau    {
601c3437056SNickeau
602c3437056SNickeau        global $conf;
603c3437056SNickeau
604c3437056SNickeau        // Parameters
605c3437056SNickeau        $pageNameSpace = getNS($id);
606c3437056SNickeau
607c3437056SNickeau        // If the page has an existing namespace start page take it, other search other namespace
608c3437056SNickeau        $startPageNameSpace = $pageNameSpace . ":";
609c3437056SNickeau        $dateAt = '';
610c3437056SNickeau        // $startPageNameSpace will get a full path (ie with start or the namespace
611c3437056SNickeau        resolve_pageid($pageNameSpace, $startPageNameSpace, $exists, $dateAt, true);
612c3437056SNickeau        if (page_exists($startPageNameSpace)) {
613c3437056SNickeau            $nameSpaces = array($startPageNameSpace);
614c3437056SNickeau        } else {
615c3437056SNickeau            $nameSpaces = ft_pageLookup($conf['start']);
616c3437056SNickeau        }
617c3437056SNickeau
618c3437056SNickeau        // Parameters and search the best namespace
619c3437056SNickeau        $pathNames = explode(':', $pageNameSpace);
620c3437056SNickeau        $bestNbWordFound = 0;
621c3437056SNickeau        $bestNamespaceId = '';
622c3437056SNickeau        foreach ($nameSpaces as $nameSpace) {
623c3437056SNickeau
624c3437056SNickeau            $nbWordFound = 0;
625c3437056SNickeau            foreach ($pathNames as $pathName) {
626c3437056SNickeau                if (strlen($pathName) > 2) {
627c3437056SNickeau                    $nbWordFound = $nbWordFound + substr_count($nameSpace, $pathName);
628c3437056SNickeau                }
629c3437056SNickeau            }
630c3437056SNickeau            if ($nbWordFound > $bestNbWordFound) {
631c3437056SNickeau                // Take only the smallest namespace
632c3437056SNickeau                if (strlen($nameSpace) < strlen($bestNamespaceId) or $nbWordFound > $bestNbWordFound) {
633c3437056SNickeau                    $bestNbWordFound = $nbWordFound;
634c3437056SNickeau                    $bestNamespaceId = $nameSpace;
635c3437056SNickeau                }
636c3437056SNickeau            }
637c3437056SNickeau        }
638c3437056SNickeau
639c3437056SNickeau        $startPageFactor = $this->getConf('WeightFactorForStartPage');
640c3437056SNickeau        $nameSpaceFactor = $this->getConf('WeightFactorForSameNamespace');
641c3437056SNickeau        if ($bestNbWordFound > 0) {
642c3437056SNickeau            $bestNamespaceScore = $bestNbWordFound * $nameSpaceFactor + $startPageFactor;
643c3437056SNickeau        } else {
644c3437056SNickeau            $bestNamespaceScore = 0;
645c3437056SNickeau        }
646c3437056SNickeau
647c3437056SNickeau
648c3437056SNickeau        return array(
649c3437056SNickeau            'namespace' => $bestNamespaceId,
650c3437056SNickeau            'score' => $bestNamespaceScore
651c3437056SNickeau        );
652c3437056SNickeau
653c3437056SNickeau    }
654c3437056SNickeau
655c3437056SNickeau    /**
656c3437056SNickeau     * @param $event
657c3437056SNickeau     */
658c3437056SNickeau    private
659c3437056SNickeau    function gotToEditMode(&$event)
660c3437056SNickeau    {
661c3437056SNickeau        global $ACT;
662c3437056SNickeau        $ACT = 'edit';
663c3437056SNickeau
664c3437056SNickeau    }
665c3437056SNickeau
666c3437056SNickeau
667c3437056SNickeau    /**
668c3437056SNickeau     * Redirect to an internal page ie:
669c3437056SNickeau     *   * on the same domain
670c3437056SNickeau     *   * no HTTP redirect
671c3437056SNickeau     *   * id rewrite
672c3437056SNickeau     * @param string $targetPageId - target page id
673c3437056SNickeau     * @param string $targetOriginId - the source of the target (redirect)
674c3437056SNickeau     * @return bool - return true if the user has the permission and that the redirect was done
675c3437056SNickeau     * @throws Exception
676c3437056SNickeau     */
677c3437056SNickeau    private
678c3437056SNickeau    function executeTransparentRedirect(string $targetPageId, string $targetOriginId): bool
679c3437056SNickeau    {
680c3437056SNickeau        /**
681c3437056SNickeau         * Because we set the ID globally for the ID redirect
682c3437056SNickeau         * we make sure that this is not a {@link Page}
683c3437056SNickeau         * object otherwise we got an error in the {@link \ComboStrap\AnalyticsMenuItem}
684c3437056SNickeau         * because the constructor takes it {@link \dokuwiki\Menu\Item\AbstractItem}
685c3437056SNickeau         */
686c3437056SNickeau        if (is_object($targetPageId)) {
687c3437056SNickeau            $class = get_class($targetPageId);
688c3437056SNickeau            LogUtility::msg("The parameters targetPageId ($targetPageId) is an object of the class ($class) and it should be a page id");
689c3437056SNickeau        }
690c3437056SNickeau
691c3437056SNickeau        if (is_object($targetOriginId)) {
692c3437056SNickeau            $class = get_class($targetOriginId);
693c3437056SNickeau            LogUtility::msg("The parameters targetOriginId ($targetOriginId) is an object of the class ($class) and it should be a page id");
694c3437056SNickeau        }
695c3437056SNickeau
696c3437056SNickeau        // If the user does not have the right to see the target page
697c3437056SNickeau        // don't do anything
698c3437056SNickeau        if (!(Identity::isReader($targetPageId))) {
699c3437056SNickeau            return false;
700c3437056SNickeau        }
701c3437056SNickeau
702c3437056SNickeau        // Change the id
703c3437056SNickeau        global $ID;
704c3437056SNickeau        global $INFO;
705c3437056SNickeau        $sourceId = $ID;
706c3437056SNickeau        $ID = $targetPageId;
707c3437056SNickeau        // Change the info id for the sidebar
708c3437056SNickeau        $INFO['id'] = $targetPageId;
709c3437056SNickeau        /**
710c3437056SNickeau         * otherwise there is:
711c3437056SNickeau         *   * a meta robot = noindex,follow
712c3437056SNickeau         * See {@link tpl_metaheaders()}
713c3437056SNickeau         */
714c3437056SNickeau        $INFO['exists'] = true;
715c3437056SNickeau
716c3437056SNickeau        /**
717c3437056SNickeau         * Not compatible with
718c3437056SNickeau         * https://www.dokuwiki.org/config:send404 is enabled
719c3437056SNickeau         *
720c3437056SNickeau         * This check happens before that dokuwiki is started
721c3437056SNickeau         * and send an header in doku.php
722c3437056SNickeau         *
723c3437056SNickeau         * We send a warning
724c3437056SNickeau         */
725c3437056SNickeau        global $conf;
726c3437056SNickeau        if ($conf['send404'] == true) {
727c3437056SNickeau            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);
728c3437056SNickeau        }
729c3437056SNickeau
730c3437056SNickeau        // Redirection
731c3437056SNickeau        $this->logRedirection($sourceId, $targetPageId, $targetOriginId, self::REDIRECT_TRANSPARENT_METHOD);
732c3437056SNickeau
733c3437056SNickeau        return true;
734c3437056SNickeau
735c3437056SNickeau    }
736c3437056SNickeau
737c3437056SNickeau    private function executePermanentRedirect(string $target, $targetOrigin): bool
738c3437056SNickeau    {
739c3437056SNickeau        return $this->executeHttpRedirect($target, $targetOrigin, self::REDIRECT_PERMANENT_METHOD);
740c3437056SNickeau    }
741c3437056SNickeau
742c3437056SNickeau    /**
743c3437056SNickeau     * The general HTTP Redirect method to an internal page
744c3437056SNickeau     * where the redirection method decide which type of redirection
745c3437056SNickeau     * @param string $target - a dokuwiki id or an url
746c3437056SNickeau     * @param string $targetOrigin - the origin of the target (the algorithm used to get the target origin)
747c3437056SNickeau     * @param string $method - the redirection method
748c3437056SNickeau     */
749c3437056SNickeau    private
750c3437056SNickeau    function executeHttpRedirect(string $target, string $targetOrigin, string $method): bool
751c3437056SNickeau    {
752c3437056SNickeau
753c3437056SNickeau        global $ID;
754c3437056SNickeau
755c3437056SNickeau
756c3437056SNickeau        // Log the redirections
757c3437056SNickeau        $this->logRedirection($ID, $target, $targetOrigin, $method);
758c3437056SNickeau
759c3437056SNickeau
760c3437056SNickeau        // An external url ?
761c3437056SNickeau        if (Url::isValidURL($target)) {
762c3437056SNickeau
763c3437056SNickeau            // defend against HTTP Response Splitting
764c3437056SNickeau            // https://owasp.org/www-community/attacks/HTTP_Response_Splitting
765c3437056SNickeau            $targetUrl = stripctl($target);
766c3437056SNickeau
767c3437056SNickeau        } else {
768c3437056SNickeau
769c3437056SNickeau
770c3437056SNickeau            // Explode the page ID and the anchor (#)
771c3437056SNickeau            $link = explode('#', $target, 2);
772c3437056SNickeau
773c3437056SNickeau            // Query String to pass the message
774c3437056SNickeau            $urlParams = [];
775c3437056SNickeau            if ($targetOrigin != self::TARGET_ORIGIN_PERMALINK) {
776c3437056SNickeau                $urlParams = array(
777c3437056SNickeau                    action_plugin_combo_routermessage::ORIGIN_PAGE => $ID,
778c3437056SNickeau                    action_plugin_combo_routermessage::ORIGIN_TYPE => $targetOrigin
779c3437056SNickeau                );
780c3437056SNickeau            }
781c3437056SNickeau
782c3437056SNickeau            // if this is search engine redirect
783c3437056SNickeau            if ($targetOrigin == self::TARGET_ORIGIN_SEARCH_ENGINE) {
784c3437056SNickeau                $replacementPart = array(':', '_', '-');
785c3437056SNickeau                $query = str_replace($replacementPart, ' ', $ID);
786c3437056SNickeau                $urlParams["do"] = "search";
787c3437056SNickeau                $urlParams["q"] = $query;
788c3437056SNickeau            }
789c3437056SNickeau
790c3437056SNickeau            $targetUrl = wl($link[0], $urlParams, true, '&');
791c3437056SNickeau            // %3A back to :
792c3437056SNickeau            $targetUrl = str_replace("%3A", ":", $targetUrl);
793c3437056SNickeau            if ($link[1]) {
794c3437056SNickeau                $targetUrl .= '#' . rawurlencode($link[1]);
795c3437056SNickeau            }
796c3437056SNickeau
797c3437056SNickeau        }
798c3437056SNickeau
799c3437056SNickeau        /**
800c3437056SNickeau         * The dokuwiki function {@link send_redirect()}
801c3437056SNickeau         * set the `Location header` and in php, the header function
802c3437056SNickeau         * in this case change the status code to 302 Arghhhh.
803c3437056SNickeau         * The code below is adapted from this function {@link send_redirect()}
804c3437056SNickeau         */
805c3437056SNickeau        global $MSG; // are there any undisplayed messages? keep them in session for display
806c3437056SNickeau        if (isset($MSG) && count($MSG) && !defined('NOSESSION')) {
807c3437056SNickeau            //reopen session, store data and close session again
808c3437056SNickeau            @session_start();
809c3437056SNickeau            $_SESSION[DOKU_COOKIE]['msg'] = $MSG;
810c3437056SNickeau        }
811c3437056SNickeau        session_write_close(); // always close the session
812c3437056SNickeau
813c3437056SNickeau        switch ($method) {
814c3437056SNickeau            case self::REDIRECT_PERMANENT_METHOD:
815c3437056SNickeau                HttpResponse::create(HttpResponse::STATUS_PERMANENT_REDIRECT)
816c3437056SNickeau                    ->addHeader(self::LOCATION_HEADER_PREFIX . $targetUrl)
817c3437056SNickeau                    ->send();
818c3437056SNickeau                return true;
819c3437056SNickeau            case self::REDIRECT_NOTFOUND_METHOD:
820c3437056SNickeau
821c3437056SNickeau                // Empty 404 body to not get the standard 404 page of the browser
822c3437056SNickeau                // but a blank page to avoid a sort of FOUC.
823c3437056SNickeau                // ie the user see a page briefly
824c3437056SNickeau                HttpResponse::create(HttpResponse::STATUS_NOT_FOUND)
825c3437056SNickeau                    ->addHeader(self::REFRESH_HEADER_PREFIX . $targetUrl)
826c3437056SNickeau                    ->send(self::PAGE_404, Mime::HTML);
827c3437056SNickeau                return true;
828c3437056SNickeau
829c3437056SNickeau            default:
830c3437056SNickeau                LogUtility::msg("The method ($method) is not an http redirection");
831c3437056SNickeau                return false;
832c3437056SNickeau        }
833c3437056SNickeau
834c3437056SNickeau
835c3437056SNickeau    }
836c3437056SNickeau
837c3437056SNickeau    /**
838c3437056SNickeau     * @param $id
839c3437056SNickeau     * @return array
840c3437056SNickeau     */
841c3437056SNickeau    private
842c3437056SNickeau    function getBestPage($id): array
843c3437056SNickeau    {
844c3437056SNickeau
845c3437056SNickeau        // The return parameters
846c3437056SNickeau        $bestPageId = null;
847c3437056SNickeau        $scorePageName = null;
848c3437056SNickeau
849c3437056SNickeau        // Get Score from a page
850c3437056SNickeau        $pageName = noNS($id);
851c3437056SNickeau        $pagesWithSameName = ft_pageLookup($pageName);
852c3437056SNickeau        if (count($pagesWithSameName) > 0) {
853c3437056SNickeau
854c3437056SNickeau            // Search same namespace in the page found than in the Id page asked.
855c3437056SNickeau            $bestNbWordFound = 0;
856c3437056SNickeau
857c3437056SNickeau
858c3437056SNickeau            $wordsInPageSourceId = explode(':', $id);
859c3437056SNickeau            foreach ($pagesWithSameName as $targetPageId => $title) {
860c3437056SNickeau
861c3437056SNickeau                // Nb of word found in the target page id
862c3437056SNickeau                // that are in the source page id
863c3437056SNickeau                $nbWordFound = 0;
864c3437056SNickeau                foreach ($wordsInPageSourceId as $word) {
865c3437056SNickeau                    $nbWordFound = $nbWordFound + substr_count($targetPageId, $word);
866c3437056SNickeau                }
867c3437056SNickeau
868c3437056SNickeau                if ($bestPageId == null) {
869c3437056SNickeau
870c3437056SNickeau                    $bestNbWordFound = $nbWordFound;
871c3437056SNickeau                    $bestPageId = $targetPageId;
872c3437056SNickeau
873c3437056SNickeau                } else {
874c3437056SNickeau
875c3437056SNickeau                    if ($nbWordFound >= $bestNbWordFound && strlen($bestPageId) > strlen($targetPageId)) {
876c3437056SNickeau
877c3437056SNickeau                        $bestNbWordFound = $nbWordFound;
878c3437056SNickeau                        $bestPageId = $targetPageId;
879c3437056SNickeau
880c3437056SNickeau                    }
881c3437056SNickeau
882c3437056SNickeau                }
883c3437056SNickeau
884c3437056SNickeau            }
885c3437056SNickeau            $scorePageName = $this->getConf('WeightFactorForSamePageName') + ($bestNbWordFound - 1) * $this->getConf('WeightFactorForSameNamespace');
886c3437056SNickeau            return array(
887c3437056SNickeau                'id' => $bestPageId,
888c3437056SNickeau                'score' => $scorePageName);
889c3437056SNickeau        }
890c3437056SNickeau        return array(
891c3437056SNickeau            'id' => $bestPageId,
892c3437056SNickeau            'score' => $scorePageName
893c3437056SNickeau        );
894c3437056SNickeau
895c3437056SNickeau    }
896c3437056SNickeau
897c3437056SNickeau
898c3437056SNickeau    /**
899c3437056SNickeau     * Redirect to the search engine
900c3437056SNickeau     */
901c3437056SNickeau    private
902c3437056SNickeau    function redirectToSearchEngine()
903c3437056SNickeau    {
904c3437056SNickeau
905c3437056SNickeau        global $ID;
906c3437056SNickeau        $this->performNotFoundRedirect($ID, self::TARGET_ORIGIN_SEARCH_ENGINE);
907c3437056SNickeau
908c3437056SNickeau    }
909c3437056SNickeau
910c3437056SNickeau
911c3437056SNickeau    /**
912c3437056SNickeau     *
913c3437056SNickeau     *   * For a conf file, it will update the Redirection Action Data as Referrer, Count Of Redirection, Redirection Date
914c3437056SNickeau     *   * For a SQlite database, it will add a row into the log
915c3437056SNickeau     *
916c3437056SNickeau     * @param string $sourcePageId
917c3437056SNickeau     * @param $targetPageId
918c3437056SNickeau     * @param $algorithmic
919c3437056SNickeau     * @param $method - http or rewrite
920c3437056SNickeau     */
921c3437056SNickeau    function logRedirection(string $sourcePageId, $targetPageId, $algorithmic, $method)
922c3437056SNickeau    {
923c3437056SNickeau
924c3437056SNickeau        $row = array(
925c3437056SNickeau            "TIMESTAMP" => date("c"),
926c3437056SNickeau            "SOURCE" => $sourcePageId,
927c3437056SNickeau            "TARGET" => $targetPageId,
928c3437056SNickeau            "REFERRER" => $_SERVER['HTTP_REFERER'],
929c3437056SNickeau            "TYPE" => $algorithmic,
930c3437056SNickeau            "METHOD" => $method
931c3437056SNickeau        );
932c3437056SNickeau        $request = Sqlite::createOrGetBackendSqlite()
933c3437056SNickeau            ->createRequest()
934c3437056SNickeau            ->setTableRow('redirections_log', $row);
935c3437056SNickeau        try {
936c3437056SNickeau            $request
937c3437056SNickeau                ->execute();
938c3437056SNickeau        } catch (ExceptionCombo $e) {
939c3437056SNickeau            LogUtility::msg("Redirection Log Insert Error. {$e->getMessage()}");
940c3437056SNickeau        } finally {
941c3437056SNickeau            $request->close();
942c3437056SNickeau        }
943c3437056SNickeau
944c3437056SNickeau
945c3437056SNickeau    }
946c3437056SNickeau
947c3437056SNickeau    /**
948c3437056SNickeau     * This function check if there is a redirection declared
949c3437056SNickeau     * in the redirection table
950c3437056SNickeau     * @return bool - true if a rewrite or redirection occurs
951c3437056SNickeau     * @throws Exception
952c3437056SNickeau     */
953c3437056SNickeau    private function processingPageRules(): bool
954c3437056SNickeau    {
955c3437056SNickeau        global $ID;
956c3437056SNickeau
957c3437056SNickeau        $calculatedTarget = null;
958c3437056SNickeau        $ruleMatcher = null; // Used in a warning message if the target page does not exist
959c3437056SNickeau        // Known redirection in the table
960c3437056SNickeau        // Get the page from redirection data
961c3437056SNickeau        $rules = $this->pageRules->getRules();
962c3437056SNickeau        foreach ($rules as $rule) {
963c3437056SNickeau
964c3437056SNickeau            $ruleMatcher = strtolower($rule[PageRules::MATCHER_NAME]);
965c3437056SNickeau            $ruleTarget = $rule[PageRules::TARGET_NAME];
966c3437056SNickeau
967c3437056SNickeau            // Glob to Rexgexp
968c3437056SNickeau            $regexpPattern = '/' . str_replace("*", "(.*)", $ruleMatcher) . '/';
969c3437056SNickeau
970c3437056SNickeau            // Match ?
971c3437056SNickeau            // https://www.php.net/manual/en/function.preg-match.php
972*86867397Sgerardnico            $pregMatchResult = @preg_match($regexpPattern, $ID, $matches);
973*86867397Sgerardnico            if ($pregMatchResult === false) {
974*86867397Sgerardnico                // The `if` to take into account this problem
975*86867397Sgerardnico                // PHP Warning:  preg_match(): Unknown modifier 'd' in /opt/www/datacadamia.com/lib/plugins/combo/action/router.php on line 972
976*86867397Sgerardnico                LogUtility::log2file("processing Page Rules An error occurred with the pattern ($regexpPattern)", LogUtility::LVL_MSG_WARNING);
977*86867397Sgerardnico                return false;
978*86867397Sgerardnico            }
979*86867397Sgerardnico            if ($pregMatchResult) {
980c3437056SNickeau                $calculatedTarget = $ruleTarget;
981c3437056SNickeau                foreach ($matches as $key => $match) {
982c3437056SNickeau                    if ($key == 0) {
983c3437056SNickeau                        continue;
984c3437056SNickeau                    } else {
985c3437056SNickeau                        $calculatedTarget = str_replace('$' . $key, $match, $calculatedTarget);
986c3437056SNickeau                    }
987c3437056SNickeau                }
988c3437056SNickeau                break;
989c3437056SNickeau            }
990c3437056SNickeau        }
991c3437056SNickeau
992c3437056SNickeau        if ($calculatedTarget == null) {
993c3437056SNickeau            return false;
994c3437056SNickeau        }
995c3437056SNickeau
996c3437056SNickeau        // If this is an external redirect (other domain)
997c3437056SNickeau        if (Url::isValidURL($calculatedTarget)) {
998c3437056SNickeau
999c3437056SNickeau            $this->executeHttpRedirect($calculatedTarget, self::TARGET_ORIGIN_PAGE_RULES, self::REDIRECT_PERMANENT_METHOD);
1000c3437056SNickeau            return true;
1001c3437056SNickeau
1002c3437056SNickeau        }
1003c3437056SNickeau
1004c3437056SNickeau        // If the page exist
1005c3437056SNickeau        if (page_exists($calculatedTarget)) {
1006c3437056SNickeau
1007c3437056SNickeau            // This is DokuWiki Id and should always be lowercase
1008c3437056SNickeau            // The page rule may have change that
1009c3437056SNickeau            $calculatedTarget = strtolower($calculatedTarget);
1010c3437056SNickeau            $res = $this->executeTransparentRedirect($calculatedTarget, self::TARGET_ORIGIN_PAGE_RULES);
1011c3437056SNickeau            if ($res) {
1012c3437056SNickeau                return true;
1013c3437056SNickeau            } else {
1014c3437056SNickeau                return false;
1015c3437056SNickeau            }
1016c3437056SNickeau
1017c3437056SNickeau        } else {
1018c3437056SNickeau
1019c3437056SNickeau            LogUtility::msg("The calculated target page ($calculatedTarget) (for the non-existing page `$ID` with the matcher `$ruleMatcher`) does not exist", LogUtility::LVL_MSG_ERROR);
1020c3437056SNickeau            return false;
1021c3437056SNickeau
1022c3437056SNickeau        }
1023c3437056SNickeau
1024c3437056SNickeau    }
1025c3437056SNickeau
1026c3437056SNickeau    private function performNotFoundRedirect(string $targetId, string $origin): bool
1027c3437056SNickeau    {
1028c3437056SNickeau        return $this->executeHttpRedirect($targetId, $origin, self::REDIRECT_NOTFOUND_METHOD);
1029c3437056SNickeau    }
1030c3437056SNickeau
1031c3437056SNickeau
1032c3437056SNickeau}
1033