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