jenkins-bot has submitted this change and it was merged.
Change subject: Initial commit
......................................................................
Initial commit
Bug: T146878
Bug: T146878
Change-Id: Ia9a0190f4b992471b32ec796b7876ec9f3ac8ff1
---
A extension.json
A i18n/en.json
A i18n/qqq.json
A src/InterwikiSorter.php
A src/InterwikiSortingHookHandlers.php
A src/InterwikiSortingHooks.php
A tests/phpunit/InterwikiSorterTest.php
A tests/phpunit/InterwikiSortingHooksTest.php
8 files changed, 490 insertions(+), 0 deletions(-)
Approvals:
WMDE-Fisch: Looks good to me, approved
jenkins-bot: Verified
diff --git a/extension.json b/extension.json
new file mode 100644
index 0000000..70e8ac3
--- /dev/null
+++ b/extension.json
@@ -0,0 +1,32 @@
+{
+ "name": "InterwikiSorting",
+ "version": "0.0.1",
+ "author": [
+ "WMDE"
+ ],
+ "url": "https://www.mediawiki.org/wiki/Extension:InterwikiSorting",
+ "descriptionmsg": "interwikisorting-desc",
+ "license-name": "GPL-2.0+",
+ "type": "other",
+ "config": {
+ "InterwikiSortingNamespaces": [],
+ "InterwikiSortingAlwaysSort": false,
+ "InterwikiSortingSort": "code",
+ "InterwikiSortingSortPrepend": [],
+ "InterwikiSortingInterwikiSortOrders": []
+ },
+ "AutoloadClasses": {
+ "InterwikiSorting\\InterwikiSorter": "src/InterwikiSorter.php",
+ "InterwikiSorting\\InterwikiSortingHooks": "src/InterwikiSortingHooks.php",
+ "InterwikiSorting\\InterwikiSortingHookHandlers":
"src/InterwikiSortingHookHandlers.php"
+ },
+ "Hooks": {
+ "BeforeInitialize": [
"InterwikiSorting\\InterwikiSortingHooks::onBeforeInitialize" ]
+ },
+ "MessagesDirs": {
+ "InterwikiSorting": [
+ "i18n"
+ ]
+ },
+ "manifest_version": 1
+}
\ No newline at end of file
diff --git a/i18n/en.json b/i18n/en.json
new file mode 100644
index 0000000..1264bf0
--- /dev/null
+++ b/i18n/en.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Addshore"
+ ]
+ },
+ "interwikisorting-desc": "Sort interwiki links."
+}
\ No newline at end of file
diff --git a/i18n/qqq.json b/i18n/qqq.json
new file mode 100644
index 0000000..158978c
--- /dev/null
+++ b/i18n/qqq.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Addshore"
+ ]
+ },
+ "interwikisorting-desc":
"{{desc|name=Cognate|url=https://www.mediawiki.org/wiki/Extension:InterwikiSorting}}"
+}
diff --git a/src/InterwikiSorter.php b/src/InterwikiSorter.php
new file mode 100644
index 0000000..02ced82
--- /dev/null
+++ b/src/InterwikiSorter.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace InterwikiSorting;
+
+/**
+ * Language sorting utility functions.
+ *
+ * @license GPL-2.0+
+ * @author Nikola Smolenski <[email protected]>
+ * @author Katie Filbert < [email protected] >
+ * @author Thiemo Mättig
+ */
+class InterwikiSorter {
+
+ /**
+ * @see Documentation of "sort" and "interwikiSortOrders" options in
docs/options.wiki.
+ */
+ const SORT_CODE = 'code';
+
+ /**
+ * @var array[]
+ */
+ private $sortOrders;
+
+ /**
+ * @var string
+ */
+ private $sort;
+
+ /**
+ * @var string[]
+ */
+ private $sortPrepend;
+
+ /**
+ * @var int[]|null
+ */
+ private $sortOrder = null;
+
+ /**
+ * @param string $sort
+ * @param array[] $sortOrders
+ * @param string[] $sortPrepend
+ */
+ public function __construct( $sort, array $sortOrders = array(), array
$sortPrepend = array() ) {
+ $this->sort = $sort;
+ $this->sortOrders = $sortOrders;
+ $this->sortPrepend = $sortPrepend;
+ }
+
+ /**
+ * Sort an array of links in-place
+ * @version Copied from InterlanguageExtension rev 114818
+ *
+ * @param string[] $links
+ *
+ * @return string[]
+ */
+ public function sortLinks( array $links ) {
+ if ( $this->sortOrder === null ) {
+ $this->sortOrder = $this->buildSortOrder( $this->sort,
$this->sortOrders );
+ }
+
+ // Prepare the array for sorting.
+ foreach ( $links as $k => $langLink ) {
+ $links[$k] = explode( ':', $langLink, 2 );
+ }
+
+ usort( $links, array( $this, 'compareLinks' ) );
+
+ // Restore the sorted array.
+ foreach ( $links as $k => $langLink ) {
+ $links[$k] = implode( ':', $langLink );
+ }
+
+ return $links;
+ }
+
+ /**
+ * usort() callback function, compares the links on the basis of
$sortOrder
+ *
+ * @param string[] $a
+ * @param string[] $b
+ *
+ * @return int
+ */
+ private function compareLinks( array $a, array $b ) {
+ $a = $a[0];
+ $b = $b[0];
+
+ if ( $a === $b ) {
+ return 0;
+ }
+
+ $aIndex = array_key_exists( $a, $this->sortOrder ) ?
$this->sortOrder[$a] : null;
+ $bIndex = array_key_exists( $b, $this->sortOrder ) ?
$this->sortOrder[$b] : null;
+
+ if ( $aIndex === $bIndex ) {
+ // If we encounter multiple unknown languages, which
may happen if the sort table is not
+ // updated, we list them alphabetically.
+ return strcmp( $a, $b );
+ } elseif ( $aIndex === null ) {
+ // Unknown languages must go under the known languages.
+ return 1;
+ } elseif ( $bIndex === null ) {
+ return -1;
+ } else {
+ return $aIndex - $bIndex;
+ }
+ }
+
+ /**
+ * Build sort order to be used by compareLinks().
+ *
+ * @param string $sort
+ * @param array[] $sortOrders
+ *
+ * @return int[]
+ */
+ private function buildSortOrder( $sort, array $sortOrders ) {
+ if ( $sort === self::SORT_CODE ) {
+ // The concept of known/unknown languages is irrelevant
in strict code order.
+ $sortOrder = array();
+ } elseif ( !array_key_exists( $sort, $sortOrders ) ) {
+ // Something went wrong, but we can use default "code"
order.
+ wfDebugLog(
+ __CLASS__,
+ __FUNCTION__ . ': Invalid or unknown sort order
specified for interwiki links.'
+ );
+ $sortOrder = array();
+ } else {
+ $sortOrder = $sortOrders[$sort];
+ }
+
+ if ( $this->sortPrepend !== array() ) {
+ $sortOrder = array_unique( array_merge(
$this->sortPrepend, $sortOrder ) );
+ }
+
+ return array_flip( $sortOrder );
+ }
+
+}
diff --git a/src/InterwikiSortingHookHandlers.php
b/src/InterwikiSortingHookHandlers.php
new file mode 100644
index 0000000..b380590
--- /dev/null
+++ b/src/InterwikiSortingHookHandlers.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace InterwikiSorting;
+
+use MediaWiki\MediaWikiServices;
+use ParserOutput;
+use Title;
+
+class InterwikiSortingHookHandlers {
+
+ /**
+ * @var InterwikiSorter
+ */
+ private $interwikiSorter;
+
+ /**
+ * @var bool
+ */
+ private $alwaysSort;
+
+ /**
+ * @var int[]
+ */
+ private $namespaces;
+
+ public static function newFromGlobalState() {
+ $config = MediaWikiServices::getInstance()->getMainConfig();
+
+ return new InterwikiSortingHookHandlers(
+ new InterwikiSorter(
+ $config->get( 'InterwikiSortingSort' ),
+ $config->get( 'InterwikiSortOrders' ),
+ $config->get( 'InterwikiSortingSortPrepend' )
+ ),
+ $config->get( 'InterwikiSortingNamespaces' ),
+ $config->get( 'InterwikiSortingAlwaysSort' )
+ );
+ }
+
+ /**
+ * @param InterwikiSorter $sorter
+ * @param int[] $namespaces
+ * @param boolean $alwaysSort
+ */
+ public function __construct(
+ InterwikiSorter $sorter,
+ $namespaces,
+ $alwaysSort
+ ) {
+ $this->interwikiSorter = $sorter;
+ $this->namespaces = $namespaces;
+ $this->alwaysSort = $alwaysSort;
+ }
+
+ /**
+ * Hook runs after internal parsing
+ * @see
https://www.mediawiki.org/wiki/Manual:Hooks/ContentAlterParserOutput
+ *
+ * @param Title $title
+ * @param ParserOutput $parserOutput
+ *
+ * @return bool
+ */
+ public function doContentAlterParserOutput( Title $title, ParserOutput
$parserOutput ) {
+ if ( !$title->inNamespaces( $this->namespaces ) ) {
+ // shorten out
+ return true;
+ }
+
+ if ( $this->alwaysSort ) {
+ $interwikiLinks = $parserOutput->getLanguageLinks();
+ $sortedLinks = $this->interwikiSorter->sortLinks(
$interwikiLinks );
+ $parserOutput->setLanguageLinks( $sortedLinks );
+ }
+
+ return true;
+ }
+
+}
diff --git a/src/InterwikiSortingHooks.php b/src/InterwikiSortingHooks.php
new file mode 100644
index 0000000..29dd769
--- /dev/null
+++ b/src/InterwikiSortingHooks.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace InterwikiSorting;
+
+use Content;
+use MediaWiki;
+use ParserOutput;
+use Title;
+
+class InterwikiSortingHooks {
+
+ /**
+ * @param Content $content
+ * @param Title $title
+ * @param ParserOutput $parserOutput
+ */
+ public static function onContentAlterParserOutput(
+ Content $content,
+ Title $title,
+ ParserOutput $parserOutput
+ ) {
+ // this hook tries to access repo SiteLinkTable
+ // it interferes with any test that parses something, like a
page or a message
+ if ( defined( 'MW_PHPUNIT_TEST' ) ) {
+ return;
+ }
+
+ $handler = ParserOutputUpdateHookHandlers::newFromGlobalState();
+ $handler->doContentAlterParserOutput( $title, $parserOutput );
+ }
+
+ public static function onBeforeInitialize(
+ /* Deliberately ignore all params ( We dont need them ) */
+ ) {
+ global $wgHooks;
+ /**
+ * The ContentAlterParserOutput hook is registered in the
BeforeInitialize so that
+ * the sorting of interwiki links is always done after anything
else might change the
+ * ParserOutput.
+ * Hooks::register() can not be used due to the array_merge in
Hooks::getHandlers()
+ * which will return hooks in $wgHooks last.
+ */
+ $wgHooks['ContentAlterParserOutput'][] =
+ array( InterwikiSortingHooks::class,
'onContentAlterParserOutput' );
+ }
+
+}
diff --git a/tests/phpunit/InterwikiSorterTest.php
b/tests/phpunit/InterwikiSorterTest.php
new file mode 100644
index 0000000..6a1e630
--- /dev/null
+++ b/tests/phpunit/InterwikiSorterTest.php
@@ -0,0 +1,134 @@
+<?php
+
+namespace InterwikiSorting\Tests;
+
+use InterwikiSorting\InterwikiSorter;
+
+/**
+ * @covers InterwikiSorting\InterwikiSorter
+ *
+ * @license GPL-2.0+
+ * @author Katie Filbert < [email protected] >
+ */
+class InterwikiSorterTest extends \PHPUnit_Framework_TestCase {
+
+ public function sortOrdersProvider() {
+ return array(
+ 'alphabetic' => array( 'ar', 'de', 'en', 'fr', 'ks',
'rn', 'ky', 'hu', 'ja', 'pt' ),
+ 'alphabetic_revised' => array( 'ar', 'de', 'en', 'fr',
'ks', 'ky', 'rn', 'hu', 'ja', 'pt' ),
+ 'alphabetic_sr' => array( 'ar', 'de', 'en', 'fr', 'ky',
'rn', 'ks', 'ja', 'hu', 'pt' ),
+ 'mycustomorder' => array( 'de', 'ja', 'pt', 'hu', 'en'
),
+ );
+ }
+
+ public function constructorProvider() {
+ $sortOrders = $this->sortOrdersProvider();
+ return array(
+ array( 'code', $sortOrders, array() ),
+ array( 'code', $sortOrders, array( 'en' ) )
+ );
+ }
+
+ /**
+ * @dataProvider constructorProvider
+ */
+ public function testConstructor( $sort, $sortOrders, $sortPrepend ) {
+ $interwikiSorter = new InterwikiSorter( $sort, $sortOrders,
$sortPrepend );
+ $this->assertInstanceOf( InterwikiSorter::class,
$interwikiSorter );
+ }
+
+ public function sortLinksProvider() {
+ $sortOrders = $this->sortOrdersProvider();
+ $links = array( 'fr', 'ky', 'hu', 'ar', 'ks', 'ja', 'de', 'en',
'pt', 'rn' );
+
+ return array(
+ array(
+ $links, 'code', $sortOrders, array(),
+ array( 'ar', 'de', 'en', 'fr', 'hu', 'ja',
'ks', 'ky', 'pt', 'rn' )
+ ),
+ array(
+ $links, 'code', $sortOrders, array( 'en' ),
+ array( 'en', 'ar', 'de', 'fr', 'hu', 'ja',
'ks', 'ky', 'pt', 'rn' )
+ ),
+ array(
+ $links, 'alphabetic', $sortOrders, array(),
+ $sortOrders['alphabetic']
+ ),
+ array(
+ $links, 'alphabetic', $sortOrders, array( 'en',
'ja' ),
+ array( 'en', 'ja', 'ar', 'de','fr', 'ks', 'rn',
'ky', 'hu', 'pt' )
+ ),
+ array(
+ $links, 'alphabetic_revised', $sortOrders,
array(),
+ $sortOrders['alphabetic_revised']
+ ),
+ array(
+ $links, 'alphabetic_revised', $sortOrders,
array( 'hu' ),
+ array( 'hu', 'ar', 'de', 'en', 'fr', 'ks',
'ky', 'rn', 'ja', 'pt' )
+ ),
+ array(
+ array( 'ja', 'de', 'pt', 'en', 'hu' ),
'mycustomorder', $sortOrders, array(),
+ $sortOrders['mycustomorder']
+ ),
+ array(
+ array( 'x2', 'x1', 'x3' ),
+ 'alphabetic',
+ array( 'alphabetic' => array() ),
+ array(),
+ array( 'x1', 'x2', 'x3' )
+ ),
+ array(
+ array( 'x2', 'x1', 'en', 'de', 'a2', 'a1' ),
+ 'alphabetic',
+ $sortOrders,
+ array(),
+ array( 'de', 'en', 'a1', 'a2', 'x1', 'x2' )
+ ),
+ array(
+ array( 'f', 'd', 'b', 'a', 'c', 'e' ),
+ 'alphabetic',
+ array( 'alphabetic' => array( 'c', 'a' ) ),
+ array( 'e' ),
+ array( 'e', 'c', 'a', 'b', 'd', 'f' )
+ ),
+ 'Strict code order' => array(
+ array( 'f', 'd', 'b', 'a', 'c', 'e' ),
+ 'code',
+ array( 'alphabetic' => array( 'c', 'a' ) ), //
this should be ignored
+ array( 'e' ), // prepend
+ array( 'e', 'a', 'b', 'c', 'd', 'f' )
+ ),
+ 'Code w/o alphabetic' => array(
+ array( 'c', 'b', 'a' ),
+ 'code',
+ array(),
+ array(),
+ array( 'a', 'b', 'c' )
+ ),
+ array(
+ array( 'a', 'b', 'k', 'x' ),
+ 'alphabetic',
+ array( 'alphabetic' => array( 'x', 'k', 'a' ) ),
+ array(),
+ array( 'x', 'k', 'a', 'b' )
+ ),
+ 'Fall back to code order' => array(
+ array( 'b', 'a' ),
+ 'invalid',
+ array(),
+ array(),
+ array( 'a', 'b' )
+ )
+ );
+ }
+
+ /**
+ * @dataProvider sortLinksProvider
+ */
+ public function testSortLinks( array $links, $sort, array $sortOrders,
$sortPrepend, $expected ) {
+ $interwikiSorter = new InterwikiSorter( $sort, $sortOrders,
$sortPrepend );
+ $sortedLinks = $interwikiSorter->sortLinks( $links );
+ $this->assertEquals( $expected, $sortedLinks );
+ }
+
+}
diff --git a/tests/phpunit/InterwikiSortingHooksTest.php
b/tests/phpunit/InterwikiSortingHooksTest.php
new file mode 100644
index 0000000..4d559f5
--- /dev/null
+++ b/tests/phpunit/InterwikiSortingHooksTest.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace InterwikiSorting\Tests;
+
+use Hooks;
+use InterwikiSorting\InterwikiSortingHooks;
+use MediaWiki;
+use MediaWikiTestCase;
+
+/**
+ * @covers InterwikiSorting\InterwikiSortingHooks
+ *
+ * @license GPL-2.0+
+ * @author Addshore
+ */
+class InterwikiSortingHooksTest extends MediaWikiTestCase {
+
+ public function testHooksAreCorrectlyRegistered() {
+ $initHook = InterwikiSortingHooks::class .
'::onBeforeInitialize';
+ $finalHook = [ InterwikiSortingHooks::class,
'onContentAlterParserOutput' ];
+
+ // Make sure the first hook has been registered
+ $this->assertContains( $initHook, Hooks::getHandlers(
'BeforeInitialize' ) );
+
+ // Fire the init hook which should register the second hook.
+ // In PHP7 we could just do $initHook();
+ InterwikiSortingHooks::onBeforeInitialize();
+
+ // Make sure that the hook has been registered and is at the
end of the list.
+ $onContentAlterParserOutputHooks = Hooks::getHandlers(
'ContentAlterParserOutput' );
+ $this->assertContains( $finalHook,
$onContentAlterParserOutputHooks );
+ $this->assertEquals(
+ $finalHook,
+ end( $onContentAlterParserOutputHooks ),
+ 'Hook should be the last to be fired'
+ );
+
+ }
+
+}
\ No newline at end of file
--
To view, visit https://gerrit.wikimedia.org/r/313979
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ia9a0190f4b992471b32ec796b7876ec9f3ac8ff1
Gerrit-PatchSet: 9
Gerrit-Project: mediawiki/extensions/InterwikiSorting
Gerrit-Branch: master
Gerrit-Owner: Addshore <[email protected]>
Gerrit-Reviewer: Addshore <[email protected]>
Gerrit-Reviewer: Hashar <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: Tobias Gritschacher <[email protected]>
Gerrit-Reviewer: WMDE-Fisch <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits