mirror of
https://github.com/diocloid/LinkTitles.git
synced 2025-07-12 17:29:30 +02:00
Refactor, add Targets class.
This commit is contained in:
@ -34,6 +34,8 @@
|
||||
},
|
||||
"AutoloadClasses": {
|
||||
"LinkTitles\\Extension": "includes/LinkTitles_Extension.php",
|
||||
"LinkTitles\\Targets": "includes/Targets.php",
|
||||
"LinkTitles\\Config": "includes/Config.php",
|
||||
"LinkTitles\\Special": "includes/LinkTitles_Special.php",
|
||||
"LinkTitles\\TestCase": "tests/phpunit/TestCase.php"
|
||||
},
|
||||
|
126
includes/Config.php
Normal file
126
includes/Config.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* The LinkTitles\Config class holds configuration for the LinkTitles extension.
|
||||
*
|
||||
* Copyright 2012-2017 Daniel Kraus <bovender@bovender.de> ('bovender')
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
* @author Daniel Kraus <bovender@bovender.de>
|
||||
*/
|
||||
namespace LinkTitles;
|
||||
|
||||
/**
|
||||
* Holds LinkTitles configuration.
|
||||
*
|
||||
* This class encapsulates the global configuration variables so we do not have
|
||||
* to pull those globals into scope in the individual LinkTitles classes.
|
||||
*
|
||||
* Using a dedicated configuration class also facilitates overriding certain
|
||||
* options, i.e. in a maintenance script that is invoked with flags from the
|
||||
* command line.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
class Config {
|
||||
/**
|
||||
* Whether to add links to a page when the page is edited/saved.
|
||||
* @var bool $parseOnEdit
|
||||
*/
|
||||
public $parseOnEdit;
|
||||
|
||||
/**
|
||||
* Whether to add links to a page when the page is rendered.
|
||||
* @var bool $parseOnRender
|
||||
*/
|
||||
public $parseOnRender;
|
||||
|
||||
/**
|
||||
* Indicates whether to prioritize short over long titles.
|
||||
* @var bool $preferShortTitles
|
||||
*/
|
||||
public $preferShortTitles;
|
||||
|
||||
/**
|
||||
* Minimum length of a page title for it to qualify as a potential link target.
|
||||
* @var int $minimumTitleLength
|
||||
*/
|
||||
public $minimumTitleLength;
|
||||
|
||||
/**
|
||||
* Array of page titles that must never be link targets.
|
||||
*
|
||||
* This may be useful to exclude common abbreviations or acronyms from
|
||||
* automatic linking.
|
||||
* @var Array $blackList
|
||||
*/
|
||||
public $blackList;
|
||||
|
||||
/**
|
||||
* Array of those name spaces (integer constants) whose pages may be linked.
|
||||
* @var Array $nameSpaces
|
||||
*/
|
||||
public $nameSpaces;
|
||||
|
||||
/**
|
||||
* Indicates whether to add a link to the first occurrence of a page title
|
||||
* only (true), or add links to all occurrences on the source page (false).
|
||||
* @var bool $firstOnly;
|
||||
*/
|
||||
public $firstOnly;
|
||||
|
||||
/**
|
||||
* Indicates whether to operate in smart mode, i.e. link to pages even if the
|
||||
* case does not match. Without smart mode, pages are linked to only if the
|
||||
* exact title appears on the source page.
|
||||
* @var bool $smartMode;
|
||||
*/
|
||||
public $smartMode;
|
||||
|
||||
/**
|
||||
* Mirrors the global MediaWiki variable $wgCapitalLinks that indicates
|
||||
* whether or not page titles are fully case sensitive
|
||||
* @var bool $capitalLinks;
|
||||
*/
|
||||
public $capitalLinks;
|
||||
|
||||
/**
|
||||
* Constructs a new Config object.
|
||||
*
|
||||
* The object's member variables will automatically be set with the values
|
||||
* from the corresponding global variables.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wgLinkTitlesParseOnEdit;
|
||||
global $wgLinkTitlesParseOnRender;
|
||||
global $wgLinkTitlesPreferShortTitles;
|
||||
global $wgLinkTitlesMinimumTitleLength;
|
||||
global $wgLinkTitlesBlackList;
|
||||
global $wgLinkTitlesNamespaces;
|
||||
global $wgLinkTitlesFirstOnly;
|
||||
global $wgLinkTitlesSmartMode;
|
||||
global $wgCapitalLinks;
|
||||
$this->parseOnEdit = $wgLinkTitlesParseOnEdit;
|
||||
$this->parseOnRender = $wgLinkTitlesParseOnRender;
|
||||
$this->preferShortTitles = $wgLinkTitlesPreferShortTitles;
|
||||
$this->minimumTitleLength = $wgLinkTitlesMinimumTitleLength;
|
||||
$this->blackList = $wgLinkTitlesBlackList;
|
||||
$this->nameSpaces = $wgLinkTitlesNamespaces;
|
||||
$this->firstOnly = $wgLinkTitlesFirstOnly;
|
||||
$this->smartMode = $wgLinkTitlesSmartMode;
|
||||
$this->capitalLinks = $wgCapitalLinks; // MediaWiki global variable
|
||||
}
|
||||
|
||||
}
|
@ -1,44 +1,32 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2012-2017 Daniel Kraus <bovender@bovender.de> ('bovender')
|
||||
/**
|
||||
* The LinkTitles\Extension class provides entry points for the extension.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* Copyright 2012-2017 Daniel Kraus <bovender@bovender.de> ('bovender')
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
* @author Daniel Kraus <bovender@bovender.de>
|
||||
*/
|
||||
/// @file
|
||||
namespace LinkTitles;
|
||||
|
||||
/// Helper function for development and debugging.
|
||||
/// @param $var Any variable. Raw content will be dumped to stderr.
|
||||
/// @return undefined
|
||||
function dump($var) {
|
||||
error_log(print_r($var, TRUE) . "\n", 3, 'php://stderr');
|
||||
};
|
||||
|
||||
/// Central class of the extension. Sets up parser hooks.
|
||||
/// This class contains only static functions; do not instantiate.
|
||||
/**
|
||||
* Provides entry points for the extension.
|
||||
*/
|
||||
class Extension {
|
||||
/// Caching variable for page titles that are fetched from the DB.
|
||||
private static $pageTitles;
|
||||
|
||||
/// Caching variable for the current namespace.
|
||||
/// This is needed because the sort order of the page titles that
|
||||
/// are cached in self::$pageTitles depends on the namespace of
|
||||
/// the page currently being processed.
|
||||
private static $currentNamespace;
|
||||
|
||||
/// A Title object for the page that is being parsed.
|
||||
private static $currentTitle;
|
||||
|
||||
@ -73,12 +61,6 @@ class Extension {
|
||||
self::BuildDelimiters();
|
||||
}
|
||||
|
||||
/// Helper method to be used in unit testing, were everything takes place
|
||||
/// in one request.
|
||||
public static function invalidateCache() {
|
||||
self::fetchPageTitles();
|
||||
}
|
||||
|
||||
/// Event handler that is hooked to the PageContentSave event.
|
||||
public static function onPageContentSave( &$wikiPage, &$user, &$content, &$summary,
|
||||
$isMinor, $isWatch, $section, &$flags, &$status ) {
|
||||
@ -136,16 +118,12 @@ class Extension {
|
||||
( $wgLinkTitlesFirstOnly ) ? $limit = 1 : $limit = -1;
|
||||
$limitReached = false;
|
||||
self::$currentTitle = $title;
|
||||
$currentNamespace = $title->getNamespace();
|
||||
$newText = $text;
|
||||
|
||||
if ( !isset( self::$pageTitles ) || ( $currentNamespace != self::$currentNamespace ) ) {
|
||||
self::$currentNamespace = $currentNamespace;
|
||||
self::fetchPageTitles();
|
||||
}
|
||||
$targets = Targets::default( $title, new Config() );
|
||||
|
||||
// Iterate through the page titles
|
||||
foreach( self::$pageTitles as $row ) {
|
||||
foreach( $targets->queryResult as $row ) {
|
||||
self::newTarget( $row->page_namespace, $row->page_title );
|
||||
|
||||
// Don't link current page
|
||||
@ -274,67 +252,6 @@ class Extension {
|
||||
return $output;
|
||||
}
|
||||
|
||||
// Fetches the page titles from the database.
|
||||
private static function fetchPageTitles() {
|
||||
global $wgLinkTitlesPreferShortTitles;
|
||||
global $wgLinkTitlesMinimumTitleLength;
|
||||
global $wgLinkTitlesBlackList;
|
||||
global $wgLinkTitlesNamespaces;
|
||||
|
||||
( $wgLinkTitlesPreferShortTitles ) ? $sort_order = 'ASC' : $sort_order = 'DESC';
|
||||
// Build a blacklist of pages that are not supposed to be link
|
||||
// targets. This includes the current page.
|
||||
$blackList = str_replace( ' ', '_', '("' . implode( '","',$wgLinkTitlesBlackList ) . '")' );
|
||||
|
||||
// Build our weight list. Make sure current namespace is first element
|
||||
$namespaces = array_diff( $wgLinkTitlesNamespaces, [ self::$currentNamespace ] );
|
||||
array_unshift( $namespaces, self::$currentNamespace );
|
||||
|
||||
// No need for sanitiy check. we are sure that we have at least one element in the array
|
||||
$weightSelect = "CASE page_namespace ";
|
||||
$currentWeight = 0;
|
||||
foreach ($namespaces as &$namspacevalue) {
|
||||
$currentWeight = $currentWeight + 100;
|
||||
$weightSelect = $weightSelect . " WHEN " . $namspacevalue . " THEN " . $currentWeight . PHP_EOL;
|
||||
}
|
||||
$weightSelect = $weightSelect . " END ";
|
||||
$namespacesClause = '(' . implode( ', ', $namespaces ) . ')';
|
||||
|
||||
// Build an SQL query and fetch all page titles ordered by length from
|
||||
// shortest to longest. Only titles from 'normal' pages (namespace uid
|
||||
// = 0) are returned. Since the db may be sqlite, we need a try..catch
|
||||
// structure because sqlite does not support the CHAR_LENGTH function.
|
||||
$dbr = wfGetDB( DB_SLAVE );
|
||||
try {
|
||||
$res = $dbr->select(
|
||||
'page',
|
||||
array( 'page_title', 'page_namespace' , "weight" => $weightSelect),
|
||||
array(
|
||||
'page_namespace IN ' . $namespacesClause,
|
||||
'CHAR_LENGTH(page_title) >= ' . $wgLinkTitlesMinimumTitleLength,
|
||||
'page_title NOT IN ' . $blackList,
|
||||
),
|
||||
__METHOD__,
|
||||
array( 'ORDER BY' => 'weight ASC, CHAR_LENGTH(page_title) ' . $sort_order )
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$res = $dbr->select(
|
||||
'page',
|
||||
array( 'page_title', 'page_namespace' , "weight" => $weightSelect ),
|
||||
array(
|
||||
'page_namespace IN ' . $namespacesClause,
|
||||
'LENGTH(page_title) >= ' . $wgLinkTitlesMinimumTitleLength,
|
||||
'page_title NOT IN ' . $blackList,
|
||||
),
|
||||
__METHOD__,
|
||||
array( 'ORDER BY' => 'weight ASC, LENGTH(page_title) ' . $sort_order )
|
||||
);
|
||||
}
|
||||
|
||||
self::$pageTitles = $res;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Build an anonymous callback function to be used in simple mode.
|
||||
private static function simpleModeCallback( array $matches ) {
|
||||
if ( self::checkTargetPage() ) {
|
||||
|
142
includes/Targets.php
Normal file
142
includes/Targets.php
Normal file
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
/**
|
||||
* The LinkTitles\Targets class.
|
||||
*
|
||||
* Copyright 2012-2017 Daniel Kraus <bovender@bovender.de> ('bovender')
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
* @author Daniel Kraus <bovender@bovender.de>
|
||||
*/
|
||||
namespace LinkTitles;
|
||||
|
||||
/**
|
||||
* Fetches potential target page titles from the database.
|
||||
*/
|
||||
class Targets {
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* Singleton factory that returns a (cached) database query results with
|
||||
* potential target page titles.
|
||||
*
|
||||
* The subset of pages that may serve as target pages depends on the
|
||||
* name space of the source page. Therefore, if the $nameSpace differs from
|
||||
* the cached name space, the database is queried again.
|
||||
*
|
||||
* @param String $nameSpace The namespace of the current page.
|
||||
* @param Config $config LinkTitles configuration.
|
||||
*/
|
||||
public static function default( \Title $title, Config $config ) {
|
||||
if ( ( self::$instance === null ) || ( self::$instance->nameSpace != $title->getNamespace() ) ) {
|
||||
self::$instance = new Targets( $title, $config );
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the cache; the next call of Targets::default() will trigger
|
||||
* a database query.
|
||||
*
|
||||
* Use this in unit tests which are performed in a single request cycle so that
|
||||
* changes to the pages list may not be picked up by the cached Targets instance.
|
||||
*/
|
||||
public static function invalidate() {
|
||||
self::$instance = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the results of a database query for target page titles, filtered
|
||||
* and sorted.
|
||||
* @var IResultWrapper $queryResult
|
||||
*/
|
||||
public $queryResult;
|
||||
|
||||
/**
|
||||
* Holds the name space (integer) for which the list of target pages was built.
|
||||
* @var Int $nameSpace
|
||||
*/
|
||||
public $nameSpace;
|
||||
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* The constructor is private to enforce using the singleton pattern.
|
||||
* @param \Title $title
|
||||
*/
|
||||
private function __construct( \Title $title, Config $config) {
|
||||
$this->config = $config;
|
||||
$this->nameSpace = $title->getNameSpace();
|
||||
$this->fetch();
|
||||
}
|
||||
|
||||
//
|
||||
/**
|
||||
* Fetches the page titles from the database.
|
||||
*/
|
||||
private function fetch() {
|
||||
|
||||
( $this->config->preferShortTitles ) ? $sortOrder = 'ASC' : $sortOrder = 'DESC';
|
||||
// Build a blacklist of pages that are not supposed to be link
|
||||
// targets. This includes the current page.
|
||||
$blackList = str_replace( ' ', '_', '("' . implode( '","',$this->config->blackList ) . '")' );
|
||||
|
||||
// Build our weight list. Make sure current namespace is first element
|
||||
$nameSpaces = array_diff( $this->config->nameSpaces, [ $this->nameSpace ] );
|
||||
array_unshift( $nameSpaces, $this->nameSpace );
|
||||
|
||||
// No need for sanitiy check. we are sure that we have at least one element in the array
|
||||
$weightSelect = "CASE page_namespace ";
|
||||
$currentWeight = 0;
|
||||
foreach ($nameSpaces as &$nameSpaceValue) {
|
||||
$currentWeight = $currentWeight + 100;
|
||||
$weightSelect = $weightSelect . " WHEN " . $nameSpaceValue . " THEN " . $currentWeight . PHP_EOL;
|
||||
}
|
||||
$weightSelect = $weightSelect . " END ";
|
||||
$nameSpacesClause = '(' . implode( ', ', $nameSpaces ) . ')';
|
||||
|
||||
// Build an SQL query and fetch all page titles ordered by length from
|
||||
// shortest to longest. Only titles from 'normal' pages (namespace uid
|
||||
// = 0) are returned. Since the db may be sqlite, we need a try..catch
|
||||
// structure because sqlite does not support the CHAR_LENGTH function.
|
||||
$dbr = wfGetDB( DB_SLAVE );
|
||||
try {
|
||||
$this->queryResult = $dbr->select(
|
||||
'page',
|
||||
array( 'page_title', 'page_namespace' , "weight" => $weightSelect),
|
||||
array(
|
||||
'page_namespace IN ' . $nameSpacesClause,
|
||||
'CHAR_LENGTH(page_title) >= ' . $this->config->minimumTitleLength,
|
||||
'page_title NOT IN ' . $blackList,
|
||||
),
|
||||
__METHOD__,
|
||||
array( 'ORDER BY' => 'weight ASC, CHAR_LENGTH(page_title) ' . $sortOrder )
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$this->queryResult = $dbr->select(
|
||||
'page',
|
||||
array( 'page_title', 'page_namespace' , "weight" => $weightSelect ),
|
||||
array(
|
||||
'page_namespace IN ' . $nameSpacesClause,
|
||||
'LENGTH(page_title) >= ' . $this->config->minimumTitleLength,
|
||||
'page_title NOT IN ' . $blackList,
|
||||
),
|
||||
__METHOD__,
|
||||
array( 'ORDER BY' => 'weight ASC, LENGTH(page_title) ' . $sortOrder )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
20
tests/phpunit/ConfigTest.php
Normal file
20
tests/phpunit/ConfigTest.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests the LinkTitles\Config class.
|
||||
*
|
||||
* This single unit test basically serves to ensure the Config class is working.
|
||||
* @group bovender
|
||||
* @group Database
|
||||
*/
|
||||
class ConfigTest extends LinkTitles\TestCase {
|
||||
|
||||
public function testParseOnEdit() {
|
||||
$this->setMwGlobals( [
|
||||
'wgLinkTitlesParseOnEdit' => true,
|
||||
'wgLinkTitlesParseOnRender' => false
|
||||
] );
|
||||
$config = new LinkTitles\Config();
|
||||
global $wgLinkTitlesParseOnEdit;
|
||||
$this->assertSame( $config->parseOnEdit, $wgLinkTitlesParseOnEdit );
|
||||
}
|
||||
}
|
@ -8,10 +8,20 @@ class ParseOnEditTest extends LinkTitles\TestCase {
|
||||
public function testParseOnEdit() {
|
||||
$this->setMwGlobals( [
|
||||
'wgLinkTitlesParseOnEdit' => true,
|
||||
'wgLinkTitlesParseOnRender' => true
|
||||
'wgLinkTitlesParseOnRender' => false
|
||||
] );
|
||||
$pageId = $this->insertPage( 'test page', 'This page should link to the link target' )['id'];
|
||||
$page = WikiPage::newFromId( $pageId );
|
||||
$this->assertSame( 'This page should link to the [[link target]]', self::getPageText( $page ) );
|
||||
}
|
||||
|
||||
public function testDoNotParseOnEdit() {
|
||||
$this->setMwGlobals( [
|
||||
'wgLinkTitlesParseOnEdit' => false,
|
||||
'wgLinkTitlesParseOnRender' => false
|
||||
] );
|
||||
$pageId = $this->insertPage( 'test page', 'This page should not link to the link target' )['id'];
|
||||
$page = WikiPage::newFromId( $pageId );
|
||||
$this->assertSame( 'This page should not link to the link target', self::getPageText( $page ) );
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ abstract class TestCase extends \MediaWikiTestCase {
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->insertPage( 'link target', 'This page serves as a link target' );
|
||||
Extension::invalidateCache();
|
||||
Targets::invalidate(); // force re-querying the pages table
|
||||
}
|
||||
|
||||
protected function tearDown() {
|
||||
|
Reference in New Issue
Block a user