instead of many optional positional arguments. Calling the constructor the old
way will issue a deprecation warning.
* The jquery.mwExtension module was deprecated.
+* $wgSpecialPageGroups was removed (deprecated in 1.21).
+* SpecialPageFactory::setGroup was removed (deprecated in 1.21).
+* SpecialPageFactory::getGroup was removed (deprecated in 1.21).
== Compatibility ==
"psr/log": "1.0.0",
"wikimedia/cdb": "1.0.1",
"wikimedia/assert": "0.2.2",
- "wikimedia/composer-merge-plugin": "1.1.0",
+ "wikimedia/composer-merge-plugin": "1.2.0",
"wikimedia/utfnormal": "1.0.2",
"zordius/lightncandy": "0.21"
},
"require-dev": {
- "jakub-onderka/php-parallel-lint": "~0.8",
+ "jakub-onderka/php-parallel-lint": "0.9",
"justinrainbow/json-schema": "~1.3",
"phpunit/phpunit": "3.7.37",
- "mediawiki/mediawiki-codesniffer": "0.2.0"
+ "mediawiki/mediawiki-codesniffer": "0.3.0"
},
"suggest": {
"ext-fileinfo": "*",
&$redirectParams: An array of parameters preserved by redirecting special pages.
'RejectParserCacheValue': Return false to reject an otherwise usable
-cached value from the Parser cache.
+cached value from the Parser cache. NOTE: CARELESS USE OF THIS HOOK CAN
+HAVE CATASTROPHIC CONSEQUENCES FOR HIGH-TRAFFIC INSTALLATIONS. USE WITH
+EXTREME CARE.
$parserOutput: ParserOutput value.
$wikiPage: WikiPage object.
$parserOptions: ParserOptions object.
*/
$wgDisableQueryPageUpdate = false;
-/**
- * List of special pages, followed by what subtitle they should go under
- * at Special:SpecialPages
- *
- * @deprecated since 1.21 Override SpecialPage::getGroupName instead
- */
-$wgSpecialPageGroups = array();
-
/**
* On Special:Unusedimages, consider images "used", if they are put
* into a category. Default (false) is not to count those as used.
// We don't use counters anymore. Left here for extensions still
// expecting this to exist. Should be removed sometime 1.26 or later.
-$wgDisableCounters = true;
+if ( !isset( $wgDisableCounters ) ) {
+ $wgDisableCounters = true;
+}
if ( $wgMainWANCache === false ) {
// Setup a WAN cache from $wgMainCacheType with no relayer.
private $mLastError = 'Unknown error';
/** @var integer Total connections opened */
private $connsOpened = 0;
- /** @var ProcessCacheLRU */
- private $mProcCache;
/** @var integer Warn when this many connection are held */
const CONN_HELD_WARN_THRESHOLD = 10;
}
}
}
-
- $this->mProcCache = new ProcessCacheLRU( 30 );
}
/**
return array( 0 => 0 ); // no replication = no lag
}
- if ( $this->mProcCache->has( 'slave_lag', 'times', 1 ) ) {
- return $this->mProcCache->get( 'slave_lag', 'times' );
- }
-
# Send the request to the load monitor
- $times = $this->getLoadMonitor()->getLagTimes( array_keys( $this->mServers ), $wiki );
-
- $this->mProcCache->set( 'slave_lag', 'times', $times );
-
- return $times;
+ return $this->getLoadMonitor()->getLagTimes( array_keys( $this->mServers ), $wiki );
}
/**
/**
* Clear the cache for slag lag delay times
+ *
+ * This is only used for testing
*/
public function clearLagTimeCache() {
- $this->mProcCache->clear( 'slave_lag' );
+ $this->getLoadMonitor()->clearCaches();
}
}
/** @var LoadBalancer */
public $parent;
/** @var BagOStuff */
- protected $cache;
+ protected $srvCache;
+ /** @var BagOStuff */
+ protected $mainCache;
public function __construct( $parent ) {
global $wgMemc;
$this->parent = $parent;
- $this->cache = $wgMemc ?: wfGetMainCache();
+
+ $this->srvCache = ObjectCache::newAccelerator( array(), 'hash' );
+ $this->mainCache = $wgMemc ?: wfGetMainCache();
}
public function scaleLoads( &$loads, $group = false, $wiki = false ) {
public function getLagTimes( $serverIndexes, $wiki ) {
if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) {
- // Single server only, just return zero without caching
+ # Single server only, just return zero without caching
return array( 0 => 0 );
}
- $expiry = 5;
- $requestRate = 10;
-
- $cache = $this->cache;
- $masterName = $this->parent->getServerName( 0 );
- $memcKey = wfMemcKey( 'lag_times', $masterName );
- $times = $cache->get( $memcKey );
- if ( is_array( $times ) ) {
- # Randomly recache with probability rising over $expiry
- $elapsed = time() - $times['timestamp'];
- $chance = max( 0, ( $expiry - $elapsed ) * $requestRate );
- if ( mt_rand( 0, $chance ) != 0 ) {
- unset( $times['timestamp'] ); // hide from caller
-
- return $times;
- }
- wfIncrStats( 'lag_cache.miss.expired' );
- } else {
- wfIncrStats( 'lag_cache.miss.absent' );
+ $key = $this->getLagTimeCacheKey();
+ # Randomize TTLs to reduce stampedes (4.0 - 5.0 sec)
+ $ttl = mt_rand( 4e6, 5e6 ) / 1e6;
+ # Keep keys around longer as fallbacks
+ $staleTTL = 60;
+
+ # (a) Check the local APC cache
+ $value = $this->srvCache->get( $key );
+ if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
+ wfDebugLog( 'replication', __FUNCTION__ . ": got lag times ($key) from local cache" );
+ return $value['lagTimes']; // cache hit
+ }
+ $staleValue = $value ?: false;
+
+ # (b) Check the shared cache and backfill APC
+ $value = $this->mainCache->get( $key );
+ if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
+ $this->srvCache->set( $key, $value, $staleTTL );
+ wfDebugLog( 'replication', __FUNCTION__ . ": got lag times ($key) from main cache" );
+
+ return $value['lagTimes']; // cache hit
}
+ $staleValue = $value ?: $staleValue;
- # Cache key missing or expired
- if ( $cache->lock( $memcKey, 0, 10 ) ) {
+ # (c) Cache key missing or expired; regenerate and backfill
+ if ( $this->mainCache->lock( $key, 0, 10 ) ) {
# Let this process alone update the cache value
- $unlocker = new ScopedCallback( function () use ( $cache, $memcKey ) {
- $cache->unlock( $memcKey );
+ $cache = $this->mainCache;
+ $unlocker = new ScopedCallback( function () use ( $cache, $key ) {
+ $cache->unlock( $key );
} );
- } elseif ( is_array( $times ) ) {
+ } elseif ( $staleValue ) {
# Could not acquire lock but an old cache exists, so use it
- unset( $times['timestamp'] ); // hide from caller
-
- return $times;
+ return $value['lagTimes'];
}
- $times = array();
+ $lagTimes = array();
foreach ( $serverIndexes as $i ) {
if ( $i == 0 ) { # Master
- $times[$i] = 0;
+ $lagTimes[$i] = 0;
} elseif ( false !== ( $conn = $this->parent->getAnyOpenConnection( $i ) ) ) {
- $times[$i] = $conn->getLag();
+ $lagTimes[$i] = $conn->getLag();
} elseif ( false !== ( $conn = $this->parent->openConnection( $i, $wiki ) ) ) {
- $times[$i] = $conn->getLag();
- // Close the connection to avoid sleeper connections piling up.
- // Note that the caller will pick one of these DBs and reconnect,
- // which is slightly inefficient, but this only matters for the lag
- // time cache miss cache, which is far less common that cache hits.
+ $lagTimes[$i] = $conn->getLag();
+ # Close the connection to avoid sleeper connections piling up.
+ # Note that the caller will pick one of these DBs and reconnect,
+ # which is slightly inefficient, but this only matters for the lag
+ # time cache miss cache, which is far less common that cache hits.
$this->parent->closeConnection( $conn );
}
}
# Add a timestamp key so we know when it was cached
- $times['timestamp'] = time();
- $cache->set( $memcKey, $times, $expiry + 10 );
- unset( $times['timestamp'] ); // hide from caller
+ $value = array( 'lagTimes' => $lagTimes, 'timestamp' => microtime( true ) );
+ $this->mainCache->set( $key, $value, $staleTTL );
+ $this->srvCache->set( $key, $value, $staleTTL );
+ wfDebugLog( 'replication', __FUNCTION__ . ": re-calculated lag times ($key)" );
+
+ return $value['lagTimes'];
+ }
+
+ public function clearCaches() {
+ $key = $this->getLagTimeCacheKey();
+ $this->srvCache->delete( $key );
+ $this->mainCache->delete( $key );
+ }
- return $times;
+ private function getLagTimeCacheKey() {
+ # Lag is per-server, not per-DB, so key on the master DB name
+ return wfForeignMemcKey( $this->parent->getServerName( 0 ), '', 'lag_times' );
}
}
$deps[$installed['name']] = array(
'version' => ComposerJson::normalizeVersion( $installed['version'] ),
'type' => $installed['type'],
+ 'licenses' => isset( $installed['license'] ) ? $installed['license'] : array(),
+ 'authors' => isset( $installed['authors'] ) ? $installed['authors'] : array(),
+ 'description' => isset( $installed['description'] ) ? $installed['description']: '',
);
}
*/
public function getFinalGroupName() {
$name = $this->getName();
- $specialPageGroups = $this->getConfig()->get( 'SpecialPageGroups' );
// Allow overbidding the group from the wiki side
$msg = $this->msg( 'specialpages-specialpagegroup-' . strtolower( $name ) )->inContentLanguage();
} else {
// Than use the group from this object
$group = $this->getGroupName();
-
- // Group '-' is used as default to have the chance to determine,
- // if the special pages overrides this method,
- // if not overridden, $wgSpecialPageGroups is checked for b/c
- if ( $group === '-' && isset( $specialPageGroups[$name] ) ) {
- $group = $specialPageGroups[$name];
- }
- }
-
- // never give '-' back, change to 'other'
- if ( $group === '-' ) {
- $group = 'other';
}
return $group;
* @since 1.21
*/
protected function getGroupName() {
- // '-' used here to determine, if this group is overridden or has a hardcoded 'other'
- // Needed for b/c in getFinalGroupName
- return '-';
+ return 'other';
}
}
return array( $name, $par );
}
- /**
- * Add a page to a certain display group for Special:SpecialPages
- *
- * @param SpecialPage|string $page
- * @param string $group
- * @deprecated since 1.21 Override SpecialPage::getGroupName
- */
- public static function setGroup( $page, $group ) {
- wfDeprecated( __METHOD__, '1.21' );
-
- global $wgSpecialPageGroups;
- $name = is_object( $page ) ? $page->getName() : $page;
- $wgSpecialPageGroups[$name] = $group;
- }
-
- /**
- * Get the group that the special page belongs in on Special:SpecialPage
- *
- * @param SpecialPage $page
- * @return string
- * @deprecated since 1.21 Use SpecialPage::getFinalGroupName
- */
- public static function getGroup( &$page ) {
- wfDeprecated( __METHOD__, '1.21' );
-
- return $page->getFinalGroupName();
- }
-
/**
* Check if a given name exist as a special page or as a special page alias
*
$out .= Html::openElement( 'tr' )
. Html::element( 'th', array(), $this->msg( 'version-libraries-library' )->text() )
. Html::element( 'th', array(), $this->msg( 'version-libraries-version' )->text() )
+ . Html::element( 'th', array(), $this->msg( 'version-libraries-license' )->text() )
+ . Html::element( 'th', array(), $this->msg( 'version-libraries-description' )->text() )
+ . Html::element( 'th', array(), $this->msg( 'version-libraries-authors' )->text() )
. Html::closeElement( 'tr' );
foreach ( $lock->getInstalledDependencies() as $name => $info ) {
// in their proper section
continue;
}
+ $authors = array_map( function( $arr ) {
+ // If a homepage is set, link to it
+ if ( isset( $arr['homepage'] ) ) {
+ return "[{$arr['homepage']} {$arr['name']}]";
+ }
+ return $arr['name'];
+ }, $info['authors'] );
+ $authors = $this->listAuthors( $authors, false, "$IP/vendor/$name" );
$out .= Html::openElement( 'tr' )
. Html::rawElement(
'td',
array(),
- Linker::makeExternalLink( "https://packagist.org/packages/$name", $name )
+ Linker::makeExternalLink(
+ "https://packagist.org/packages/$name", $name,
+ true, '',
+ array( 'class' => 'mw-version-library-name' )
+ )
)
. Html::element( 'td', array(), $info['version'] )
+ . Html::element( 'td', array(), $this->listToText( $info['licenses'] ) )
+ . Html::element( 'td', array(), $info['description'] )
+ . Html::rawElement( 'td', array(), $authors )
. Html::closeElement( 'tr' );
}
$out .= Html::closeElement( 'table' );
* 'and others' will be added to the end of the credits.
*
* @param string|array $authors
- * @param string $extName Name of the extension for link creation
+ * @param string|bool $extName Name of the extension for link creation,
+ * false if no links should be created
* @param string $extDir Path to the extension root directory
*
* @return string HTML fragment
if ( $item == '...' ) {
$hasOthers = true;
- if ( $this->getExtAuthorsFileName( $extDir ) ) {
+ if ( $extName && $this->getExtAuthorsFileName( $extDir ) ) {
$text = Linker::link(
$this->getPageTitle( "Credits/$extName" ),
$this->msg( 'version-poweredby-others' )->escaped()
}
}
- if ( !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
+ if ( $extName && !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
$list[] = $text = Linker::link(
$this->getPageTitle( "Credits/$extName" ),
$this->msg( 'version-poweredby-others' )->escaped()
"version-libraries": "Installed libraries",
"version-libraries-library": "Library",
"version-libraries-version": "Version",
+ "version-libraries-license": "License",
+ "version-libraries-description": "Description",
+ "version-libraries-authors": "Authors",
"redirect": "Redirect by file, user, page or revision ID",
"redirect-legend": "Redirect to a file or page",
"redirect-text": "",
"version-libraries": "Header on [[Special:Version]] above a table that lists installed external libraries and their version numbers.",
"version-libraries-library": "Column header for the library's name\n{{Identical|Library}}",
"version-libraries-version": "Column header for the library's version number\n{{Identical|Version}}",
+ "version-libraries-license": "Column header for the library's license",
+ "version-libraries-description": "Column header for the library's description",
+ "version-libraries-authors": "Column header for the library's authors",
"redirect": "{{doc-special|Redirect}}\nThis means \"Redirect by file'''name''', user '''ID''', page '''ID''', or revision ID\".",
"redirect-legend": "Legend of fieldset around input box in [[Special:Redirect]]",
"redirect-text": "Inside fieldset for [[Special:Redirect]]",
* @var array
*/
protected $noLongerSupportedGlobals = array(
- 'SpecialPageGroups' => 'deprecated',
+ 'SpecialPageGroups' => 'deprecated', // Deprecated 1.21, removed in 1.26
);
/**
/*!
* Styling for Special:Version
*/
-.mw-version-ext-name {
+.mw-version-ext-name,
+.mw-version-library-name {
font-weight: bold;
}
'wikimedia/cdb' => array(
'version' => '1.0.1',
'type' => 'library',
+ 'licenses' => array( 'GPL-2.0' ),
+ 'authors' => array(
+ array(
+ 'name' => 'Tim Starling',
+ 'email' => 'tstarling@wikimedia.org',
+ ),
+ array(
+ 'name' => 'Chad Horohoe',
+ 'email' => 'chad@wikimedia.org',
+ ),
+ ),
+ 'description' => 'Constant Database (CDB) wrapper library for PHP. Provides pure-PHP fallback when dba_* functions are absent.',
),
'cssjanus/cssjanus' => array(
'version' => '1.1.1',
'type' => 'library',
+ 'licenses' => array( 'Apache-2.0' ),
+ 'authors' => array(),
+ 'description' => 'Convert CSS stylesheets between left-to-right and right-to-left.',
),
'leafo/lessphp' => array(
'version' => '0.5.0',
'type' => 'library',
+ 'licenses' => array( 'MIT', 'GPL-3.0' ),
+ 'authors' => array(
+ array(
+ 'name' => 'Leaf Corcoran',
+ 'email' => 'leafot@gmail.com',
+ 'homepage' => 'http://leafo.net',
+ ),
+ ),
+ 'description' => 'lessphp is a compiler for LESS written in PHP.',
),
'psr/log' => array(
'version' => '1.0.0',
'type' => 'library',
+ 'licenses' => array( 'MIT' ),
+ 'authors' => array(
+ array(
+ 'name' => 'PHP-FIG',
+ 'homepage' => 'http://www.php-fig.org/',
+ ),
+ ),
+ 'description' => 'Common interface for logging libraries',
),
'oojs/oojs-ui' => array(
'version' => '0.6.0',
'type' => 'library',
+ 'licenses' => array( 'MIT' ),
+ 'authors' => array(),
+ 'description' => '',
),
'composer/installers' => array(
'version' => '1.0.19',
'type' => 'composer-installer',
+ 'licenses' => array( 'MIT' ),
+ 'authors' => array(
+ array(
+ 'name' => 'Kyle Robinson Young',
+ 'email' => 'kyle@dontkry.com',
+ 'homepage' => 'https://github.com/shama',
+ ),
+ ),
+ 'description' => 'A multi-framework Composer library installer',
),
'mediawiki/translate' => array(
'version' => '2014.12',
'type' => 'mediawiki-extension',
+ 'licenses' => array( 'GPL-2.0+' ),
+ 'authors' => array(
+ array(
+ 'name' => 'Niklas Laxström',
+ 'email' => 'niklas.laxstrom@gmail.com',
+ 'role' => 'Lead nitpicker',
+ ),
+ array(
+ 'name' => 'Siebrand Mazeland',
+ 'email' => 's.mazeland@xs4all.nl',
+ 'role' => 'Developer',
+ ),
+ ),
+ 'description' => 'The only standard solution to translate any kind of text with an avant-garde web interface within MediaWiki, including your documentation and software',
),
'mediawiki/universal-language-selector' => array(
'version' => '2014.12',
'type' => 'mediawiki-extension',
+ 'licenses' => array( 'GPL-2.0+', 'MIT' ),
+ 'authors' => array(),
+ 'description' => 'The primary aim is to allow users to select a language and configure its support in an easy way. Main features are language selection, input methods and web fonts.',
),
), $lock->getInstalledDependencies(), false, true );
}