jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/398635 )
Change subject: Move classes to own files ...................................................................... Move classes to own files Makes MediaWiki.Files.OneClassPerFile.MultipleFound pass Change-Id: I358bc50deee92964cdc25d1912708cfa2f5cc081 --- M .phpcs.xml M Collection.php R CollectionProposals.php A CollectionSuggest.php A rendering/CollectionAPIResult.php R rendering/CollectionRenderingAPI.php A rendering/MWServeRenderingAPI.php 7 files changed, 336 insertions(+), 316 deletions(-) Approvals: Florianschmidtwelzow: Looks good to me, approved jenkins-bot: Verified diff --git a/.phpcs.xml b/.phpcs.xml index 10a4c41..1d566f2 100644 --- a/.phpcs.xml +++ b/.phpcs.xml @@ -6,7 +6,6 @@ <exclude name="MediaWiki.Commenting.FunctionComment.MissingParamComment" /> <exclude name="MediaWiki.Commenting.FunctionComment.WrongStyle" /> <exclude name="MediaWiki.Files.ClassMatchesFilename.NotMatch" /> - <exclude name="MediaWiki.Files.OneClassPerFile.MultipleFound" /> <exclude name="MediaWiki.WhiteSpace.SpaceBeforeSingleLineComment.NewLineComment"/> <exclude name="MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName"/> </rule> diff --git a/Collection.php b/Collection.php index 2f6f21a..3e60c5e 100644 --- a/Collection.php +++ b/Collection.php @@ -180,8 +180,8 @@ $wgAutoloadClasses['SpecialCollection'] = __DIR__ . '/Collection.body.php'; $wgAutoloadClasses['CollectionSession'] = __DIR__ . '/Collection.session.php'; $wgAutoloadClasses['CollectionHooks'] = __DIR__ . '/Collection.hooks.php'; -$wgAutoloadClasses['CollectionSuggest'] = __DIR__ . '/Collection.suggest.php'; -$wgAutoloadClasses['CollectionProposals'] = __DIR__ . '/Collection.suggest.php'; +$wgAutoloadClasses['CollectionSuggest'] = __DIR__ . '/CollectionSuggest.php'; +$wgAutoloadClasses['CollectionProposals'] = __DIR__ . '/CollectionProposals.php'; $wgAutoloadClasses['SpecialRenderBook'] = __DIR__ . '/SpecialRenderBook.php'; $wgAutoloadClasses[\MediaWiki\Extensions\Collection\DataProvider::class] @@ -214,9 +214,9 @@ $wgAutoloadClasses['CollectionSuggestTemplate'] = __DIR__ . '/templates/CollectionSuggestTemplate.php'; -$wgAutoloadClasses['CollectionRenderingAPI'] = __DIR__ . '/RenderingAPI.php'; -$wgAutoloadClasses['MWServeRenderingAPI'] = __DIR__ . '/RenderingAPI.php'; -$wgAutoloadClasses['CollectionAPIResult'] = __DIR__ . '/RenderingAPI.php'; +$wgAutoloadClasses['CollectionRenderingAPI'] = __DIR__ . '/rendering/CollectionRenderingAPI.php'; +$wgAutoloadClasses['MWServeRenderingAPI'] = __DIR__ . '/rendering/MWServeRenderingAPI.php'; +$wgAutoloadClasses['CollectionAPIResult'] = __DIR__ . '/rendering/CollectionAPIResult.php'; $wgMessagesDirs['Collection'] = __DIR__ . '/i18n'; $wgExtensionMessagesFiles['CollectionAlias'] = __DIR__ . '/Collection.alias.php'; diff --git a/Collection.suggest.php b/CollectionProposals.php similarity index 62% rename from Collection.suggest.php rename to CollectionProposals.php index 7d1e142..42d7d77 100644 --- a/Collection.suggest.php +++ b/CollectionProposals.php @@ -21,208 +21,6 @@ */ /** - * Class: CollectionSuggest - * - * This class contains only static methods, so theres no need for a constructer. - * When the page Special:Book/suggest/ is loaded the method run() is called. - * Ajax calles refresh(). - * When clearing a book the method clear() should be called. - */ -class CollectionSuggest { - /** - * =============================================================================== - * public methods - * =============================================================================== - */ - - /** - * Main entrypoint - * - * @param string $mode - * 'add' => add one title to the book. - * 'addAll' => Add a list of titles to the book. - * 'ban' => Ban a title from the proposals. - * 'unban' => Undo a ban. - * 'remove' => Remove a title from the book, and ban it. - * 'removeOnly' => Remove a title without banning it. - * @param string|string[] $param Name of the article to be added, banned - * or removed, or a list of article names to be added. - */ - public static function run( $mode = '', $param = '' ) { - global $wgOut; - - if ( !CollectionSession::hasSession() ) { - CollectionSession::startSession(); - } - - $template = self::getCollectionSuggestTemplate( $mode, $param ); - $wgOut->setPageTitle( wfMessage( 'coll-suggest_title' ) ); - $wgOut->addModules( 'ext.collection.suggest' ); - $wgOut->addTemplate( $template ); - } - - /** - * Entrypoint for Ajax - * - * @param string $mode - * 'add' => add one title to the book. - * 'addAll' => Add a list of titles to the book. - * 'ban' => Ban a title from the proposals. - * 'unban' => Undo a ban. - * 'remove' => Remove a title from the book, and ban it. - * 'removeOnly' => Remove a title without banning it. - * @param string|string[] $param Name of the article to be added, banned - * or removed, or a list of article names to be added. - * @return string html-code for the proposallist and the memberlist - */ - public static function refresh( $mode, $param ) { - $template = self::getCollectionSuggestTemplate( $mode, $param ); - return [ - 'suggestions_html' => $template->getProposalList(), - 'members_html' => $template->getMemberList(), - 'num_pages' => wfMessage( 'coll-n_pages' ) - ->numParams( CollectionSession::countArticles() ) - ->escaped(), - ]; - } - - /** - * @param string $lastAction - * @param string|string[] $article - * @return array - */ - public static function undo( $lastAction, $article ) { - switch ( $lastAction ) { - case 'add': - $template = self::getCollectionSuggestTemplate( 'removeonly', $article ); - break; - case 'ban': - $template = self::getCollectionSuggestTemplate( 'unban', $article ); - break; - case 'remove': - $template = self::getCollectionSuggestTemplate( 'add', $article ); - break; - } - return [ - 'suggestions_html' => $template->getProposalList(), - 'members_html' => $template->getMemberList(), - ]; - } - - // remove the suggestion data from the session - public static function clear() { - if ( isset( $_SESSION['wsCollectionSuggestBan'] ) ) { - unset( $_SESSION['wsCollectionSuggestBan'] ); - } - if ( isset( $_SESSION['wsCollectionSuggestProp'] ) ) { - unset( $_SESSION['wsCollectionSuggestProp'] ); - } - } - - /** - * =============================================================================== - * private methods - * =============================================================================== - */ - - /** - * @param string $article - * @return mixed - */ - private static function unban( $article ) { - if ( !isset( $_SESSION['wsCollectionSuggestBan'] ) ) { - return; - } - $bans = $_SESSION['wsCollectionSuggestBan']; - $newbans = []; - foreach ( $bans as $ban ) { - if ( $ban != $article ) { - $newbans[] = $ban; - } - } - $_SESSION['wsCollectionSuggestBan'] = $newbans; - } - - /** - * Update the session and return the template - * - * @param string $mode - * 'add' => add one title to the book. - * 'addAll' => Add a list of titles to the book. - * 'ban' => Ban a title from the proposals. - * 'unban' => Undo a ban. - * 'remove' => Remove a title from the book, and ban it. - * 'removeOnly' => Remove a title without banning it. - * @param string|string[] $param Name of the article to be added, banned - * or removed, or a list of article names to be added. - * @return CollectionSuggestTemplate the template for the wikipage - */ - private static function getCollectionSuggestTemplate( $mode, $param ) { - global $wgCollectionMaxSuggestions; - - if ( !isset( $_SESSION['wsCollectionSuggestBan'] ) || $mode == 'resetbans' ) { - $_SESSION['wsCollectionSuggestBan'] = []; - } - if ( !isset( $_SESSION['wsCollectionSuggestProp'] ) ) { - $_SESSION['wsCollectionSuggestProp'] = []; - } - - switch ( $mode ) { - case 'add': - SpecialCollection::addArticleFromName( NS_MAIN, $param ); - self::unban( $param ); - break; - case 'ban': - $_SESSION['wsCollectionSuggestBan'][] = $param; - break; - case 'remove': - SpecialCollection::removeArticleFromName( NS_MAIN, $param ); - $_SESSION['wsCollectionSuggestBan'][] = $param; - break; - case 'removeonly': // remove w/out banning (for undo) - SpecialCollection::removeArticleFromName( NS_MAIN, $param ); - break; - case 'unban': // for undo - self::unban( $param ); - break; - } - - $template = new CollectionSuggestTemplate(); - $proposals = new CollectionProposals( - $_SESSION['wsCollection'], - $_SESSION['wsCollectionSuggestBan'], - $_SESSION['wsCollectionSuggestProp'] - ); - - if ( $mode == 'addAll' ) { - self::addArticlesFromName( $param, $proposals ); - } - - $template->set( 'collection', $_SESSION['wsCollection'] ); - $template->set( 'proposals', $proposals->getProposals( $wgCollectionMaxSuggestions ) ); - $template->set( 'hasbans', $proposals->hasBans() ); - $template->set( 'num_pages', CollectionSession::countArticles() ); - - $_SESSION['wsCollectionSuggestProp'] = $proposals->getLinkList(); - - return $template; - } - - /** - * Add some articles and update the book of the Proposal-Object - * - * @param array $articleList with the names of the articles to be added - * @param CollectionProposals $prop the proposal Object - */ - private static function addArticlesFromName( $articleList, $prop ) { - foreach ( $articleList as $article ) { - SpecialCollection::addArticleFromName( NS_MAIN, $article ); - } - $prop->setCollection( $_SESSION['wsCollection'] ); - } -} - -/** * class: CollectionProposals * * it needs 3 Lists: @@ -235,8 +33,6 @@ * a list with the bookarticles as first and information about the outgoing * links of that article as second dimension can be accessed with the method * getLinkList() - * - * * * the Class can only sort the proposals, if it can access the function compareProps */ diff --git a/CollectionSuggest.php b/CollectionSuggest.php new file mode 100644 index 0000000..858e42e --- /dev/null +++ b/CollectionSuggest.php @@ -0,0 +1,223 @@ +<?php +/** + * Collection Extension for MediaWiki + * + * Copyright (C) 2008-2009, PediaPress GmbH + * + * 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. + * http://www.gnu.org/copyleft/gpl.html + */ + +/** + * Class: CollectionSuggest + * + * This class contains only static methods, so theres no need for a constructer. + * When the page Special:Book/suggest/ is loaded the method run() is called. + * Ajax calles refresh(). + * When clearing a book the method clear() should be called. + */ +class CollectionSuggest { + /** + * =============================================================================== + * public methods + * =============================================================================== + */ + + /** + * Main entrypoint + * + * @param string $mode + * 'add' => add one title to the book. + * 'addAll' => Add a list of titles to the book. + * 'ban' => Ban a title from the proposals. + * 'unban' => Undo a ban. + * 'remove' => Remove a title from the book, and ban it. + * 'removeOnly' => Remove a title without banning it. + * @param string|string[] $param Name of the article to be added, banned + * or removed, or a list of article names to be added. + */ + public static function run( $mode = '', $param = '' ) { + global $wgOut; + + if ( !CollectionSession::hasSession() ) { + CollectionSession::startSession(); + } + + $template = self::getCollectionSuggestTemplate( $mode, $param ); + $wgOut->setPageTitle( wfMessage( 'coll-suggest_title' ) ); + $wgOut->addModules( 'ext.collection.suggest' ); + $wgOut->addTemplate( $template ); + } + + /** + * Entrypoint for Ajax + * + * @param string $mode + * 'add' => add one title to the book. + * 'addAll' => Add a list of titles to the book. + * 'ban' => Ban a title from the proposals. + * 'unban' => Undo a ban. + * 'remove' => Remove a title from the book, and ban it. + * 'removeOnly' => Remove a title without banning it. + * @param string|string[] $param Name of the article to be added, banned + * or removed, or a list of article names to be added. + * @return string html-code for the proposallist and the memberlist + */ + public static function refresh( $mode, $param ) { + $template = self::getCollectionSuggestTemplate( $mode, $param ); + return [ + 'suggestions_html' => $template->getProposalList(), + 'members_html' => $template->getMemberList(), + 'num_pages' => wfMessage( 'coll-n_pages' ) + ->numParams( CollectionSession::countArticles() ) + ->escaped(), + ]; + } + + /** + * @param string $lastAction + * @param string|string[] $article + * @return array + */ + public static function undo( $lastAction, $article ) { + switch ( $lastAction ) { + case 'add': + $template = self::getCollectionSuggestTemplate( 'removeonly', $article ); + break; + case 'ban': + $template = self::getCollectionSuggestTemplate( 'unban', $article ); + break; + case 'remove': + $template = self::getCollectionSuggestTemplate( 'add', $article ); + break; + } + return [ + 'suggestions_html' => $template->getProposalList(), + 'members_html' => $template->getMemberList(), + ]; + } + + // remove the suggestion data from the session + public static function clear() { + if ( isset( $_SESSION['wsCollectionSuggestBan'] ) ) { + unset( $_SESSION['wsCollectionSuggestBan'] ); + } + if ( isset( $_SESSION['wsCollectionSuggestProp'] ) ) { + unset( $_SESSION['wsCollectionSuggestProp'] ); + } + } + + /** + * =============================================================================== + * private methods + * =============================================================================== + */ + + /** + * @param string $article + * @return mixed + */ + private static function unban( $article ) { + if ( !isset( $_SESSION['wsCollectionSuggestBan'] ) ) { + return; + } + $bans = $_SESSION['wsCollectionSuggestBan']; + $newbans = []; + foreach ( $bans as $ban ) { + if ( $ban != $article ) { + $newbans[] = $ban; + } + } + $_SESSION['wsCollectionSuggestBan'] = $newbans; + } + + /** + * Update the session and return the template + * + * @param string $mode + * 'add' => add one title to the book. + * 'addAll' => Add a list of titles to the book. + * 'ban' => Ban a title from the proposals. + * 'unban' => Undo a ban. + * 'remove' => Remove a title from the book, and ban it. + * 'removeOnly' => Remove a title without banning it. + * @param string|string[] $param Name of the article to be added, banned + * or removed, or a list of article names to be added. + * @return CollectionSuggestTemplate the template for the wikipage + */ + private static function getCollectionSuggestTemplate( $mode, $param ) { + global $wgCollectionMaxSuggestions; + + if ( !isset( $_SESSION['wsCollectionSuggestBan'] ) || $mode == 'resetbans' ) { + $_SESSION['wsCollectionSuggestBan'] = []; + } + if ( !isset( $_SESSION['wsCollectionSuggestProp'] ) ) { + $_SESSION['wsCollectionSuggestProp'] = []; + } + + switch ( $mode ) { + case 'add': + SpecialCollection::addArticleFromName( NS_MAIN, $param ); + self::unban( $param ); + break; + case 'ban': + $_SESSION['wsCollectionSuggestBan'][] = $param; + break; + case 'remove': + SpecialCollection::removeArticleFromName( NS_MAIN, $param ); + $_SESSION['wsCollectionSuggestBan'][] = $param; + break; + case 'removeonly': // remove w/out banning (for undo) + SpecialCollection::removeArticleFromName( NS_MAIN, $param ); + break; + case 'unban': // for undo + self::unban( $param ); + break; + } + + $template = new CollectionSuggestTemplate(); + $proposals = new CollectionProposals( + $_SESSION['wsCollection'], + $_SESSION['wsCollectionSuggestBan'], + $_SESSION['wsCollectionSuggestProp'] + ); + + if ( $mode == 'addAll' ) { + self::addArticlesFromName( $param, $proposals ); + } + + $template->set( 'collection', $_SESSION['wsCollection'] ); + $template->set( 'proposals', $proposals->getProposals( $wgCollectionMaxSuggestions ) ); + $template->set( 'hasbans', $proposals->hasBans() ); + $template->set( 'num_pages', CollectionSession::countArticles() ); + + $_SESSION['wsCollectionSuggestProp'] = $proposals->getLinkList(); + + return $template; + } + + /** + * Add some articles and update the book of the Proposal-Object + * + * @param array $articleList with the names of the articles to be added + * @param CollectionProposals $prop the proposal Object + */ + private static function addArticlesFromName( $articleList, $prop ) { + foreach ( $articleList as $article ) { + SpecialCollection::addArticleFromName( NS_MAIN, $article ); + } + $prop->setCollection( $_SESSION['wsCollection'] ); + } +} diff --git a/rendering/CollectionAPIResult.php b/rendering/CollectionAPIResult.php new file mode 100644 index 0000000..9276d7e --- /dev/null +++ b/rendering/CollectionAPIResult.php @@ -0,0 +1,61 @@ +<?php + +/** + * A wrapper for data returned by the API + */ +class CollectionAPIResult { + /** @var array: Decoded JSON returned by server */ + public $response = []; + + /** + * @param string|null $data Data returned by HTTP request + */ + public function __construct( $data ) { + if ( $data ) { + $this->response = FormatJson::decode( $data, true ); + if ( $this->response === null ) { + wfDebugLog( 'collection', "Server returned bogus data: $data" ); + $this->response = null; + } + if ( $this->isError() ) { + wfDebugLog( 'collection', "Server returned error: {$this->getError()}" ); + } + } + } + + /** + * Returns data for specified key(s) + * Has variable number of parameters, e.g. get( 'foo', 'bar', 'baz' ) + * @param string $key + * @return mixed + */ + public function get( $key /*, ... */ ) { + $args = func_get_args(); + $val = $this->response; + foreach ( $args as $arg ) { + if ( !isset( $val[$arg] ) ) { + return ''; + } + $val = $val[$arg]; + } + return $val; + } + + /** + * @return bool + */ + public function isError() { + return !$this->response + || ( isset( $this->response['error'] ) && $this->response['error'] ); + } + + /** + * @return string Internal (not user-facing) error description + */ + protected function getError() { + if ( isset( $this->response['error'] ) ) { + return $this->response['error']; + } + return '(error unknown)'; + } +} diff --git a/RenderingAPI.php b/rendering/CollectionRenderingAPI.php similarity index 72% rename from RenderingAPI.php rename to rendering/CollectionRenderingAPI.php index 2593416..a54b34f 100644 --- a/RenderingAPI.php +++ b/rendering/CollectionRenderingAPI.php @@ -274,109 +274,3 @@ return FormatJson::encode( $result ); } } - -/** - * API for PediaPress' mw-serve - */ -class MWServeRenderingAPI extends CollectionRenderingAPI { - protected function makeRequest( $command, array $params ) { - global $wgCollectionMWServeURL, $wgCollectionMWServeCredentials, - $wgCollectionFormatToServeURL, $wgCollectionCommandToServeURL; - - $serveURL = $wgCollectionMWServeURL; - if ( $this->writer ) { - if ( isset( $wgCollectionFormatToServeURL[ $this->writer ] ) ) { - $serveURL = $wgCollectionFormatToServeURL[ $this->writer ]; - } - $params['writer'] = $this->writer; - } - - $params['command'] = $command; - if ( isset( $wgCollectionCommandToServeURL[ $command ] ) ) { - $serveURL = $wgCollectionCommandToServeURL[ $command ]; - } - if ( $wgCollectionMWServeCredentials ) { - $params['login_credentials'] = $wgCollectionMWServeCredentials; - } - // If $serveURL has a | in it, we need to use a proxy. - list( $proxy, $serveURL ) = array_pad( explode( '|', $serveURL, 2 ), -2, '' ); - - if ( !$serveURL ) { - wfDebugLog( 'collection', 'The mwlib/OCG render server URL isn\'t configured.' ); - - return new CollectionAPIResult( false ); - } - - $response = Http::post( - $serveURL, - [ 'postData' => $params, 'proxy' => $proxy ], - __METHOD__ - ); - - if ( $response === false ) { - wfDebugLog( 'collection', "Request to $serveURL resulted in error" ); - } - - return new CollectionAPIResult( $response ); - } -} - -/** - * A wrapper for data returned by the API - */ -class CollectionAPIResult { - /** @var array: Decoded JSON returned by server */ - public $response = []; - - /** - * @param string|null $data Data returned by HTTP request - */ - public function __construct( $data ) { - if ( $data ) { - $this->response = FormatJson::decode( $data, true ); - if ( $this->response === null ) { - wfDebugLog( 'collection', "Server returned bogus data: $data" ); - $this->response = null; - } - if ( $this->isError() ) { - wfDebugLog( 'collection', "Server returned error: {$this->getError()}" ); - } - } - } - - /** - * Returns data for specified key(s) - * Has variable number of parameters, e.g. get( 'foo', 'bar', 'baz' ) - * @param string $key - * @return mixed - */ - public function get( $key /*, ... */ ) { - $args = func_get_args(); - $val = $this->response; - foreach ( $args as $arg ) { - if ( !isset( $val[$arg] ) ) { - return ''; - } - $val = $val[$arg]; - } - return $val; - } - - /** - * @return bool - */ - public function isError() { - return !$this->response - || ( isset( $this->response['error'] ) && $this->response['error'] ); - } - - /** - * @return string Internal (not user-facing) error description - */ - protected function getError() { - if ( isset( $this->response['error'] ) ) { - return $this->response['error']; - } - return '(error unknown)'; - } -} diff --git a/rendering/MWServeRenderingAPI.php b/rendering/MWServeRenderingAPI.php new file mode 100644 index 0000000..d14b894 --- /dev/null +++ b/rendering/MWServeRenderingAPI.php @@ -0,0 +1,47 @@ +<?php + +/** + * API for PediaPress' mw-serve + */ +class MWServeRenderingAPI extends CollectionRenderingAPI { + protected function makeRequest( $command, array $params ) { + global $wgCollectionMWServeURL, $wgCollectionMWServeCredentials, + $wgCollectionFormatToServeURL, $wgCollectionCommandToServeURL; + + $serveURL = $wgCollectionMWServeURL; + if ( $this->writer ) { + if ( isset( $wgCollectionFormatToServeURL[ $this->writer ] ) ) { + $serveURL = $wgCollectionFormatToServeURL[ $this->writer ]; + } + $params['writer'] = $this->writer; + } + + $params['command'] = $command; + if ( isset( $wgCollectionCommandToServeURL[ $command ] ) ) { + $serveURL = $wgCollectionCommandToServeURL[ $command ]; + } + if ( $wgCollectionMWServeCredentials ) { + $params['login_credentials'] = $wgCollectionMWServeCredentials; + } + // If $serveURL has a | in it, we need to use a proxy. + list( $proxy, $serveURL ) = array_pad( explode( '|', $serveURL, 2 ), -2, '' ); + + if ( !$serveURL ) { + wfDebugLog( 'collection', 'The mwlib/OCG render server URL isn\'t configured.' ); + + return new CollectionAPIResult( false ); + } + + $response = Http::post( + $serveURL, + [ 'postData' => $params, 'proxy' => $proxy ], + __METHOD__ + ); + + if ( $response === false ) { + wfDebugLog( 'collection', "Request to $serveURL resulted in error" ); + } + + return new CollectionAPIResult( $response ); + } +} -- To view, visit https://gerrit.wikimedia.org/r/398635 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I358bc50deee92964cdc25d1912708cfa2f5cc081 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Collection Gerrit-Branch: master Gerrit-Owner: Umherirrender <umherirrender_de...@web.de> Gerrit-Reviewer: C. Scott Ananian <canan...@wikimedia.org> Gerrit-Reviewer: Florianschmidtwelzow <florian.schmidt.stargatewis...@gmail.com> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits