setupLocale();
require_once(dirname(__FILE__) . '/Message404.php');
$this->message = new Message404();
}
function register(Doku_Event_Handler $controller)
{
$this->objectId = spl_object_hash($this);
/* This will call the function _handle404 */
$controller->register_hook('DOKUWIKI_STARTED',
'AFTER',
$this,
'_handle404',
array());
/* This will call the function _displayRedirectMessage */
$controller->register_hook(
'TPL_ACT_RENDER',
'BEFORE',
$this,
'_displayRedirectMessage',
array()
);
}
/**
* Verify if there is a 404
* Inspiration comes from Not Found Plugin
* @param $event Doku_Event
* @param $param
* @return bool not required
* @throws Exception
*/
function _handle404(&$event, $param)
{
global $ACT;
if ($ACT != 'show') return false;
global $INFO;
if ($INFO['exists']) return false;
// We instantiate the redirect manager because it's use overall
// it holds the function and methods
require_once(dirname(__FILE__) . '/admin.php');
if ($this->redirectManager == null) {
$this->redirectManager = admin_plugin_404manager::get();
}
// Event is also used in some sub-function, we make it them object scope
$this->event = $event;
// Global variable needed in the process
global $ID;
global $conf;
$targetPage = $this->redirectManager->getRedirectionTarget($ID);
// If this is an external redirect
if ($this->redirectManager->isValidURL($targetPage) && $targetPage) {
$this->redirectToExternalPage($targetPage);
return true;
}
// Internal redirect
// Their is one action for a writer:
// * edit mode direct
// If the user is a writer (It have the right to edit).
If ($this->userCanWrite() && $this->getConf(self::GO_TO_EDIT_MODE) == 1) {
$this->gotToEditMode($event);
// Stop here
return true;
}
// This is a reader
// Their are only three actions for a reader:
// * redirect to a page (show another page id)
// * go to the search page
// * do nothing
// If the page exist
if (page_exists($targetPage)) {
$this->redirectToDokuwikiPage($targetPage, self::REDIRECT_TARGET_PAGE_FROM_DATASTORE);
return true;
}
// We are still a reader, the redirection does not exist the user not allowed to edit the page (public of other)
if ($this->getConf('ActionReaderFirst') == self::NOTHING) {
return true;
}
// We are reader and their is no redirection set, we apply the algorithm
$readerAlgorithms = array();
$readerAlgorithms[0] = $this->getConf('ActionReaderFirst');
$readerAlgorithms[1] = $this->getConf('ActionReaderSecond');
$readerAlgorithms[2] = $this->getConf('ActionReaderThird');
$i = 0;
while (isset($readerAlgorithms[$i])) {
switch ($readerAlgorithms[$i]) {
case self::NOTHING:
return true;
break;
case self::GO_TO_NS_START_PAGE:
// Start page with the conf['start'] parameter
$startPage = getNS($ID) . ':' . $conf['start'];
if (page_exists($startPage)) {
$this->redirectToDokuwikiPage($startPage, self::REDIRECT_SOURCE_START_PAGE);
return true;
}
// Start page with the same name than the namespace
$startPage = getNS($ID) . ':' . curNS($ID);
if (page_exists($startPage)) {
$this->redirectToDokuwikiPage($startPage, self::REDIRECT_SOURCE_START_PAGE);
return true;
}
break;
case self::GO_TO_BEST_PAGE_NAME:
$bestPageId = null;
$bestPage = $this->getBestPage($ID);
$bestPageId = $bestPage['id'];
$scorePageName = $bestPage['score'];
// Get Score from a Namespace
$bestNamespace = $this->scoreBestNamespace($ID);
$bestNamespaceId = $bestNamespace['namespace'];
$namespaceScore = $bestNamespace['score'];
// Compare the two score
if ($scorePageName > 0 or $namespaceScore > 0) {
if ($scorePageName > $namespaceScore) {
$this->redirectToDokuwikiPage($bestPageId, self::REDIRECT_SOURCE_BEST_PAGE_NAME);
} else {
$this->redirectToDokuwikiPage($bestNamespaceId, self::REDIRECT_SOURCE_BEST_PAGE_NAME);
}
return true;
}
break;
case self::GO_TO_BEST_NAMESPACE:
$scoreNamespace = $this->scoreBestNamespace($ID);
$bestNamespaceId = $scoreNamespace['namespace'];
$score = $scoreNamespace['score'];
if ($score > 0) {
$this->redirectToDokuwikiPage($bestNamespaceId, self::REDIRECT_SOURCE_BEST_NAMESPACE);
return true;
}
break;
case self::GO_TO_SEARCH_ENGINE:
$this->redirectToSearchEngine();
return true;
break;
// End Switch Action
}
$i++;
// End While Action
}
// End if not connected
return true;
}
/**
* Main function; dispatches the visual comment actions
* @param $event Doku_Event
*/
function _displayRedirectMessage(&$event, $param)
{
// After a redirect to another page via query string ?
global $INPUT;
// Comes from method redirectToDokuwikiPage
$pageIdOrigin = $INPUT->str(self::QUERY_STRING_ORIGIN_PAGE);
if ($pageIdOrigin) {
$redirectSource = $INPUT->str(self::QUERY_STRING_REDIR_TYPE);
switch ($redirectSource) {
case self::REDIRECT_TARGET_PAGE_FROM_DATASTORE:
$this->message->addContent(sprintf($this->lang['message_redirected_by_redirect'], hsc($pageIdOrigin)));
$this->message->setType(Message404::TYPE_CLASSIC);
break;
case self::REDIRECT_SOURCE_START_PAGE:
$this->message->addContent(sprintf($this->lang['message_redirected_to_startpage'], hsc($pageIdOrigin)));
$this->message->setType(Message404::TYPE_WARNING);
break;
case self::REDIRECT_SOURCE_BEST_PAGE_NAME:
$this->message->addContent(sprintf($this->lang['message_redirected_to_bestpagename'], hsc($pageIdOrigin)));
$this->message->setType(Message404::TYPE_WARNING);
break;
case self::REDIRECT_SOURCE_BEST_NAMESPACE:
$this->message->addContent(sprintf($this->lang['message_redirected_to_bestnamespace'], hsc($pageIdOrigin)));
$this->message->setType(Message404::TYPE_WARNING);
break;
case self::REDIRECT_SEARCH_ENGINE:
$this->message->addContent(sprintf($this->lang['message_redirected_to_searchengine'], hsc($pageIdOrigin)));
$this->message->setType(Message404::TYPE_WARNING);
break;
}
// Add a list of page with the same name to the message
// if the redirections is not planned
if ($redirectSource!=self::REDIRECT_TARGET_PAGE_FROM_DATASTORE) {
$this->addToMessagePagesWithSameName($pageIdOrigin);
}
}
if ($event->data == 'show' || $event->data == 'edit' || $event->data == 'search') {
$this->printMessage($this->message);
}
}
/**
* getBestNamespace
* Return a list with 'BestNamespaceId Score'
* @param $id
* @return array
*/
private function scoreBestNamespace($id)
{
global $conf;
// Parameters
$pageNameSpace = getNS($id);
// If the page has an existing namespace start page take it, other search other namespace
$startPageNameSpace = $pageNameSpace . ":";
$dateAt = '';
// $startPageNameSpace will get a full path (ie with start or the namespace
resolve_pageid($pageNameSpace, $startPageNameSpace, $exists, $dateAt, true);
if (page_exists($startPageNameSpace)) {
$nameSpaces = array($startPageNameSpace);
} else {
$nameSpaces = ft_pageLookup($conf['start']);
}
// Parameters and search the best namespace
$pathNames = explode(':', $pageNameSpace);
$bestNbWordFound = 0;
$bestNamespaceId = '';
foreach ($nameSpaces as $nameSpace) {
$nbWordFound = 0;
foreach ($pathNames as $pathName) {
if (strlen($pathName) > 2) {
$nbWordFound = $nbWordFound + substr_count($nameSpace, $pathName);
}
}
if ($nbWordFound > $bestNbWordFound) {
// Take only the smallest namespace
if (strlen($nameSpace) < strlen($bestNamespaceId) or $nbWordFound > $bestNbWordFound) {
$bestNbWordFound = $nbWordFound;
$bestNamespaceId = $nameSpace;
}
}
}
$startPageFactor = $this->getConf('WeightFactorForStartPage');
$nameSpaceFactor = $this->getConf('WeightFactorForSameNamespace');
if ($bestNbWordFound > 0) {
$bestNamespaceScore = $bestNbWordFound * $nameSpaceFactor + $startPageFactor;
} else {
$bestNamespaceScore = 0;
}
return array(
'namespace' => $bestNamespaceId,
'score' => $bestNamespaceScore
);
}
/**
* @param $event
*/
private function gotToEditMode(&$event)
{
global $ID;
global $conf;
global $ACT;
$ACT = 'edit';
// If this is a side bar no message.
// There is always other page with the same name
$pageName = noNS($ID);
if ($pageName != $conf['sidebar']) {
if ($this->getConf('ShowMessageClassic') == 1) {
$this->message->addContent($this->lang['message_redirected_to_edit_mode']);
$this->message->setType(Message404::TYPE_CLASSIC);
}
// If Param show page name unique and it's not a start page
$this->addToMessagePagesWithSameName($ID);
}
}
/**
* Return if the user has the right/permission to create/write an article
* @return bool
*/
private function userCanWrite()
{
global $ID;
if ($_SERVER['REMOTE_USER']) {
$perm = auth_quickaclcheck($ID);
} else {
$perm = auth_aclcheck($ID, '', null);
}
if ($perm >= AUTH_EDIT) {
return true;
} else {
return false;
}
}
/**
* Redirect to an internal page, no external resources
* @param $targetPage the target page id or an URL
* @param string|the $redirectSource the source of the redirect
* @throws Exception
*/
private function redirectToDokuwikiPage($targetPage, $redirectSource = 'Not Known')
{
global $ID;
//If the user have right to see the target page
if ($_SERVER['REMOTE_USER']) {
$perm = auth_quickaclcheck($targetPage);
} else {
$perm = auth_aclcheck($targetPage, '', null);
}
if ($perm <= AUTH_NONE) {
return;
}
// TODO: Create a cache table ? with the source, target and type of redirections ?
// if (!$this->redirectManager->isRedirectionPresent($ID)) {
// $this->redirectManager->addRedirection($ID, $targetPage);
//}
// Redirection
$this->redirectManager->logRedirection($ID, $targetPage, $redirectSource);
// Explode the page ID and the anchor (#)
$link = explode('#', $targetPage, 2);
// Query String to pass the message
$urlParams = array(
self::QUERY_STRING_ORIGIN_PAGE => $ID,
self::QUERY_STRING_REDIR_TYPE => $redirectSource
);
// TODO: Status code
// header('HTTP/1.1 301 Moved Permanently') will cache it in the browser !!!
$wl = wl($link[0], $urlParams, true, '&');
if ($link[1]) {
$wl .= '#' . rawurlencode($link[1]);
}
send_redirect($wl);
}
/**
* Redirect to an internal page, no external resources
* @param string $url target page id or an URL
*/
private function redirectToExternalPage($url)
{
global $ID;
// No message can be shown because this is an external URL
// Update the redirections
$this->redirectManager->logRedirection($ID, $url, self::REDIRECT_EXTERNAL);
// TODO: Status code
// header('HTTP/1.1 301 Moved Permanently');
send_redirect($url);
if (defined('DOKU_UNITTEST')) return; // no exits during unit tests
exit();
}
/**
* @param $id
* @return array
*/
private function getBestPage($id)
{
// The return parameters
$bestPageId = null;
$scorePageName = null;
// Get Score from a page
$pageName = noNS($id);
$pagesWithSameName = ft_pageLookup($pageName);
if (count($pagesWithSameName) > 0) {
// Search same namespace in the page found than in the Id page asked.
$bestNbWordFound = 0;
$wordsInPageSourceId = explode(':', $id);
foreach ($pagesWithSameName as $targetPageId => $title) {
// Nb of word found in the target page id
// that are in the source page id
$nbWordFound = 0;
foreach ($wordsInPageSourceId as $word) {
$nbWordFound = $nbWordFound + substr_count($targetPageId, $word);
}
if ($bestPageId == null) {
$bestNbWordFound = $nbWordFound;
$bestPageId = $targetPageId;
} else {
if ($nbWordFound >= $bestNbWordFound && strlen($bestPageId) > strlen($targetPageId)) {
$bestNbWordFound = $nbWordFound;
$bestPageId = $targetPageId;
}
}
}
$scorePageName = $this->getConf('WeightFactorForSamePageName') + ($bestNbWordFound - 1) * $this->getConf('WeightFactorForSameNamespace');
return array(
'id' => $bestPageId,
'score' => $scorePageName);
}
return array(
'id' => $bestPageId,
'score' => $scorePageName
);
}
/**
* Add the page with the same page name but in an other location
* @param $pageId
*/
private function addToMessagePagesWithSameName($pageId)
{
global $conf;
$pageName = noNS($pageId);
if ($this->getConf('ShowPageNameIsNotUnique') == 1 && $pageName <> $conf['start']) {
//Search same page name
$pagesWithSameName = ft_pageLookup($pageName);
if (count($pagesWithSameName) > 0) {
$this->message->setType(Message404::TYPE_WARNING);
// Assign the value to a variable to be able to use the construct .=
if ($this->message->getContent() <> '') {
$this->message->addContent('
');
}
$this->message->addContent($this->lang['message_pagename_exist_one']);
$this->message->addContent('