MediaWiki 1.29 requires PHP 5.5.9 or later. There is experimental support for
HHVM 3.6.5 or later.
-MySQL is the recommended DBMS. PostgreSQL or SQLite can also be used, but
-support for them is somewhat less mature. There is experimental support for
+MySQL/MariaDB is the recommended DBMS. PostgreSQL or SQLite can also be used,
+but support for them is somewhat less mature. There is experimental support for
Oracle and Microsoft SQL Server.
The supported versions are:
table, the schema update may take quite long (minutes on a medium sized site,
many hours on a large site).
-If upgrading from before 1.11, and you are using a wiki as a commons
-repository, make sure that it is updated as well. Otherwise, errors may arise
-due to database schema changes.
-
-If upgrading from before 1.7, you may want to run refreshLinks.php to ensure
-new database fields are filled with data.
-
-If you are upgrading from MediaWiki 1.4.x or earlier, you should upgrade to
-1.5 first. The upgrade script maintenance/upgrade1_5.php has been removed
-with MediaWiki 1.21.
-
Don't forget to always back up your database before upgrading!
-See the file UPGRADE for more detailed upgrade instructions.
+See the file UPGRADE for more detailed upgrade instructions, including
+important information when upgrading from versions prior to 1.11.
For notes on 1.28.x and older releases, see HISTORY.
script. This will update the searchindex table for those pages that
contain double-byte latin characters.
+== Upgrading from 1.10 or earlier ==
+
+If upgrading from before 1.11, and you are using a wiki as a commons
+repository, make sure that it is updated as well. Otherwise, errors may arise
+due to database schema changes.
+
== Upgrading from 1.8 or earlier ==
MediaWiki 1.9 and later no longer keep default localized message text
$wgLocalTZoffset was in hours, it is now using minutes.
+If upgrading from before 1.7, you may want to run refreshLinks.php to ensure
+new database fields are filled with data.
+
== Upgrading from 1.5 or earlier ==
Major changes have been made to the schema from 1.4.x. The updater
'upgrade1_5.php', can do this -- run it prior to 'update.php' or
the web upgrader.
-NOTE that upgrade1_5.php does not work properly with recent version
+NOTE that upgrade1_5.php does not work properly with recent versions
of MediaWiki. If upgrading a 1.4.x wiki, you should upgrade to 1.5
first. upgrade1_5.php has been removed from MediaWiki 1.21.
/** @var array Profiling data */
private $limitReportJSData = [];
+ /**
+ * Link: header contents
+ */
+ private $mLinkHeader = [];
+
/**
* Constructor for OutputPage. This should not be called directly.
* Instead a new RequestContext should be created and it will implicitly create
return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
}
+ /**
+ * Add an HTTP Link: header
+ *
+ * @param string $header Header value
+ */
+ public function addLinkHeader( $header ) {
+ $this->mLinkHeader[] = $header;
+ }
+
+ /**
+ * Return a Link: header. Based on the values of $mLinkHeader.
+ *
+ * @return string
+ */
+ public function getLinkHeader() {
+ if ( !$this->mLinkHeader ) {
+ return false;
+ }
+
+ return 'Link: ' . implode( ',', $this->mLinkHeader );
+ }
+
/**
* Get a complete Key header
*
// jQuery etc. can work correctly.
$response->header( 'X-UA-Compatible: IE=Edge' );
+ $this->addLogoPreloadLinkHeaders();
+ $linkHeader = $this->getLinkHeader();
+ if ( $linkHeader ) {
+ $response->header( $linkHeader );
+ }
+
// Prevent framing, if requested
$frameOptions = $this->getFrameOptions();
if ( $frameOptions ) {
'mediawiki.widgets.styles',
] );
}
+
+ /**
+ * Add Link headers for preloading the wiki's logo.
+ *
+ * @since 1.26
+ */
+ protected function addLogoPreloadLinkHeaders() {
+ $logo = $this->getConfig()->get( 'Logo' ); // wgLogo
+ $logoHD = $this->getConfig()->get( 'LogoHD' ); // wgLogoHD
+
+ $tags = [];
+ $logosPerDppx = [];
+ $logos = [];
+
+ $logosPerDppx['1.0'] = $logo;
+
+ if ( !$logoHD ) {
+ // No media queries required if we only have one variant
+ $this->addLinkHeader( '<' . $logo . '>;rel=preload;as=image' );
+ return;
+ }
+
+ foreach ( $logoHD as $dppx => $src ) {
+ // Only 1.5x and 2x are supported
+ // Note: Keep in sync with ResourceLoaderSkinModule
+ if ( in_array( $dppx, [ '1.5x', '2x' ] ) ) {
+ // LogoHD uses a string in this format: "1.5x"
+ $dppx = substr( $dppx, 0, -1 );
+ $logosPerDppx[$dppx] = $src;
+ }
+ }
+
+ // Because PHP can't have floats as array keys
+ uksort( $logosPerDppx, function ( $a , $b ) {
+ $a = floatval( $a );
+ $b = floatval( $b );
+
+ if ( $a == $b ) {
+ return 0;
+ }
+ // Sort from smallest to largest (e.g. 1x, 1.5x, 2x)
+ return ( $a < $b ) ? -1 : 1;
+ } );
+
+ foreach ( $logosPerDppx as $dppx => $src ) {
+ $logos[] = [ 'dppx' => $dppx, 'src' => $src ];
+ }
+
+ $logosCount = count( $logos );
+ // Logic must match ResourceLoaderSkinModule:
+ // - 1x applies to resolution < 1.5dppx
+ // - 1.5x applies to resolution >= 1.5dppx && < 2dppx
+ // - 2x applies to resolution >= 2dppx
+ // Note that min-resolution and max-resolution are both inclusive.
+ for ( $i = 0; $i < $logosCount; $i++ ) {
+ if ( $i === 0 ) {
+ // Smallest dppx
+ // min-resolution is ">=" (larger than or equal to)
+ // "not min-resolution" is essentially "<"
+ $media_query = 'not all and (min-resolution: ' . $logos[ 1 ]['dppx'] . 'dppx)';
+ } elseif ( $i !== $logosCount - 1 ) {
+ // In between
+ // Media query expressions can only apply "not" to the entire expression
+ // (e.g. can't express ">= 1.5 and not >= 2).
+ // Workaround: Use <= 1.9999 in place of < 2.
+ $upper_bound = floatval( $logos[ $i + 1 ]['dppx'] ) - 0.000001;
+ $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] .
+ 'dppx) and (max-resolution: ' . $upper_bound . 'dppx)';
+ } else {
+ // Largest dppx
+ $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] . 'dppx)';
+ }
+
+ $this->addLinkHeader(
+ '<' . $logos[$i]['src'] . '>;rel=preload;as=image;media=' . $media_query
+ );
+ }
+ }
}
'rb' => $common,
'rp' => $common,
'rt' => $common, # array_merge( $common, array( 'rbspan' ) ),
- 'rtc' => $common,
+ 'rtc' => $common,
# MathML root element, where used for extensions
# 'title' may not be 100% valid here; it's XHTML
}
/**
- * Whether the magic words __INDEX__ and __NOINDEX__ function for this page.
+ * Whether the magic words __INDEX__ and __NOINDEX__ function for this page.
*
* @return bool
*/
}
if ( array_key_exists( 'rcTypes', $options ) ) {
- $conds['rc_type'] = array_map( 'intval', $options['rcTypes'] );
+ $conds['rc_type'] = array_map( 'intval', $options['rcTypes'] );
}
$conds = array_merge(
}
if ( isset( $options['filter'] ) ) {
$filter = $options['filter'];
- if ( $filter === self::FILTER_CHANGED ) {
+ if ( $filter === self::FILTER_CHANGED ) {
$conds[] = 'wl_notificationtimestamp IS NOT NULL';
} else {
$conds[] = 'wl_notificationtimestamp IS NULL';
class WikiReference {
private $mCanonicalServer; ///< canonical server URL, e.g. 'https://www.mediawiki.org'
private $mServer; ///< server URL, may be protocol-relative, e.g. '//www.mediawiki.org'
- private $mPath; ///< path, '/wiki/$1'
+ private $mPath; ///< path, '/wiki/$1'
/**
* @param string $canonicalServer
$throttleKey = wfGlobalCacheKey( 'throttler', $this->type, $index, $ipKey, $userKey );
$throttleCount = $this->cache->get( $throttleKey );
- if ( !$throttleCount ) { // counter not started yet
+ if ( !$throttleCount ) { // counter not started yet
$this->cache->add( $throttleKey, 1, $expiry );
} elseif ( $throttleCount < $count ) { // throttle limited not yet reached
$this->cache->incr( $throttleKey );
'rc_logid' => 0,
'rc_log_type' => null,
'rc_log_action' => '',
- 'rc_params' => serialize( [
+ 'rc_params' => serialize( [
'hidden-cat' => WikiCategoryPage::factory( $categoryTitle )->isHidden()
] )
];
$deleteWheres = []; // list of WHERE clause arrays for each DB delete() call
if ( $table === 'pagelinks' || $table === 'templatelinks' || $table === 'iwlinks' ) {
- $baseKey = ( $table === 'iwlinks' ) ? 'iwl_prefix' : "{$prefix}_namespace";
+ $baseKey = ( $table === 'iwlinks' ) ? 'iwl_prefix' : "{$prefix}_namespace";
$curBatchSize = 0;
$curDeletionBatch = [];
$error = array_merge( [ $error['message'] ], $error['params'] );
}
}
- } elseif ( $elementsType === 'errors' ) {
+ } elseif ( $elementsType === 'errors' ) {
$errors = $elements;
if ( !is_array( $errors ) ) {
$errors = [ $errors ];
$this->parseHeader();
$this->setStatus();
- return Status::wrap( $this->status ); // TODO B/C; move this to callers
+ return Status::wrap( $this->status ); // TODO B/C; move this to callers
}
/**
* @since 1.26
*/
public function lazyPush( $jobs ) {
- if ( PHP_SAPI === 'cli' ) {
+ if ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ) {
$this->push( $jobs );
return;
}
$styles['all'][] = '.mw-wiki-logo { background-image: ' .
CSSMin::buildUrlValue( $logo1 ) .
'; }';
+ // Only 1.5x and 2x are supported
+ // Note: Keep in sync with OutputPage::addLogoPreloadLinkHeaders()
if ( $logoHD ) {
if ( isset( $logoHD['1.5x'] ) ) {
$styles[
},
'cssClassSuffix' => 'patrolled',
'isRowApplicableCallable' => function ( $ctx, $rc ) {
- return $rc->getAttribute( 'rc_patrolled' );
+ return $rc->getAttribute( 'rc_patrolled' );
},
],
[
},
'cssClassSuffix' => 'unpatrolled',
'isRowApplicableCallable' => function ( $ctx, $rc ) {
- return !$rc->getAttribute( 'rc_patrolled' );
+ return !$rc->getAttribute( 'rc_patrolled' );
},
],
],
&$tables, &$fields, &$conds, &$query_options, &$join_conds, $selectedExpLevels ) {
global $wgLearnerEdits,
- $wgExperiencedUserEdits,
- $wgLearnerMemberSince,
- $wgExperiencedUserMemberSince;
+ $wgExperiencedUserEdits,
+ $wgLearnerMemberSince,
+ $wgExperiencedUserMemberSince;
$LEVEL_COUNT = 3;
);
if ( $selectedExpLevels === [ 'newcomer' ] ) {
- $conds[] = "NOT ( $aboveNewcomer )";
+ $conds[] = "NOT ( $aboveNewcomer )";
} elseif ( $selectedExpLevels === [ 'learner' ] ) {
$conds[] = $dbr->makeList(
[ $aboveNewcomer, "NOT ( $aboveLearner )" ],
'text',
[
'id' => 'emailusertarget',
- 'class' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
+ 'class' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
'autofocus' => true,
'size' => 30,
]
*/
protected function transformFilterDefinition( array $filterDefinition ) {
if ( isset( $filterDefinition['showHideSuffix'] ) ) {
- $filterDefinition['showHide'] = 'wl' . $filterDefinition['showHideSuffix'];
+ $filterDefinition['showHide'] = 'wl' . $filterDefinition['showHideSuffix'];
}
return $filterDefinition;
$this->assertEquals( [ 0 => 'Test' ], $outputPage->getCategories( 'hidden' ) );
}
+ /**
+ * @dataProvider provideLinkHeaders
+ * @covers OutputPage::addLinkHeader
+ * @covers OutputPage::getLinkHeader
+ */
+ public function testLinkHeaders( $headers, $result ) {
+ $outputPage = $this->newInstance();
+
+ foreach ( $headers as $header ) {
+ $outputPage->addLinkHeader( $header );
+ }
+
+ $this->assertEquals( $result, $outputPage->getLinkHeader() );
+ }
+
+ public function provideLinkHeaders() {
+ return [
+ [
+ [],
+ false
+ ],
+ [
+ [ '<https://foo/bar.jpg>;rel=preload;as=image' ],
+ 'Link: <https://foo/bar.jpg>;rel=preload;as=image',
+ ],
+ [
+ [ '<https://foo/bar.jpg>;rel=preload;as=image','<https://foo/baz.jpg>;rel=preload;as=image' ],
+ 'Link: <https://foo/bar.jpg>;rel=preload;as=image,<https://foo/baz.jpg>;rel=preload;as=image',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider providePreloadLinkHeaders
+ * @covers OutputPage::addLogoPreloadLinkHeaders
+ */
+ public function testPreloadLinkHeaders( $config, $result ) {
+ $out = TestingAccessWrapper::newFromObject( $this->newInstance( $config ) );
+ $out->addLogoPreloadLinkHeaders();
+
+ $this->assertEquals( $result, $out->getLinkHeader() );
+ }
+
+ public function providePreloadLinkHeaders() {
+ return [
+ [
+ [
+ 'Logo' => '/img/default.png',
+ 'LogoHD' => [
+ '1.5x' => '/img/one-point-five.png',
+ '2x' => '/img/two-x.png',
+ ],
+ ],
+ 'Link: </img/default.png>;rel=preload;as=image;media=' .
+ 'not all and (min-resolution: 1.5dppx),' .
+ '</img/one-point-five.png>;rel=preload;as=image;media=' .
+ '(min-resolution: 1.5dppx) and (max-resolution: 1.999999dppx),' .
+ '</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
+ ],
+ [
+ [
+ 'Logo' => '/img/default.png',
+ 'LogoHD' => false,
+ ],
+ 'Link: </img/default.png>;rel=preload;as=image'
+ ],
+ [
+ [
+ 'Logo' => '/img/default.png',
+ 'LogoHD' => [
+ '2x' => '/img/two-x.png',
+ ],
+ ],
+ 'Link: </img/default.png>;rel=preload;as=image;media=' .
+ 'not all and (min-resolution: 2dppx),' .
+ '</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
+ ],
+ ];
+ }
+
/**
* @return OutputPage
*/
- private function newInstance() {
+ private function newInstance( $config = [] ) {
$context = new RequestContext();
- $context->setConfig( new HashConfig( [
+ $context->setConfig( new HashConfig( $config + [
'AppleTouchIcon' => false,
'DisableLangConversion' => true,
'EnableAPI' => false,