Pwirth has uploaded a new change for review. https://gerrit.wikimedia.org/r/274972
Change subject: [WIP] ResponsibleEditors: Used API instead of AjaxExportList ...................................................................... [WIP] ResponsibleEditors: Used API instead of AjaxExportList * Added tasks api * Added pages store api * Removed unused AjaxExportList methods * Reworked getter for RespEditors * Reworked caching mechanism => Requires 274970 Change-Id: I1631e9baf66b281be983d5101392466bf37bc7aa --- M ResponsibleEditors/ResponsibleEditors.class.php M ResponsibleEditors/ResponsibleEditors.setup.php A ResponsibleEditors/includes/api/BSApiResponsibleEditorsPagesStore.php A ResponsibleEditors/includes/api/BSApiTasksResponsibleEditors.php M ResponsibleEditors/includes/specials/SpecialResponsibleEditors.class.php 5 files changed, 247 insertions(+), 127 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/BlueSpiceExtensions refs/changes/72/274972/1 diff --git a/ResponsibleEditors/ResponsibleEditors.class.php b/ResponsibleEditors/ResponsibleEditors.class.php index ca8fb41..79eb8b3 100644 --- a/ResponsibleEditors/ResponsibleEditors.class.php +++ b/ResponsibleEditors/ResponsibleEditors.class.php @@ -33,8 +33,6 @@ class ResponsibleEditors extends BsExtensionMW { - protected static $aResponsibleEditorIdsByArticleId = array(); - protected static $aResponsibleEditorsByArticleId = array(); public function __construct() { @@ -176,7 +174,9 @@ //Make information about current pages RespEds available on client side $iArticleId = $out->getTitle()->getArticleID(); - $aResponsibleEditorIds = $this->getResponsibleEditorIdsByArticleId( $iArticleId ); + $aResponsibleEditorIds = static::getResponsibleEditorIds( + $iArticleId + ); $oData = new stdClass(); $oData->articleId = $iArticleId; $oData->editorIds = $aResponsibleEditorIds; @@ -250,7 +250,9 @@ */ public function applyTempPermissionsForRespEditor( Title $oTitle, User $oUser ) { $iArticleID = $oTitle->getArticleID(); - $aResponsibleEditorsIDs = $this->getResponsibleEditorIdsByArticleId( $iArticleID ); + $aResponsibleEditorsIDs = static::getResponsibleEditorIds( + $iArticleID + ); if ( !in_array( $oUser->getId(), $aResponsibleEditorsIDs ) ){ return false; @@ -337,7 +339,7 @@ } public function onBSUEModulePDFcollectMetaData($oTitle, $oPageDOM, &$aParams, $oDOMXPath, &$aMeta) { - $aEditors = $this->getResponsibleEditorIdsByArticleId($oTitle->getArticleId()); + $aEditors = static::getResponsibleEditorIds( $oTitle->getArticleId() ); $aEditorNames = array(); foreach ( $aEditors as $iEditorId ) { $aEditorNames[] = $this->mCore->getUserDisplayName(User::newFromId($iEditorId)); @@ -348,7 +350,7 @@ public function onBSBookshelfManagerGetBookDataRow($oBookTitle, $oBookRow) { $oBookRow->editors = array(); - $aEditors = $this->getResponsibleEditorIdsByArticleId($oBookRow->page_id); + $aEditors = static::getResponsibleEditorIds( $oBookRow->page_id ); foreach ( $aEditors as $iEditorId ) { $oBookRow->editors[] = array( 'id' => $iEditorId, @@ -477,7 +479,9 @@ public function userIsAllowedToChangeResponsibility($oCurrentUser, $oCurrentTitle) { //Check users permissions and/or if he is assigned as a responsible editor $bUserIsAllowedToChangeResponsiblity = false; - $aResponsibleEditorIds = $this->getResponsibleEditorIdsByArticleId($oCurrentTitle->getArticleId()); + $aResponsibleEditorIds = static::getResponsibleEditorIds( + $oCurrentTitle->getArticleId() + ); if ($oCurrentTitle->userCan('responsibleeditors-changeresponsibility') === true) { $bUserIsAllowedToChangeResponsiblity = true; } else { @@ -585,7 +589,7 @@ */ public function onArticleDeleteComplete($oArticle, $oUser, $sReason, $iArticleId) { //E-Mail notifcation - $aResponsibleEditorIds = $this->getResponsibleEditorIdsByArticleId($iArticleId); + $aResponsibleEditorIds = static::getResponsibleEditorIds( $iArticleId ); self::notifyResponsibleEditors($aResponsibleEditorIds, $oUser, array($oArticle->getTitle()), 'delete'); $dbw = wfGetDB(DB_MASTER); @@ -599,20 +603,20 @@ public function onArticleSaveComplete(&$article, &$user, $text, $summary, $minoredit, $watchthis, $sectionanchor, &$flags, $revision, &$status, $baseRevId) { $iArticleId = $article->getID(); - $aResponsibleEditorIds = $this->getResponsibleEditorIdsByArticleId($iArticleId); + $aResponsibleEditorIds = static::getResponsibleEditorIds( $iArticleId ); self::notifyResponsibleEditors($aResponsibleEditorIds, $user, array(Title::newFromID($iArticleId)), 'change'); return true; } public function onTitleMoveComplete(&$title, &$newtitle, &$user, $oldid, $newid) { - $aResponsibleEditorIds = $this->getResponsibleEditorIdsByArticleId($oldid); + $aResponsibleEditorIds = static::getResponsibleEditorIds( $oldid ); self::notifyResponsibleEditors($aResponsibleEditorIds, $user, array($title, $newtitle), 'move'); return true; } private function makeStateBarTopResponsibleEditorsEntries($iArticleId) { global $wgScriptPath; - $aResponsibleEditorIds = $this->getResponsibleEditorIdsByArticleId($iArticleId); + $aResponsibleEditorIds = static::getResponsibleEditorIds( $iArticleId ); if (empty($aResponsibleEditorIds)) return false; @@ -632,7 +636,7 @@ } private function makeStateBarBodyResponsibleEditorsEntries($iArticleId) { - $aResponsibleEditorIds = $this->getResponsibleEditorIdsByArticleId($iArticleId); + $aResponsibleEditorIds = static::getResponsibleEditorIds( $iArticleId ); if (empty($aResponsibleEditorIds)) return false; @@ -704,52 +708,6 @@ return true; } - //<editor-fold desc="AJAX Interfaces"> - public static function ajaxDeleteResponsibleEditorsForArticle() { - if ( BsCore::checkAccessAdmission( 'edit' ) === false ) return true; - global $wgRequest; - $oResponse = new BsXHRJSONResponse(); - $oResponse->status = BsXHRResponseStatus::ERROR; - - $iArticleId = $wgRequest->getInt( 'articleId', -1 ); - $aUserIDs = $wgRequest->getArray( 'articleId', array() ); - - $oRequestedTitle = Title::newFromId($iArticleId); - - if ($iArticleId === -1 || empty( $aUserIDs ) || $oRequestedTitle === null) { - $oResponse->shortMessage = wfMessage( 'bs-responsibleeditors-error-ajax-invalid-parameter' )->plain(); - echo $oResponse; - return; - } - - //TODO: prevent delete on specific variations - //$oCurrentUserResponsibleEditor = BsResponsibleEditor::newFromUser($oCurrentUser); - if ($oRequestedTitle->userCan('responsibleeditors-changeresponsibility') === false - //&& ( $oCurrentUserResponsibleEditor->isAssignedToArticleId($iArticleId) === false - //&& BsConfig::get('MW::ResponsibleEditors::ResponsibleEditorMayChangeAssignment') === true - ) { - $oResponse->shortMessage = wfMessage( 'bs-responsibleeditors-error-ajax-not-allowed' )->plain(); - echo $oResponse; - return; - } - - $dbw = wfGetDB(DB_MASTER); - $res = $dbw->delete( - 'bs_responsible_editors', - array( - 're_page_id' => $iArticleId, - 're_user_id' => $aUserIDs - ) - ); - - self::deleteResponsibleEditorsFromCache( $iArticleId ); - $oRequestedTitle->invalidateCache(); - - $oResponse->status = BsXHRResponseStatus::SUCCESS; - $oResponse->shortMessage = wfMessage( 'bs-responsibleeditors-success-ajax' )->plain(); - return $oResponse; - } - public static function ajaxGetActivatedNamespacesForCombobox() { if ( BsCore::checkAccessAdmission( 'edit' ) === false ) return true; $aNamespaces = array(); @@ -766,22 +724,6 @@ ); } return '{ namespaces: ' . json_encode($aNamespaces) . ' }'; - } - - public static function ajaxGetResponsibleEditorsByArticleId($iArticleId) { - if ( BsCore::checkAccessAdmission( 'edit' ) === false ) return true; - $aResponsibleEditorIds = BsExtensionManager::getExtension( 'ResponsibleEditors' )->getResponsibleEditorIdsByArticleId($iArticleId); - return json_encode($aResponsibleEditorIds); - } - - public static function ajaxGetListOfResponsibleEditorsForArticle() { - if ( BsCore::checkAccessAdmission( 'edit' ) === false ) return true; - global $wgRequest; - $iArticleId = $wgRequest->getInt( 'articleId', -1 ); - if ($iArticleId == -1) - return 'ERROR'; - $aListOfPossibleEditors = BsExtensionManager::getExtension( 'ResponsibleEditors' )->getListOfResponsibleEditorsForArticle($iArticleId); - return '{users: ' . json_encode($aListOfPossibleEditors) . '}'; } public function getListOfResponsibleEditorsForArticle($iArticleId) { @@ -905,8 +847,7 @@ $oPage->page_prefixedtext = $oTitle->getPrefixedText(); $oPage->users = array(); - $aEditorIDs = BsExtensionManager::getExtension( 'ResponsibleEditors' ) - ->getResponsibleEditorIdsByArticleId($row->page_id); + $aEditorIDs = static::getResponsibleEditorIds( $row->page_id ); $aEditorIDs = array_unique($aEditorIDs); foreach ($aEditorIDs as $iEditorID) { @@ -930,66 +871,124 @@ //</editor-fold> /** + * DEPRECATED * Helper function. Fetches database and returns array of user_id's of * responsible editors of an article + * @deprecated since version 2.23.3 * @param Integer $iArticleId The page_id of the article you want to retrieve the responsible editors for. * @return Array user_ids of responsible editors for given article */ public function getResponsibleEditorIdsByArticleId( $iArticleId, $bForceReload = false ) { - if( isset(self::$aResponsibleEditorIdsByArticleId[$iArticleId]) && $bForceReload === false ) - return self::$aResponsibleEditorIdsByArticleId[$iArticleId]; - - $this->getResponsibleEditorsByArticleId( $iArticleId, $bForceReload ); - - return self::$aResponsibleEditorIdsByArticleId[$iArticleId]; + return static::getResponsibleEditorIds( $iArticleId, $bForceReload ); } /** + * DEPRECATED * Helper function. Fetches database and returns array of responsible editors of an article + * @deprecated since version 2.23.3 * @param Integer $iArticleId The page_id of the article you want to retrieve the responsible editors for. * @return Array user_ids of responsible editors for given article */ public function getResponsibleEditorsByArticleId( $iArticleId, $bForceReload = false ) { - if( isset(self::$aResponsibleEditorsByArticleId[$iArticleId]) && $bForceReload === false ) + return static::getResponsibleEditors( $iArticleId, $bForceReload ); + } + + protected static function getResponsibleEditorsFromCache( $iArticleId ) { + if( isset(self::$aResponsibleEditorsByArticleId[$iArticleId]) ) { return self::$aResponsibleEditorsByArticleId[$iArticleId]; - - $aResponsibleEditorIds = array(); - $aResponsibleEditors = array(); - - $sKey = BsCacheHelper::getCacheKey( 'ResponsibleEditors', 'getResponsibleEditorsByArticleId', (int)$iArticleId ); - $aData = BsCacheHelper::get( $sKey ); - - if( $aData !== false ) { - wfDebugLog( 'BsMemcached' , __CLASS__.': Fetching ResponsibleEditors from cache' ); - self::$aResponsibleEditorIdsByArticleId[$iArticleId] = $aData['EditorIdsByArticleId']; - self::$aResponsibleEditorsByArticleId[$iArticleId] = $aData['EditorsByArticleId']; - } else { - wfDebugLog( 'BsMemcached' , __CLASS__.': Fetching ResponsibleEditors from DB' ); - $dbr = wfGetDB(DB_SLAVE); - $res = $dbr->select( - 'bs_responsible_editors', - '*', - array('re_page_id' => $iArticleId), - __METHOD__, - array('ORDER BY' => 're_position') - ); - - - foreach ($res as $row) { - $row->re_user_id = (int)$row->re_user_id; - $aResponsibleEditorIds[] = $row->re_user_id; - $aResponsibleEditors[] = $row; - } - - $aData = array(); - $aData['EditorIdsByArticleId'] = $aResponsibleEditorIds; - $aData['EditorsByArticleId'] = $aResponsibleEditors; - BsCacheHelper::set( $sKey, $aData ); - - self::$aResponsibleEditorIdsByArticleId[$iArticleId] = $aResponsibleEditorIds; - self::$aResponsibleEditorsByArticleId[$iArticleId] = $aResponsibleEditors; } - return $aResponsibleEditors; + $sKey = BsCacheHelper::getCacheKey( + 'ResponsibleEditors', + 'getResponsibleEditorsByArticleId', + (int)$iArticleId + ); + + if( $aData = BsCacheHelper::get( $sKey ) ) { + wfDebugLog( + 'BsMemcached', + __CLASS__.': Fetching ResponsibleEditors from cache' + ); + } + return $aData; + } + + protected static function appendResponsibleEditorsCache( $iArticleId, $aRespEditors = array() ) { + $sKey = BsCacheHelper::getCacheKey( + 'ResponsibleEditors', + 'getResponsibleEditorsByArticleId', + (int) $iArticleId + ); + BsCacheHelper::set( $sKey, $aRespEditors ); + self::$aResponsibleEditorsByArticleId[$iArticleId] = $aRespEditors; + + return $aRespEditors; + } + + /** + * Helper function. Fetches database and returns array of user_id's of + * responsible editors of an article + * @param Integer $iArticleId The page_id of the article you want to + * retrieve the responsible editors for. + * @return Array user_ids of responsible editors for given article + */ + public static function getResponsibleEditorIds( $iArticleId, $bForceReload = false ) { + $aRespEditors = static::getResponsibleEditors( + $iArticleId, + $bForceReload + ); + if( empty($aRespEditors) ) { + return $aRespEditors; + } + array_walk($aRespEditors, function( &$e ) { + $e = $e['re_user_id']; + }); + return $aRespEditors; + } + + /** + * Helper function. Fetches database and returns array of responsible + * editors of an article + * @param Integer $iArticleId The page_id of the article you want to + * retrieve the responsible editors for. + * @return Array of responsible editors for given article + */ + public static function getResponsibleEditors( $iArticleId, $bForceReload = false ) { + if( empty($iArticleId) ) { + return false; + } + + if( !$bForceReload ) { + $aRespEditors = self::getResponsibleEditorsFromCache( $iArticleId ); + if( $aRespEditors ) { + return $aRespEditors; + } + } + wfDebugLog( + 'BsMemcached', + __CLASS__.': Fetching ResponsibleEditors from DB' + ); + + $oRes = wfGetDB( DB_SLAVE )->select( + 'bs_responsible_editors', + '*', + array( 're_page_id' => $iArticleId ), + __METHOD__, + array( 'ORDER BY' => 're_position' ) + ); + if( !$oRes ) { + return array(); + } + + $aRespEditors = array(); + foreach( $oRes as $row ) { + $row->re_user_id = (int) $row->re_user_id; + $aRespEditors[] = (array) $row; + } + + return self::appendResponsibleEditorsCache( + $iArticleId, + $aRespEditors + ); } /** @@ -1157,7 +1156,22 @@ return true; } - public static function deleteResponsibleEditorsFromCache( $iArticleId ) { - BsCacheHelper::invalidateCache( BsCacheHelper::getCacheKey( 'ResponsibleEditors', 'getResponsibleEditorsByArticleId', (int)$iArticleId ) ); + public static function invalidateCache( $iArticleId ) { + if( empty($iArticleId) ) { + return false; + } + $sKey = BsCacheHelper::getCacheKey( + 'ResponsibleEditors', + 'getResponsibleEditorsByArticleId', + (int) $iArticleId + ); + if( isset(self::$aResponsibleEditorsByArticleId[$iArticleId]) ) { + unset( self::$aResponsibleEditorsByArticleId[$iArticleId] ); + } + BsCacheHelper::invalidateCache( $sKey ); + if( $oTitle = Title::newFromID( $iArticleId ) ) { + $oTitle->invalidateCache(); + } + return true; } } diff --git a/ResponsibleEditors/ResponsibleEditors.setup.php b/ResponsibleEditors/ResponsibleEditors.setup.php index 7932e37..6c8d303 100644 --- a/ResponsibleEditors/ResponsibleEditors.setup.php +++ b/ResponsibleEditors/ResponsibleEditors.setup.php @@ -10,6 +10,8 @@ // Specialpage and messages $GLOBALS['wgAutoloadClasses']['ResponsibleEditors'] = __DIR__ . '/ResponsibleEditors.class.php'; $wgAutoloadClasses['BsResponsibleEditor'] = __DIR__ . '/includes/BsResponsibleEditor.php'; +$wgAutoloadClasses['BSApiResponsibleEditorsPagesStore'] = __DIR__ . '/includes/api/BSApiResponsibleEditorsPagesStore.php'; +$wgAutoloadClasses['BSApiTasksResponsibleEditors'] = __DIR__ . '/includes/api/BSApiTasksResponsibleEditors.php'; $wgAutoloadClasses['SpecialResponsibleEditors'] = __DIR__ . '/includes/specials/SpecialResponsibleEditors.class.php'; $wgAutoloadClasses['ResponsibleEditorFormatter'] = __DIR__ . '/includes/ResponsibleEditorFormatter.class.php'; @@ -81,14 +83,13 @@ ) ) + $aResourceModuleTemplate; -$wgAjaxExportList[] = 'SpecialResponsibleEditors::ajaxGetResponsibleEditors'; +$wgAPIModules['bs-responsibleeditorspages-store'] = 'BSApiResponsibleEditorsPagesStore'; +$wgAPIModules['bs-responsibleeditors-tasks'] = 'BSApiTasksResponsibleEditors'; + $wgAjaxExportList[] = 'SpecialResponsibleEditors::ajaxSetResponsibleEditors'; $wgAjaxExportList[] = 'SpecialResponsibleEditors::ajaxGetPossibleEditors'; $wgAjaxExportList[] = 'ResponsibleEditors::ajaxGetActivatedNamespacesForCombobox'; -$wgAjaxExportList[] = 'ResponsibleEditors::ajaxGetResponsibleEditorsByArticleId'; $wgAjaxExportList[] = 'ResponsibleEditors::ajaxGetArticlesByNamespaceId'; -$wgAjaxExportList[] = 'ResponsibleEditors::ajaxGetListOfResponsibleEditorsForArticle'; -$wgAjaxExportList[] = 'ResponsibleEditors::ajaxDeleteResponsibleEditorsForArticle'; $wgAjaxExportList[] = 'ResponsibleEditors::getResponsibleEditorsPortletData'; $wgLogTypes[] = 'bs-responsible-editors'; diff --git a/ResponsibleEditors/includes/api/BSApiResponsibleEditorsPagesStore.php b/ResponsibleEditors/includes/api/BSApiResponsibleEditorsPagesStore.php new file mode 100644 index 0000000..6f30e61 --- /dev/null +++ b/ResponsibleEditors/includes/api/BSApiResponsibleEditorsPagesStore.php @@ -0,0 +1,41 @@ +<?php +/** + * This class serves as a backend for the responsible editors pages store. + * + * 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. + * + * This file is part of BlueSpice for MediaWiki + * For further information visit http://www.blue-spice.org + * + * @author Patric Wirth <wi...@hallowelt.com> + * @package Bluespice_Extensions + * @copyright Copyright (C) 2015 Hallo Welt! - Medienwerkstatt GmbH, All rights reserved. + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License v2 or later + * + * Example request parameters of an ExtJS store + */ +class BSApiResponsibleEditorsPagesStore extends BSApiWikiPageStore { + + public function getAllowedParams() { + return parent::getAllowedParams() + array( + 'displayMode' => array( + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_REQUIRED => false, + ApiBase::PARAM_DFLT => '[]', + 10 /*ApiBase::PARAM_HELP_MSG*/ => 'apihelp-bs-responsibleeditorspages-store-param-displaymode', + ), + ); + } +} \ No newline at end of file diff --git a/ResponsibleEditors/includes/api/BSApiTasksResponsibleEditors.php b/ResponsibleEditors/includes/api/BSApiTasksResponsibleEditors.php new file mode 100644 index 0000000..cf60782 --- /dev/null +++ b/ResponsibleEditors/includes/api/BSApiTasksResponsibleEditors.php @@ -0,0 +1,64 @@ +<?php +/** + * Provides the group manager tasks api for BlueSpice. + * + * 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. + * + * This file is part of BlueSpice for MediaWiki + * For further information visit http://www.blue-spice.org + * + * @author Patric Wirth <wi...@hallowelt.biz> + * @package Bluespice_Extensions + * @copyright Copyright (C) 2011 Hallo Welt! - Medienwerkstatt GmbH, All rights reserved. + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License v2 or later + */ + +/** + * GroupManager Api class + * @package BlueSpice_Extensions + */ +class BSApiTasksResponsibleEditors extends BSApiTasksBase { + + /** + * Methods that can be called by task param + * @var array + */ + protected $aTasks = array( + 'setResponsibleEditors', + ); + + protected function task_setResponsibleEditors( $oTaskData, $aParams ) { + $oReturn = $this->makeStandardReturn(); + + return $oReturn; + } + + /** + * Returns an array of tasks and their required permissions + * array( 'taskname' => array('read', 'edit') ) + * @return array + */ + protected function getRequiredTaskPermissions() { + return array( + 'setResponsibleEditors' => array( + 'responsibleeditors-changeresponsibility' + ), + ); + } + + public function needsToken() { + return parent::needsToken(); + } +} \ No newline at end of file diff --git a/ResponsibleEditors/includes/specials/SpecialResponsibleEditors.class.php b/ResponsibleEditors/includes/specials/SpecialResponsibleEditors.class.php index 4ac897d..6f8633d 100644 --- a/ResponsibleEditors/includes/specials/SpecialResponsibleEditors.class.php +++ b/ResponsibleEditors/includes/specials/SpecialResponsibleEditors.class.php @@ -73,7 +73,7 @@ $iArticleId = $aParams['articleId']; $aEditors = $aParams['editorIds']; - ResponsibleEditors::deleteResponsibleEditorsFromCache($iArticleId ); + ResponsibleEditors::invalidateCache($iArticleId ); $oRequestedTitle = Title::newFromId($iArticleId); -- To view, visit https://gerrit.wikimedia.org/r/274972 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I1631e9baf66b281be983d5101392466bf37bc7aa Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/BlueSpiceExtensions Gerrit-Branch: master Gerrit-Owner: Pwirth <wi...@hallowelt.biz> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits