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