<exclude-pattern>*/includes/Feed\.php</exclude-pattern>
<exclude-pattern>*/includes/filerepo/file/LocalFile\.php</exclude-pattern>
<exclude-pattern>*/includes/gallery/PackedOverlayImageGallery\.php</exclude-pattern>
- <exclude-pattern>*/includes/HistoryBlob\.php</exclude-pattern>
<exclude-pattern>*/includes/htmlform/HTMLFormElement\.php</exclude-pattern>
<exclude-pattern>*/includes/libs/filebackend/FileBackendStore\.php</exclude-pattern>
<exclude-pattern>*/includes/libs/filebackend/FSFileBackend\.php</exclude-pattern>
Use CdnCacheUpdate::newFromTitles() instead.
* Handling of multiple arguments by the Block constructor, deprecated in 1.26,
has been removed.
+* The translation of main page in Sardinian (sc) was changed from "Pàgina Base"
+ to "Pàgina printzipale". Existing wikis using this content language need to
+ move the main page or change the name through MediaWiki:Mainpage page.
=== Deprecations in 1.33 ===
* The configuration option $wgUseESI has been deprecated, and is expected
* ManualLogEntry::setTags() is deprecated, use ManualLogEntry::addTags()
instead. The setTags() method was overriding the tags, addTags() doesn't
override, only adds new tags.
+* Block::isValid is deprecated, since it is no longer needed in core.
=== Other changes in 1.33 ===
* (T201747) Html::openElement() warns if given an element name with a space
'ComposerVendorHtaccessCreator' => __DIR__ . '/includes/composer/ComposerVendorHtaccessCreator.php',
'ComposerVersionNormalizer' => __DIR__ . '/includes/composer/ComposerVersionNormalizer.php',
'CompressOld' => __DIR__ . '/maintenance/storage/compressOld.php',
- 'ConcatenatedGzipHistoryBlob' => __DIR__ . '/includes/HistoryBlob.php',
+ 'ConcatenatedGzipHistoryBlob' => __DIR__ . '/includes/historyblob/ConcatenatedGzipHistoryBlob.php',
'Config' => __DIR__ . '/includes/config/Config.php',
'ConfigException' => __DIR__ . '/includes/config/ConfigException.php',
'ConfigFactory' => __DIR__ . '/includes/config/ConfigFactory.php',
'Diff' => __DIR__ . '/includes/diff/DairikiDiff.php',
'DiffEngine' => __DIR__ . '/includes/diff/DiffEngine.php',
'DiffFormatter' => __DIR__ . '/includes/diff/DiffFormatter.php',
- 'DiffHistoryBlob' => __DIR__ . '/includes/HistoryBlob.php',
+ 'DiffHistoryBlob' => __DIR__ . '/includes/historyblob/DiffHistoryBlob.php',
'DiffOp' => __DIR__ . '/includes/diff/DairikiDiff.php',
'DiffOpAdd' => __DIR__ . '/includes/diff/DairikiDiff.php',
'DiffOpChange' => __DIR__ . '/includes/diff/DairikiDiff.php',
'HashSiteStore' => __DIR__ . '/includes/site/HashSiteStore.php',
'HashtableReplacer' => __DIR__ . '/includes/libs/replacers/HashtableReplacer.php',
'HistoryAction' => __DIR__ . '/includes/actions/HistoryAction.php',
- 'HistoryBlob' => __DIR__ . '/includes/HistoryBlob.php',
- 'HistoryBlobCurStub' => __DIR__ . '/includes/HistoryBlob.php',
- 'HistoryBlobStub' => __DIR__ . '/includes/HistoryBlob.php',
+ 'HistoryBlob' => __DIR__ . '/includes/historyblob/HistoryBlob.php',
+ 'HistoryBlobCurStub' => __DIR__ . '/includes/historyblob/HistoryBlobCurStub.php',
+ 'HistoryBlobStub' => __DIR__ . '/includes/historyblob/HistoryBlobStub.php',
'HistoryPager' => __DIR__ . '/includes/actions/pagers/HistoryPager.php',
'Hooks' => __DIR__ . '/includes/Hooks.php',
'Html' => __DIR__ . '/includes/Html.php',
'ZhConverter' => __DIR__ . '/languages/classes/LanguageZh.php',
'ZipDirectoryReader' => __DIR__ . '/includes/utils/ZipDirectoryReader.php',
'ZipDirectoryReaderError' => __DIR__ . '/includes/utils/ZipDirectoryReaderError.php',
- 'concatenatedgziphistoryblob' => __DIR__ . '/includes/HistoryBlob.php',
- 'historyblobcurstub' => __DIR__ . '/includes/HistoryBlob.php',
- 'historyblobstub' => __DIR__ . '/includes/HistoryBlob.php',
+ 'concatenatedgziphistoryblob' => __DIR__ . '/includes/historyblob/ConcatenatedGzipHistoryBlob.php',
+ 'historyblobcurstub' => __DIR__ . '/includes/historyblob/HistoryBlobCurStub.php',
+ 'historyblobstub' => __DIR__ . '/includes/historyblob/HistoryBlobStub.php',
'profile_point' => __DIR__ . '/profileinfo.php',
];
/**
* Is the block address valid (i.e. not a null string?)
+ *
+ * @deprecated since 1.33 No longer needed in core.
* @return bool
*/
public function isValid() {
+ wfDeprecated( __METHOD__, '1.33' );
return $this->getTarget() != null;
}
*/
$wgElementTiming = false;
-/**
- * Temporary option to show rollback confirmation user settings
- * without activating the feature itself
- * @see T217039
- * @since 1.33
- * @deprecated 1.33
- * @var bool
- */
-$wgDisableRollbackConfirmationFeature = false;
-
/**
* For really cool vim folding this needs to be at the end:
* vim: foldmarker=@{,@} foldmethod=marker
+++ /dev/null
-<?php
-/**
- * Efficient concatenated text storage.
- *
- * 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
- *
- * @file
- */
-
-/**
- * Base class for general text storage via the "object" flag in old_flags, or
- * two-part external storage URLs. Used for represent efficient concatenated
- * storage, and migration-related pointer objects.
- */
-interface HistoryBlob {
- /**
- * Adds an item of text, returns a stub object which points to the item.
- * You must call setLocation() on the stub object before storing it to the
- * database
- *
- * @param string $text
- *
- * @return string The key for getItem()
- */
- function addItem( $text );
-
- /**
- * Get item by key, or false if the key is not present
- *
- * @param string $key
- *
- * @return string|bool
- */
- function getItem( $key );
-
- /**
- * Set the "default text"
- * This concept is an odd property of the current DB schema, whereby each text item has a revision
- * associated with it. The default text is the text of the associated revision. There may, however,
- * be other revisions in the same object.
- *
- * Default text is not required for two-part external storage URLs.
- *
- * @param string $text
- */
- function setText( $text );
-
- /**
- * Get default text. This is called from Revision::getRevisionText()
- *
- * @return string
- */
- function getText();
-}
-
-/**
- * Concatenated gzip (CGZ) storage
- * Improves compression ratio by concatenating like objects before gzipping
- */
-class ConcatenatedGzipHistoryBlob implements HistoryBlob {
- public $mVersion = 0, $mCompressed = false, $mItems = [], $mDefaultHash = '';
- public $mSize = 0;
- public $mMaxSize = 10000000;
- public $mMaxCount = 100;
-
- public function __construct() {
- if ( !function_exists( 'gzdeflate' ) ) {
- throw new MWException( "Need zlib support to read or write this "
- . "kind of history object (ConcatenatedGzipHistoryBlob)\n" );
- }
- }
-
- /**
- * @param string $text
- * @return string
- */
- public function addItem( $text ) {
- $this->uncompress();
- $hash = md5( $text );
- if ( !isset( $this->mItems[$hash] ) ) {
- $this->mItems[$hash] = $text;
- $this->mSize += strlen( $text );
- }
- return $hash;
- }
-
- /**
- * @param string $hash
- * @return array|bool
- */
- public function getItem( $hash ) {
- $this->uncompress();
- if ( array_key_exists( $hash, $this->mItems ) ) {
- return $this->mItems[$hash];
- } else {
- return false;
- }
- }
-
- /**
- * @param string $text
- * @return void
- */
- public function setText( $text ) {
- $this->uncompress();
- $this->mDefaultHash = $this->addItem( $text );
- }
-
- /**
- * @return array|bool
- */
- public function getText() {
- $this->uncompress();
- return $this->getItem( $this->mDefaultHash );
- }
-
- /**
- * Remove an item
- *
- * @param string $hash
- */
- public function removeItem( $hash ) {
- $this->mSize -= strlen( $this->mItems[$hash] );
- unset( $this->mItems[$hash] );
- }
-
- /**
- * Compress the bulk data in the object
- */
- public function compress() {
- if ( !$this->mCompressed ) {
- $this->mItems = gzdeflate( serialize( $this->mItems ) );
- $this->mCompressed = true;
- }
- }
-
- /**
- * Uncompress bulk data
- */
- public function uncompress() {
- if ( $this->mCompressed ) {
- $this->mItems = unserialize( gzinflate( $this->mItems ) );
- $this->mCompressed = false;
- }
- }
-
- /**
- * @return array
- */
- function __sleep() {
- $this->compress();
- return [ 'mVersion', 'mCompressed', 'mItems', 'mDefaultHash' ];
- }
-
- function __wakeup() {
- $this->uncompress();
- }
-
- /**
- * Helper function for compression jobs
- * Returns true until the object is "full" and ready to be committed
- *
- * @return bool
- */
- public function isHappy() {
- return $this->mSize < $this->mMaxSize
- && count( $this->mItems ) < $this->mMaxCount;
- }
-}
-
-/**
- * Pointer object for an item within a CGZ blob stored in the text table.
- */
-class HistoryBlobStub {
- /**
- * @var array One-step cache variable to hold base blobs; operations that
- * pull multiple revisions may often pull multiple times from the same
- * blob. By keeping the last-used one open, we avoid redundant
- * unserialization and decompression overhead.
- */
- protected static $blobCache = [];
-
- /** @var int */
- public $mOldId;
-
- /** @var string */
- public $mHash;
-
- /** @var string */
- public $mRef;
-
- /**
- * @param string $hash The content hash of the text
- * @param int $oldid The old_id for the CGZ object
- */
- function __construct( $hash = '', $oldid = 0 ) {
- $this->mHash = $hash;
- }
-
- /**
- * Sets the location (old_id) of the main object to which this object
- * points
- * @param int $id
- */
- function setLocation( $id ) {
- $this->mOldId = $id;
- }
-
- /**
- * Sets the location (old_id) of the referring object
- * @param string $id
- */
- function setReferrer( $id ) {
- $this->mRef = $id;
- }
-
- /**
- * Gets the location of the referring object
- * @return string
- */
- function getReferrer() {
- return $this->mRef;
- }
-
- /**
- * @return string|false
- */
- function getText() {
- if ( isset( self::$blobCache[$this->mOldId] ) ) {
- $obj = self::$blobCache[$this->mOldId];
- } else {
- $dbr = wfGetDB( DB_REPLICA );
- $row = $dbr->selectRow(
- 'text',
- [ 'old_flags', 'old_text' ],
- [ 'old_id' => $this->mOldId ]
- );
-
- if ( !$row ) {
- return false;
- }
-
- $flags = explode( ',', $row->old_flags );
- if ( in_array( 'external', $flags ) ) {
- $url = $row->old_text;
- $parts = explode( '://', $url, 2 );
- if ( !isset( $parts[1] ) || $parts[1] == '' ) {
- return false;
- }
- $row->old_text = ExternalStore::fetchFromURL( $url );
-
- }
-
- if ( !in_array( 'object', $flags ) ) {
- return false;
- }
-
- if ( in_array( 'gzip', $flags ) ) {
- // This shouldn't happen, but a bug in the compress script
- // may at times gzip-compress a HistoryBlob object row.
- $obj = unserialize( gzinflate( $row->old_text ) );
- } else {
- $obj = unserialize( $row->old_text );
- }
-
- if ( !is_object( $obj ) ) {
- // Correct for old double-serialization bug.
- $obj = unserialize( $obj );
- }
-
- // Save this item for reference; if pulling many
- // items in a row we'll likely use it again.
- $obj->uncompress();
- self::$blobCache = [ $this->mOldId => $obj ];
- }
-
- return $obj->getItem( $this->mHash );
- }
-
- /**
- * Get the content hash
- *
- * @return string
- */
- function getHash() {
- return $this->mHash;
- }
-}
-
-/**
- * To speed up conversion from 1.4 to 1.5 schema, text rows can refer to the
- * leftover cur table as the backend. This avoids expensively copying hundreds
- * of megabytes of data during the conversion downtime.
- *
- * Serialized HistoryBlobCurStub objects will be inserted into the text table
- * on conversion if $wgLegacySchemaConversion is set to true.
- */
-class HistoryBlobCurStub {
- /** @var int */
- public $mCurId;
-
- /**
- * @param int $curid The cur_id pointed to
- */
- function __construct( $curid = 0 ) {
- $this->mCurId = $curid;
- }
-
- /**
- * Sets the location (cur_id) of the main object to which this object
- * points
- *
- * @param int $id
- */
- function setLocation( $id ) {
- $this->mCurId = $id;
- }
-
- /**
- * @return string|bool
- */
- function getText() {
- $dbr = wfGetDB( DB_REPLICA );
- $row = $dbr->selectRow( 'cur', [ 'cur_text' ], [ 'cur_id' => $this->mCurId ] );
- if ( !$row ) {
- return false;
- }
- return $row->cur_text;
- }
-}
-
-/**
- * Diff-based history compression
- * Requires xdiff 1.5+ and zlib
- */
-class DiffHistoryBlob implements HistoryBlob {
- /** @var array Uncompressed item cache */
- public $mItems = [];
-
- /** @var int Total uncompressed size */
- public $mSize = 0;
-
- /**
- * @var array Array of diffs. If a diff D from A to B is notated D = B - A,
- * and Z is an empty string:
- *
- * { item[map[i]] - item[map[i-1]] where i > 0
- * diff[i] = {
- * { item[map[i]] - Z where i = 0
- */
- public $mDiffs;
-
- /** @var array The diff map, see above */
- public $mDiffMap;
-
- /** @var int The key for getText()
- */
- public $mDefaultKey;
-
- /** @var string Compressed storage */
- public $mCompressed;
-
- /** @var bool True if the object is locked against further writes */
- public $mFrozen = false;
-
- /**
- * @var int The maximum uncompressed size before the object becomes sad
- * Should be less than max_allowed_packet
- */
- public $mMaxSize = 10000000;
-
- /** @var int The maximum number of text items before the object becomes sad */
- public $mMaxCount = 100;
-
- /** Constants from xdiff.h */
- const XDL_BDOP_INS = 1;
- const XDL_BDOP_CPY = 2;
- const XDL_BDOP_INSB = 3;
-
- function __construct() {
- if ( !function_exists( 'gzdeflate' ) ) {
- throw new MWException( "Need zlib support to read or write DiffHistoryBlob\n" );
- }
- }
-
- /**
- * @throws MWException
- * @param string $text
- * @return int
- */
- function addItem( $text ) {
- if ( $this->mFrozen ) {
- throw new MWException( __METHOD__ . ": Cannot add more items after sleep/wakeup" );
- }
-
- $this->mItems[] = $text;
- $this->mSize += strlen( $text );
- $this->mDiffs = null; // later
- return count( $this->mItems ) - 1;
- }
-
- /**
- * @param string $key
- * @return string
- */
- function getItem( $key ) {
- return $this->mItems[$key];
- }
-
- /**
- * @param string $text
- */
- function setText( $text ) {
- $this->mDefaultKey = $this->addItem( $text );
- }
-
- /**
- * @return string
- */
- function getText() {
- return $this->getItem( $this->mDefaultKey );
- }
-
- /**
- * @throws MWException
- */
- function compress() {
- if ( !function_exists( 'xdiff_string_rabdiff' ) ) {
- throw new MWException( "Need xdiff 1.5+ support to write DiffHistoryBlob\n" );
- }
- if ( isset( $this->mDiffs ) ) {
- // Already compressed
- return;
- }
- if ( $this->mItems === [] ) {
- return;
- }
-
- // Create two diff sequences: one for main text and one for small text
- $sequences = [
- 'small' => [
- 'tail' => '',
- 'diffs' => [],
- 'map' => [],
- ],
- 'main' => [
- 'tail' => '',
- 'diffs' => [],
- 'map' => [],
- ],
- ];
- $smallFactor = 0.5;
-
- $mItemsCount = count( $this->mItems );
- for ( $i = 0; $i < $mItemsCount; $i++ ) {
- $text = $this->mItems[$i];
- if ( $i == 0 ) {
- $seqName = 'main';
- } else {
- $mainTail = $sequences['main']['tail'];
- if ( strlen( $text ) < strlen( $mainTail ) * $smallFactor ) {
- $seqName = 'small';
- } else {
- $seqName = 'main';
- }
- }
- $seq =& $sequences[$seqName];
- $tail = $seq['tail'];
- $diff = $this->diff( $tail, $text );
- $seq['diffs'][] = $diff;
- $seq['map'][] = $i;
- $seq['tail'] = $text;
- }
- unset( $seq ); // unlink dangerous alias
-
- // Knit the sequences together
- $tail = '';
- $this->mDiffs = [];
- $this->mDiffMap = [];
- foreach ( $sequences as $seq ) {
- if ( $seq['diffs'] === [] ) {
- continue;
- }
- if ( $tail === '' ) {
- $this->mDiffs[] = $seq['diffs'][0];
- } else {
- $head = $this->patch( '', $seq['diffs'][0] );
- $this->mDiffs[] = $this->diff( $tail, $head );
- }
- $this->mDiffMap[] = $seq['map'][0];
- $diffsCount = count( $seq['diffs'] );
- for ( $i = 1; $i < $diffsCount; $i++ ) {
- $this->mDiffs[] = $seq['diffs'][$i];
- $this->mDiffMap[] = $seq['map'][$i];
- }
- $tail = $seq['tail'];
- }
- }
-
- /**
- * @param string $t1
- * @param string $t2
- * @return string
- */
- function diff( $t1, $t2 ) {
- # Need to do a null concatenation with warnings off, due to bugs in the current version of xdiff
- # "String is not zero-terminated"
- Wikimedia\suppressWarnings();
- $diff = xdiff_string_rabdiff( $t1, $t2 ) . '';
- Wikimedia\restoreWarnings();
- return $diff;
- }
-
- /**
- * @param string $base
- * @param string $diff
- * @return bool|string
- */
- function patch( $base, $diff ) {
- if ( function_exists( 'xdiff_string_bpatch' ) ) {
- Wikimedia\suppressWarnings();
- $text = xdiff_string_bpatch( $base, $diff ) . '';
- Wikimedia\restoreWarnings();
- return $text;
- }
-
- # Pure PHP implementation
-
- $header = unpack( 'Vofp/Vcsize', substr( $diff, 0, 8 ) );
-
- # Check the checksum if hash extension is available
- $ofp = $this->xdiffAdler32( $base );
- if ( $ofp !== false && $ofp !== substr( $diff, 0, 4 ) ) {
- wfDebug( __METHOD__ . ": incorrect base checksum\n" );
- return false;
- }
- if ( $header['csize'] != strlen( $base ) ) {
- wfDebug( __METHOD__ . ": incorrect base length\n" );
- return false;
- }
-
- $p = 8;
- $out = '';
- while ( $p < strlen( $diff ) ) {
- $x = unpack( 'Cop', substr( $diff, $p, 1 ) );
- $op = $x['op'];
- ++$p;
- switch ( $op ) {
- case self::XDL_BDOP_INS:
- $x = unpack( 'Csize', substr( $diff, $p, 1 ) );
- $p++;
- $out .= substr( $diff, $p, $x['size'] );
- $p += $x['size'];
- break;
- case self::XDL_BDOP_INSB:
- $x = unpack( 'Vcsize', substr( $diff, $p, 4 ) );
- $p += 4;
- $out .= substr( $diff, $p, $x['csize'] );
- $p += $x['csize'];
- break;
- case self::XDL_BDOP_CPY:
- $x = unpack( 'Voff/Vcsize', substr( $diff, $p, 8 ) );
- $p += 8;
- $out .= substr( $base, $x['off'], $x['csize'] );
- break;
- default:
- wfDebug( __METHOD__ . ": invalid op\n" );
- return false;
- }
- }
- return $out;
- }
-
- /**
- * Compute a binary "Adler-32" checksum as defined by LibXDiff, i.e. with
- * the bytes backwards and initialised with 0 instead of 1. See T36428.
- *
- * @param string $s
- * @return string|bool False if the hash extension is not available
- */
- function xdiffAdler32( $s ) {
- if ( !function_exists( 'hash' ) ) {
- return false;
- }
-
- static $init;
- if ( $init === null ) {
- $init = str_repeat( "\xf0", 205 ) . "\xee" . str_repeat( "\xf0", 67 ) . "\x02";
- }
-
- // The real Adler-32 checksum of $init is zero, so it initialises the
- // state to zero, as it is at the start of LibXDiff's checksum
- // algorithm. Appending the subject string then simulates LibXDiff.
- return strrev( hash( 'adler32', $init . $s, true ) );
- }
-
- function uncompress() {
- if ( !$this->mDiffs ) {
- return;
- }
- $tail = '';
- $mDiffsCount = count( $this->mDiffs );
- for ( $diffKey = 0; $diffKey < $mDiffsCount; $diffKey++ ) {
- $textKey = $this->mDiffMap[$diffKey];
- $text = $this->patch( $tail, $this->mDiffs[$diffKey] );
- $this->mItems[$textKey] = $text;
- $tail = $text;
- }
- }
-
- /**
- * @return array
- */
- function __sleep() {
- $this->compress();
- if ( $this->mItems === [] ) {
- $info = false;
- } else {
- // Take forward differences to improve the compression ratio for sequences
- $map = '';
- $prev = 0;
- foreach ( $this->mDiffMap as $i ) {
- if ( $map !== '' ) {
- $map .= ',';
- }
- $map .= $i - $prev;
- $prev = $i;
- }
- $info = [
- 'diffs' => $this->mDiffs,
- 'map' => $map
- ];
- }
- if ( isset( $this->mDefaultKey ) ) {
- $info['default'] = $this->mDefaultKey;
- }
- $this->mCompressed = gzdeflate( serialize( $info ) );
- return [ 'mCompressed' ];
- }
-
- function __wakeup() {
- // addItem() doesn't work if mItems is partially filled from mDiffs
- $this->mFrozen = true;
- $info = unserialize( gzinflate( $this->mCompressed ) );
- unset( $this->mCompressed );
-
- if ( !$info ) {
- // Empty object
- return;
- }
-
- if ( isset( $info['default'] ) ) {
- $this->mDefaultKey = $info['default'];
- }
- $this->mDiffs = $info['diffs'];
- if ( isset( $info['base'] ) ) {
- // Old format
- $this->mDiffMap = range( 0, count( $this->mDiffs ) - 1 );
- array_unshift( $this->mDiffs,
- pack( 'VVCV', 0, 0, self::XDL_BDOP_INSB, strlen( $info['base'] ) ) .
- $info['base'] );
- } else {
- // New format
- $map = explode( ',', $info['map'] );
- $cur = 0;
- $this->mDiffMap = [];
- foreach ( $map as $i ) {
- $cur += $i;
- $this->mDiffMap[] = $cur;
- }
- }
- $this->uncompress();
- }
-
- /**
- * Helper function for compression jobs
- * Returns true until the object is "full" and ready to be committed
- *
- * @return bool
- */
- function isHappy() {
- return $this->mSize < $this->mMaxSize
- && count( $this->mItems ) < $this->mMaxCount;
- }
-
-}
-
-// phpcs:ignore Generic.CodeAnalysis.UnconditionalIfStatement.Found
-if ( false ) {
- // Blobs generated by MediaWiki < 1.5 on PHP 4 were serialized with the
- // class name coerced to lowercase. We can improve efficiency by adding
- // autoload entries for the lowercase variants of these classes (T166759).
- // The code below is never executed, but it is picked up by the AutoloadGenerator
- // parser, which scans for class_alias() calls.
- class_alias( ConcatenatedGzipHistoryBlob::class, 'concatenatedgziphistoryblob' );
- class_alias( HistoryBlobCurStub::class, 'historyblobcurstub' );
- class_alias( HistoryBlobStub::class, 'historyblobstub' );
-}
$inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
}
- /**
- * FIXME
- * Remove all references to DisableRollbackConfirmationFeature
- * after release of rollback feature. See T199534
- */
- if ( !MediaWikiServices::getInstance()
- ->getMainConfig()->get( 'DisableRollbackConfirmationFeature' ) &&
- $context->getUser()->getBoolOption( 'showrollbackconfirmation' )
- ) {
+ if ( $context->getUser()->getBoolOption( 'showrollbackconfirmation' ) ) {
$stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
$stats->increment( 'rollbackconfirmation.event.load' );
$context->getOutput()->addModules( 'mediawiki.page.rollback.confirmation' );
$oldid = $this->oldTitle->getArticleID();
- if ( strlen( $this->newTitle->getDBkey() ) < 1 ) {
+ if ( $this->newTitle->getDBkey() === '' ) {
$status->fatal( 'articleexists' );
}
if (
->execute();
$data = trim( $result->getStdout() );
- if ( $result->getExitCode() != 0 || !strlen( $data ) ) {
+ if ( $result->getExitCode() || $data === '' ) {
throw new MWException( "Failed to run getConfiguration.php: {$result->getStdout()}" );
}
$res = unserialize( $data );
* @return string Base name
*/
public function getBaseText() {
+ $text = $this->getText();
if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
- return $this->getText();
+ return $text;
}
- $parts = explode( '/', $this->getText() );
- # Don't discard the real title if there's no subpage involved
- if ( count( $parts ) > 1 ) {
- unset( $parts[count( $parts ) - 1] );
+ $lastSlashPos = strrpos( $text, '/' );
+ // Don't discard the real title if there's no subpage involved
+ if ( $lastSlashPos === false ) {
+ return $text;
}
- return implode( '/', $parts );
+
+ return substr( $text, 0, $lastSlashPos );
}
/**
}
/**
- * @throws ConfigException
* @throws ErrorPageError
* @throws ReadOnlyError
* @throws ThrottledError
*/
public function show() {
- /**
- * FIXME
- * Remove temporary check of DisableRollbackConfirmationFeature
- * after release of rollback feature. See T199534
- */
- $config = \MediaWiki\MediaWikiServices::getInstance()->getMainConfig();
- if ( $config->get( 'DisableRollbackConfirmationFeature' ) == true ||
- $this->getUser()->getOption( 'showrollbackconfirmation' ) == false ||
+ if ( $this->getUser()->getOption( 'showrollbackconfirmation' ) == false ||
$this->getRequest()->wasPosted() ) {
$this->handleRollbackRequest();
} else {
}
$data = [
- 'url' => strlen( $url ) ? $url : '',
- 'text' => strlen( $text ) ? $text : '',
+ 'url' => (string)$url,
+ 'text' => (string)$text,
];
return $this->getResult()->addValue( 'query', $property, $data );
'enableSectionEditTokens' => false,
'allowTOC' => false,
] );
- if ( strlen( $text ) == 0 ) {
+ if ( $text === '' ) {
$this->allText = "";
// empty text - nothing to seek here
return;
--- /dev/null
+<?php
+/**
+ * Efficient concatenated text storage.
+ *
+ * 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
+ *
+ * @file
+ */
+
+/**
+ * Concatenated gzip (CGZ) storage
+ * Improves compression ratio by concatenating like objects before gzipping
+ */
+class ConcatenatedGzipHistoryBlob implements HistoryBlob {
+ public $mVersion = 0, $mCompressed = false, $mItems = [], $mDefaultHash = '';
+ public $mSize = 0;
+ public $mMaxSize = 10000000;
+ public $mMaxCount = 100;
+
+ public function __construct() {
+ if ( !function_exists( 'gzdeflate' ) ) {
+ throw new MWException( "Need zlib support to read or write this "
+ . "kind of history object (ConcatenatedGzipHistoryBlob)\n" );
+ }
+ }
+
+ /**
+ * @param string $text
+ * @return string
+ */
+ public function addItem( $text ) {
+ $this->uncompress();
+ $hash = md5( $text );
+ if ( !isset( $this->mItems[$hash] ) ) {
+ $this->mItems[$hash] = $text;
+ $this->mSize += strlen( $text );
+ }
+ return $hash;
+ }
+
+ /**
+ * @param string $hash
+ * @return array|bool
+ */
+ public function getItem( $hash ) {
+ $this->uncompress();
+ if ( array_key_exists( $hash, $this->mItems ) ) {
+ return $this->mItems[$hash];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @param string $text
+ * @return void
+ */
+ public function setText( $text ) {
+ $this->uncompress();
+ $this->mDefaultHash = $this->addItem( $text );
+ }
+
+ /**
+ * @return array|bool
+ */
+ public function getText() {
+ $this->uncompress();
+ return $this->getItem( $this->mDefaultHash );
+ }
+
+ /**
+ * Remove an item
+ *
+ * @param string $hash
+ */
+ public function removeItem( $hash ) {
+ $this->mSize -= strlen( $this->mItems[$hash] );
+ unset( $this->mItems[$hash] );
+ }
+
+ /**
+ * Compress the bulk data in the object
+ */
+ public function compress() {
+ if ( !$this->mCompressed ) {
+ $this->mItems = gzdeflate( serialize( $this->mItems ) );
+ $this->mCompressed = true;
+ }
+ }
+
+ /**
+ * Uncompress bulk data
+ */
+ public function uncompress() {
+ if ( $this->mCompressed ) {
+ $this->mItems = unserialize( gzinflate( $this->mItems ) );
+ $this->mCompressed = false;
+ }
+ }
+
+ /**
+ * @return array
+ */
+ function __sleep() {
+ $this->compress();
+ return [ 'mVersion', 'mCompressed', 'mItems', 'mDefaultHash' ];
+ }
+
+ function __wakeup() {
+ $this->uncompress();
+ }
+
+ /**
+ * Helper function for compression jobs
+ * Returns true until the object is "full" and ready to be committed
+ *
+ * @return bool
+ */
+ public function isHappy() {
+ return $this->mSize < $this->mMaxSize
+ && count( $this->mItems ) < $this->mMaxCount;
+ }
+}
+
+// phpcs:ignore Generic.CodeAnalysis.UnconditionalIfStatement.Found
+if ( false ) {
+ // Blobs generated by MediaWiki < 1.5 on PHP 4 were serialized with the
+ // class name coerced to lowercase. We can improve efficiency by adding
+ // autoload entries for the lowercase variants of these classes (T166759).
+ // The code below is never executed, but it is picked up by the AutoloadGenerator
+ // parser, which scans for class_alias() calls.
+ class_alias( ConcatenatedGzipHistoryBlob::class, 'concatenatedgziphistoryblob' );
+}
--- /dev/null
+<?php
+/**
+ * Efficient concatenated text storage.
+ *
+ * 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
+ *
+ * @file
+ */
+
+/**
+ * Diff-based history compression
+ * Requires xdiff 1.5+ and zlib
+ */
+class DiffHistoryBlob implements HistoryBlob {
+ /** @var array Uncompressed item cache */
+ public $mItems = [];
+
+ /** @var int Total uncompressed size */
+ public $mSize = 0;
+
+ /**
+ * @var array Array of diffs. If a diff D from A to B is notated D = B - A,
+ * and Z is an empty string:
+ *
+ * { item[map[i]] - item[map[i-1]] where i > 0
+ * diff[i] = {
+ * { item[map[i]] - Z where i = 0
+ */
+ public $mDiffs;
+
+ /** @var array The diff map, see above */
+ public $mDiffMap;
+
+ /** @var int The key for getText()
+ */
+ public $mDefaultKey;
+
+ /** @var string Compressed storage */
+ public $mCompressed;
+
+ /** @var bool True if the object is locked against further writes */
+ public $mFrozen = false;
+
+ /**
+ * @var int The maximum uncompressed size before the object becomes sad
+ * Should be less than max_allowed_packet
+ */
+ public $mMaxSize = 10000000;
+
+ /** @var int The maximum number of text items before the object becomes sad */
+ public $mMaxCount = 100;
+
+ /** Constants from xdiff.h */
+ const XDL_BDOP_INS = 1;
+ const XDL_BDOP_CPY = 2;
+ const XDL_BDOP_INSB = 3;
+
+ function __construct() {
+ if ( !function_exists( 'gzdeflate' ) ) {
+ throw new MWException( "Need zlib support to read or write DiffHistoryBlob\n" );
+ }
+ }
+
+ /**
+ * @throws MWException
+ * @param string $text
+ * @return int
+ */
+ function addItem( $text ) {
+ if ( $this->mFrozen ) {
+ throw new MWException( __METHOD__ . ": Cannot add more items after sleep/wakeup" );
+ }
+
+ $this->mItems[] = $text;
+ $this->mSize += strlen( $text );
+ $this->mDiffs = null; // later
+ return count( $this->mItems ) - 1;
+ }
+
+ /**
+ * @param string $key
+ * @return string
+ */
+ function getItem( $key ) {
+ return $this->mItems[$key];
+ }
+
+ /**
+ * @param string $text
+ */
+ function setText( $text ) {
+ $this->mDefaultKey = $this->addItem( $text );
+ }
+
+ /**
+ * @return string
+ */
+ function getText() {
+ return $this->getItem( $this->mDefaultKey );
+ }
+
+ /**
+ * @throws MWException
+ */
+ function compress() {
+ if ( !function_exists( 'xdiff_string_rabdiff' ) ) {
+ throw new MWException( "Need xdiff 1.5+ support to write DiffHistoryBlob\n" );
+ }
+ if ( isset( $this->mDiffs ) ) {
+ // Already compressed
+ return;
+ }
+ if ( $this->mItems === [] ) {
+ return;
+ }
+
+ // Create two diff sequences: one for main text and one for small text
+ $sequences = [
+ 'small' => [
+ 'tail' => '',
+ 'diffs' => [],
+ 'map' => [],
+ ],
+ 'main' => [
+ 'tail' => '',
+ 'diffs' => [],
+ 'map' => [],
+ ],
+ ];
+ $smallFactor = 0.5;
+
+ $mItemsCount = count( $this->mItems );
+ for ( $i = 0; $i < $mItemsCount; $i++ ) {
+ $text = $this->mItems[$i];
+ if ( $i == 0 ) {
+ $seqName = 'main';
+ } else {
+ $mainTail = $sequences['main']['tail'];
+ if ( strlen( $text ) < strlen( $mainTail ) * $smallFactor ) {
+ $seqName = 'small';
+ } else {
+ $seqName = 'main';
+ }
+ }
+ $seq =& $sequences[$seqName];
+ $tail = $seq['tail'];
+ $diff = $this->diff( $tail, $text );
+ $seq['diffs'][] = $diff;
+ $seq['map'][] = $i;
+ $seq['tail'] = $text;
+ }
+ unset( $seq ); // unlink dangerous alias
+
+ // Knit the sequences together
+ $tail = '';
+ $this->mDiffs = [];
+ $this->mDiffMap = [];
+ foreach ( $sequences as $seq ) {
+ if ( $seq['diffs'] === [] ) {
+ continue;
+ }
+ if ( $tail === '' ) {
+ $this->mDiffs[] = $seq['diffs'][0];
+ } else {
+ $head = $this->patch( '', $seq['diffs'][0] );
+ $this->mDiffs[] = $this->diff( $tail, $head );
+ }
+ $this->mDiffMap[] = $seq['map'][0];
+ $diffsCount = count( $seq['diffs'] );
+ for ( $i = 1; $i < $diffsCount; $i++ ) {
+ $this->mDiffs[] = $seq['diffs'][$i];
+ $this->mDiffMap[] = $seq['map'][$i];
+ }
+ $tail = $seq['tail'];
+ }
+ }
+
+ /**
+ * @param string $t1
+ * @param string $t2
+ * @return string
+ */
+ function diff( $t1, $t2 ) {
+ # Need to do a null concatenation with warnings off, due to bugs in the current version of xdiff
+ # "String is not zero-terminated"
+ Wikimedia\suppressWarnings();
+ $diff = xdiff_string_rabdiff( $t1, $t2 ) . '';
+ Wikimedia\restoreWarnings();
+ return $diff;
+ }
+
+ /**
+ * @param string $base
+ * @param string $diff
+ * @return bool|string
+ */
+ function patch( $base, $diff ) {
+ if ( function_exists( 'xdiff_string_bpatch' ) ) {
+ Wikimedia\suppressWarnings();
+ $text = xdiff_string_bpatch( $base, $diff ) . '';
+ Wikimedia\restoreWarnings();
+ return $text;
+ }
+
+ # Pure PHP implementation
+
+ $header = unpack( 'Vofp/Vcsize', substr( $diff, 0, 8 ) );
+
+ # Check the checksum if hash extension is available
+ $ofp = $this->xdiffAdler32( $base );
+ if ( $ofp !== false && $ofp !== substr( $diff, 0, 4 ) ) {
+ wfDebug( __METHOD__ . ": incorrect base checksum\n" );
+ return false;
+ }
+ if ( $header['csize'] != strlen( $base ) ) {
+ wfDebug( __METHOD__ . ": incorrect base length\n" );
+ return false;
+ }
+
+ $p = 8;
+ $out = '';
+ while ( $p < strlen( $diff ) ) {
+ $x = unpack( 'Cop', substr( $diff, $p, 1 ) );
+ $op = $x['op'];
+ ++$p;
+ switch ( $op ) {
+ case self::XDL_BDOP_INS:
+ $x = unpack( 'Csize', substr( $diff, $p, 1 ) );
+ $p++;
+ $out .= substr( $diff, $p, $x['size'] );
+ $p += $x['size'];
+ break;
+ case self::XDL_BDOP_INSB:
+ $x = unpack( 'Vcsize', substr( $diff, $p, 4 ) );
+ $p += 4;
+ $out .= substr( $diff, $p, $x['csize'] );
+ $p += $x['csize'];
+ break;
+ case self::XDL_BDOP_CPY:
+ $x = unpack( 'Voff/Vcsize', substr( $diff, $p, 8 ) );
+ $p += 8;
+ $out .= substr( $base, $x['off'], $x['csize'] );
+ break;
+ default:
+ wfDebug( __METHOD__ . ": invalid op\n" );
+ return false;
+ }
+ }
+ return $out;
+ }
+
+ /**
+ * Compute a binary "Adler-32" checksum as defined by LibXDiff, i.e. with
+ * the bytes backwards and initialised with 0 instead of 1. See T36428.
+ *
+ * @param string $s
+ * @return string|bool False if the hash extension is not available
+ */
+ function xdiffAdler32( $s ) {
+ if ( !function_exists( 'hash' ) ) {
+ return false;
+ }
+
+ static $init;
+ if ( $init === null ) {
+ $init = str_repeat( "\xf0", 205 ) . "\xee" . str_repeat( "\xf0", 67 ) . "\x02";
+ }
+
+ // The real Adler-32 checksum of $init is zero, so it initialises the
+ // state to zero, as it is at the start of LibXDiff's checksum
+ // algorithm. Appending the subject string then simulates LibXDiff.
+ return strrev( hash( 'adler32', $init . $s, true ) );
+ }
+
+ function uncompress() {
+ if ( !$this->mDiffs ) {
+ return;
+ }
+ $tail = '';
+ $mDiffsCount = count( $this->mDiffs );
+ for ( $diffKey = 0; $diffKey < $mDiffsCount; $diffKey++ ) {
+ $textKey = $this->mDiffMap[$diffKey];
+ $text = $this->patch( $tail, $this->mDiffs[$diffKey] );
+ $this->mItems[$textKey] = $text;
+ $tail = $text;
+ }
+ }
+
+ /**
+ * @return array
+ */
+ function __sleep() {
+ $this->compress();
+ if ( $this->mItems === [] ) {
+ $info = false;
+ } else {
+ // Take forward differences to improve the compression ratio for sequences
+ $map = '';
+ $prev = 0;
+ foreach ( $this->mDiffMap as $i ) {
+ if ( $map !== '' ) {
+ $map .= ',';
+ }
+ $map .= $i - $prev;
+ $prev = $i;
+ }
+ $info = [
+ 'diffs' => $this->mDiffs,
+ 'map' => $map
+ ];
+ }
+ if ( isset( $this->mDefaultKey ) ) {
+ $info['default'] = $this->mDefaultKey;
+ }
+ $this->mCompressed = gzdeflate( serialize( $info ) );
+ return [ 'mCompressed' ];
+ }
+
+ function __wakeup() {
+ // addItem() doesn't work if mItems is partially filled from mDiffs
+ $this->mFrozen = true;
+ $info = unserialize( gzinflate( $this->mCompressed ) );
+ unset( $this->mCompressed );
+
+ if ( !$info ) {
+ // Empty object
+ return;
+ }
+
+ if ( isset( $info['default'] ) ) {
+ $this->mDefaultKey = $info['default'];
+ }
+ $this->mDiffs = $info['diffs'];
+ if ( isset( $info['base'] ) ) {
+ // Old format
+ $this->mDiffMap = range( 0, count( $this->mDiffs ) - 1 );
+ array_unshift( $this->mDiffs,
+ pack( 'VVCV', 0, 0, self::XDL_BDOP_INSB, strlen( $info['base'] ) ) .
+ $info['base'] );
+ } else {
+ // New format
+ $map = explode( ',', $info['map'] );
+ $cur = 0;
+ $this->mDiffMap = [];
+ foreach ( $map as $i ) {
+ $cur += $i;
+ $this->mDiffMap[] = $cur;
+ }
+ }
+ $this->uncompress();
+ }
+
+ /**
+ * Helper function for compression jobs
+ * Returns true until the object is "full" and ready to be committed
+ *
+ * @return bool
+ */
+ function isHappy() {
+ return $this->mSize < $this->mMaxSize
+ && count( $this->mItems ) < $this->mMaxCount;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Efficient concatenated text storage.
+ *
+ * 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
+ *
+ * @file
+ */
+
+/**
+ * Base class for general text storage via the "object" flag in old_flags, or
+ * two-part external storage URLs. Used for represent efficient concatenated
+ * storage, and migration-related pointer objects.
+ */
+interface HistoryBlob {
+ /**
+ * Adds an item of text, returns a stub object which points to the item.
+ * You must call setLocation() on the stub object before storing it to the
+ * database
+ *
+ * @param string $text
+ *
+ * @return string The key for getItem()
+ */
+ function addItem( $text );
+
+ /**
+ * Get item by key, or false if the key is not present
+ *
+ * @param string $key
+ *
+ * @return string|bool
+ */
+ function getItem( $key );
+
+ /**
+ * Set the "default text"
+ * This concept is an odd property of the current DB schema, whereby each text item has a revision
+ * associated with it. The default text is the text of the associated revision. There may, however,
+ * be other revisions in the same object.
+ *
+ * Default text is not required for two-part external storage URLs.
+ *
+ * @param string $text
+ */
+ function setText( $text );
+
+ /**
+ * Get default text. This is called from Revision::getRevisionText()
+ *
+ * @return string
+ */
+ function getText();
+}
--- /dev/null
+<?php
+/**
+ * Efficient concatenated text storage.
+ *
+ * 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
+ *
+ * @file
+ */
+
+/**
+ * To speed up conversion from 1.4 to 1.5 schema, text rows can refer to the
+ * leftover cur table as the backend. This avoids expensively copying hundreds
+ * of megabytes of data during the conversion downtime.
+ *
+ * Serialized HistoryBlobCurStub objects will be inserted into the text table
+ * on conversion if $wgLegacySchemaConversion is set to true.
+ */
+class HistoryBlobCurStub {
+ /** @var int */
+ public $mCurId;
+
+ /**
+ * @param int $curid The cur_id pointed to
+ */
+ function __construct( $curid = 0 ) {
+ $this->mCurId = $curid;
+ }
+
+ /**
+ * Sets the location (cur_id) of the main object to which this object
+ * points
+ *
+ * @param int $id
+ */
+ function setLocation( $id ) {
+ $this->mCurId = $id;
+ }
+
+ /**
+ * @return string|bool
+ */
+ function getText() {
+ $dbr = wfGetDB( DB_REPLICA );
+ $row = $dbr->selectRow( 'cur', [ 'cur_text' ], [ 'cur_id' => $this->mCurId ] );
+ if ( !$row ) {
+ return false;
+ }
+ return $row->cur_text;
+ }
+}
+
+// phpcs:ignore Generic.CodeAnalysis.UnconditionalIfStatement.Found
+if ( false ) {
+ // Blobs generated by MediaWiki < 1.5 on PHP 4 were serialized with the
+ // class name coerced to lowercase. We can improve efficiency by adding
+ // autoload entries for the lowercase variants of these classes (T166759).
+ // The code below is never executed, but it is picked up by the AutoloadGenerator
+ // parser, which scans for class_alias() calls.
+ class_alias( HistoryBlobCurStub::class, 'historyblobcurstub' );
+}
--- /dev/null
+<?php
+/**
+ * Efficient concatenated text storage.
+ *
+ * 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
+ *
+ * @file
+ */
+
+/**
+ * Pointer object for an item within a CGZ blob stored in the text table.
+ */
+class HistoryBlobStub {
+ /**
+ * @var array One-step cache variable to hold base blobs; operations that
+ * pull multiple revisions may often pull multiple times from the same
+ * blob. By keeping the last-used one open, we avoid redundant
+ * unserialization and decompression overhead.
+ */
+ protected static $blobCache = [];
+
+ /** @var int */
+ public $mOldId;
+
+ /** @var string */
+ public $mHash;
+
+ /** @var string */
+ public $mRef;
+
+ /**
+ * @param string $hash The content hash of the text
+ * @param int $oldid The old_id for the CGZ object
+ */
+ function __construct( $hash = '', $oldid = 0 ) {
+ $this->mHash = $hash;
+ }
+
+ /**
+ * Sets the location (old_id) of the main object to which this object
+ * points
+ * @param int $id
+ */
+ function setLocation( $id ) {
+ $this->mOldId = $id;
+ }
+
+ /**
+ * Sets the location (old_id) of the referring object
+ * @param string $id
+ */
+ function setReferrer( $id ) {
+ $this->mRef = $id;
+ }
+
+ /**
+ * Gets the location of the referring object
+ * @return string
+ */
+ function getReferrer() {
+ return $this->mRef;
+ }
+
+ /**
+ * @return string|false
+ */
+ function getText() {
+ if ( isset( self::$blobCache[$this->mOldId] ) ) {
+ $obj = self::$blobCache[$this->mOldId];
+ } else {
+ $dbr = wfGetDB( DB_REPLICA );
+ $row = $dbr->selectRow(
+ 'text',
+ [ 'old_flags', 'old_text' ],
+ [ 'old_id' => $this->mOldId ]
+ );
+
+ if ( !$row ) {
+ return false;
+ }
+
+ $flags = explode( ',', $row->old_flags );
+ if ( in_array( 'external', $flags ) ) {
+ $url = $row->old_text;
+ $parts = explode( '://', $url, 2 );
+ if ( !isset( $parts[1] ) || $parts[1] == '' ) {
+ return false;
+ }
+ $row->old_text = ExternalStore::fetchFromURL( $url );
+
+ }
+
+ if ( !in_array( 'object', $flags ) ) {
+ return false;
+ }
+
+ if ( in_array( 'gzip', $flags ) ) {
+ // This shouldn't happen, but a bug in the compress script
+ // may at times gzip-compress a HistoryBlob object row.
+ $obj = unserialize( gzinflate( $row->old_text ) );
+ } else {
+ $obj = unserialize( $row->old_text );
+ }
+
+ if ( !is_object( $obj ) ) {
+ // Correct for old double-serialization bug.
+ $obj = unserialize( $obj );
+ }
+
+ // Save this item for reference; if pulling many
+ // items in a row we'll likely use it again.
+ $obj->uncompress();
+ self::$blobCache = [ $this->mOldId => $obj ];
+ }
+
+ return $obj->getItem( $this->mHash );
+ }
+
+ /**
+ * Get the content hash
+ *
+ * @return string
+ */
+ function getHash() {
+ return $this->mHash;
+ }
+}
+
+// phpcs:ignore Generic.CodeAnalysis.UnconditionalIfStatement.Found
+if ( false ) {
+ // Blobs generated by MediaWiki < 1.5 on PHP 4 were serialized with the
+ // class name coerced to lowercase. We can improve efficiency by adding
+ // autoload entries for the lowercase variants of these classes (T166759).
+ // The code below is never executed, but it is picked up by the AutoloadGenerator
+ // parser, which scans for class_alias() calls.
+ class_alias( HistoryBlobStub::class, 'historyblobstub' );
+}
break;
}
- if ( strlen( $buf ) ) {
+ if ( $buf !== '' ) {
call_user_func( $this->callback, $fh, $buf );
}
}
}
if ( $status->isOk() ) {
$this->showMessage(
- 'config-install-success',
- $this->getVar( 'wgServer' ),
- $this->getVar( 'wgScriptPath' )
+ 'config-install-db-success'
);
$this->setVar( '_InstallDone', true );
}
"config-install-done": "<strong>Congratulations!</strong>\nYou have installed MediaWiki.\n\nThe installer has generated a <code>LocalSettings.php</code> file.\nIt contains all your configuration.\n\nYou will need to download it and put it in the base of your wiki installation (the same directory as index.php). The download should have started automatically.\n\nIf the download was not offered, or if you cancelled it, you can restart the download by clicking the link below:\n\n$3\n\n<strong>Note:</strong> If you do not do this now, this generated configuration file will not be available to you later if you exit the installation without downloading it.\n\nWhen that has been done, you can <strong>[$2 enter your wiki]</strong>.",
"config-install-done-path": "<strong>Congratulations!</strong>\nYou have installed MediaWiki.\n\nThe installer has generated a <code>LocalSettings.php</code> file.\nIt contains all your configuration.\n\nYou will need to download it and put it at <code>$4</code>. The download should have started automatically.\n\nIf the download was not offered, or if you cancelled it, you can restart the download by clicking the link below:\n\n$3\n\n<strong>Note:</strong> If you do not do this now, this generated configuration file will not be available to you later if you exit the installation without downloading it.\n\nWhen that has been done, you can <strong>[$2 enter your wiki]</strong>.",
"config-install-success": "MediaWiki has been successfully installed. You can now visit <$1$2> to view your wiki.\nIf you have questions, check out our frequently asked questions list:\n<https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ> or use one of the\nsupport forums linked on that page.",
+ "config-install-db-success": "Database was successfully set up",
"config-download-localsettings": "Download <code>LocalSettings.php</code>",
"config-help": "help",
"config-help-tooltip": "click to expand",
"config-install-mainpage-failed": "Used as error message. Parameters:\n* $1 - detailed error message",
"config-install-done": "Parameters:\n* $1 is the URL to LocalSettings download\n* $2 is a link to the wiki.\n* $3 is a download link with attached download icon. The config-download-localsettings message will be used as the link text.",
"config-install-done-path": "Parameters:\n* $1 is the URL to LocalSettings download\n* $2 is a link to the wiki.\n* $3 is a download link with attached download icon. The config-download-localsettings message will be used as the link text.\n* $4 is the filesystem location of where the LocalSettings.php file should be saved to.",
- "config-install-success": "Gives user information that installation was successful. Parameters:\n* $1 - server name\n* $2 - script path",
+ "config-install-db-success": "Shown after DB is set up. In web installer this is step prior to downloading LocalSettings.php",
+ "config-install-success": "Gives user information that installation was successful. Only shown in command line installer. Parameters:\n* $1 - server name\n* $2 - script path",
"config-download-localsettings": "The link text used in the download link in config-install-done.",
"config-help": "This is used in help boxes.\n{{Identical|Help}}",
"config-help-tooltip": "Tooltip for the 'help' links ({{msg-mw|config-help}}), to make it clear they'll expand in place rather than open a new page",
if ( $wgUploadThumbnailRenderHttpCustomDomain ) {
$parsedUrl = wfParseUrl( $thumbUrl );
- if ( !$parsedUrl || !isset( $parsedUrl['path'] ) || !strlen( $parsedUrl['path'] ) ) {
+ if ( !isset( $parsedUrl['path'] ) || $parsedUrl['path'] === '' ) {
$this->setLastError( __METHOD__ . ": invalid thumb URL: $thumbUrl" );
return false;
}
*/
protected function mergeViaCas( $key, $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
do {
- $this->clearLastError();
- $reportDupes = $this->reportDupes;
- $this->reportDupes = false;
$casToken = null; // passed by reference
+ // Get the old value and CAS token from cache
+ $this->clearLastError();
$currentValue = $this->doGet( $key, self::READ_LATEST, $casToken );
- $this->reportDupes = $reportDupes;
-
if ( $this->getLastError() ) {
$this->logger->warning(
__METHOD__ . ' failed due to I/O error on get() for {key}.',
/** Tiny positive float to use when using "minTime" to assert an inequality */
const TINY_POSTIVE = 0.000001;
- /** Seconds of delay after get() where set() storms are a consideration with 'lockTSE' */
- const SET_DELAY_HIGH_SEC = 0.1;
+ /** Milliseconds of delay after get() where set() storms are a consideration with 'lockTSE' */
+ const SET_DELAY_HIGH_MS = 50;
/** Min millisecond set() backoff for keys in hold-off (far less than INTERIM_KEY_TTL) */
const RECENT_SET_LOW_MS = 50;
/** Max millisecond set() backoff for keys in hold-off (far less than INTERIM_KEY_TTL) */
* @param int $ttl Seconds to live. Special values are:
* - WANObjectCache::TTL_INDEFINITE: Cache forever (default)
* @param array $opts Options map:
- * - lag : Seconds of replica DB lag. Typically, this is either the replica DB lag
+ * - lag: seconds of replica DB lag. Typically, this is either the replica DB lag
* before the data was read or, if applicable, the replica DB lag before
* the snapshot-isolated transaction the data was read from started.
* Use false to indicate that replication is not running.
* Default: 0 seconds
- * - since : UNIX timestamp of the data in $value. Typically, this is either
+ * - since: UNIX timestamp of the data in $value. Typically, this is either
* the current time the data was read or (if applicable) the time when
* the snapshot-isolated transaction the data was read from started.
* Default: 0 seconds
- * - pending : Whether this data is possibly from an uncommitted write transaction.
+ * - pending: whether this data is possibly from an uncommitted write transaction.
* Generally, other threads should not see values from the future and
* they certainly should not see ones that ended up getting rolled back.
* Default: false
- * - lockTSE : if excessive replication/snapshot lag is detected, then store the value
+ * - lockTSE: if excessive replication/snapshot lag is detected, then store the value
* with this TTL and flag it as stale. This is only useful if the reads for this key
* use getWithSetCallback() with "lockTSE" set. Note that if "staleTTL" is set
* then it will still add on to this TTL in the excessive lag scenario.
* Default: WANObjectCache::TSE_NONE
- * - staleTTL : Seconds to keep the key around if it is stale. The get()/getMulti()
+ * - staleTTL: seconds to keep the key around if it is stale. The get()/getMulti()
* methods return such stale values with a $curTTL of 0, and getWithSetCallback()
* will call the regeneration callback in such cases, passing in the old value
* and its as-of time to the callback. This is useful if adaptiveTTL() is used
* on the old value's as-of time when it is verified as still being correct.
* Default: WANObjectCache::STALE_TTL_NONE.
+ * - creating: optimize for the case where the key does not already exist.
+ * Default: false
* @note Options added in 1.28: staleTTL
+ * @note Options added in 1.33: creating
* @return bool Success
*/
final public function set( $key, $value, $ttl = self::TTL_INDEFINITE, array $opts = [] ) {
$lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
$staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
$age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
+ $creating = $opts['creating'] ?? false;
$lag = $opts['lag'] ?? 0;
// Do not cache potentially uncommitted data as it might get rolled back
// Wrap that value with time/TTL/version metadata
$wrapped = $this->wrap( $value, $logicalTTL ?: $ttl, $now );
+ $storeTTL = $ttl + $staleTTL;
- $func = function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
- return ( is_string( $cWrapped ) )
- ? false // key is tombstoned; do nothing
- : $wrapped;
- };
+ if ( $creating ) {
+ $ok = $this->cache->add( self::VALUE_KEY_PREFIX . $key, $wrapped, $storeTTL );
+ } else {
+ $ok = $this->cache->merge(
+ self::VALUE_KEY_PREFIX . $key,
+ function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
+ // A string value means that it is a tombstone; do nothing in that case
+ return ( is_string( $cWrapped ) ) ? false : $wrapped;
+ },
+ $storeTTL,
+ 1 // 1 attempt
+ );
+ }
- return $this->cache->merge( self::VALUE_KEY_PREFIX . $key, $func, $ttl + $staleTTL, 1 );
+ return $ok;
}
/**
* stampede is worth avoiding. Note that if the key falls out of cache then concurrent
* threads will all run the callback on cache miss until the value is saved in cache.
* The only stampede protection in that case is from duplicate cache sets when the
- * callback takes longer than WANObjectCache::SET_DELAY_HIGH_SEC seconds; consider
+ * callback takes longer than WANObjectCache::SET_DELAY_HIGH_MS milliseconds; consider
* using "busyValue" if such stampedes are a problem. Note that the higher "lockTSE" is
* set, the higher the worst-case staleness of returned values can be. Also note that
* this option does not by itself handle the case of the key simply expiring on account
}
} elseif ( !$useMutex || $hasLock ) {
if ( $this->checkAndSetCooloff( $key, $kClass, $ago, $lockTSE, $hasLock ) ) {
+ $setOpts['creating'] = ( $curValue === false );
// Save the value unless a lock-winning thread is already expected to do that
$setOpts['lockTSE'] = $lockTSE;
$setOpts['staleTTL'] = $staleTTL;
// consistent hashing).
if ( $lockTSE < 0 || $hasLock ) {
return true; // either not a priori hot or thread has the lock
- } elseif ( $elapsed <= self::SET_DELAY_HIGH_SEC ) {
+ } elseif ( $elapsed <= self::SET_DELAY_HIGH_MS * 1e3 ) {
return true; // not enough time for threads to pile up
}
* @param string $prefix Table prefix
*/
public function __construct( $database, $schema, $prefix ) {
- if ( $database !== null && ( !is_string( $database ) || !strlen( $database ) ) ) {
+ if ( $database !== null && ( !is_string( $database ) || $database === '' ) ) {
throw new InvalidArgumentException( 'Database must be null or a non-empty string.' );
}
$this->database = $database;
- if ( $schema !== null && ( !is_string( $schema ) || !strlen( $schema ) ) ) {
+ if ( $schema !== null && ( !is_string( $schema ) || $schema === '' ) ) {
throw new InvalidArgumentException( 'Schema must be null or a non-empty string.' );
}
$this->schema = $schema;
'section' => 'rendering/advancedrendering',
'label-message' => 'tog-showrollbackconfirmation',
];
-
- /**
- * FIXME
- * Remove temporary help text and references to DisableRollbackConfirmationFeature
- * after release of rollback feature. See T199534
- */
- if ( MediaWikiServices::getInstance()
- ->getMainConfig()->get( 'DisableRollbackConfirmationFeature' ) ) {
- $defaultPreferences['showrollbackconfirmation']
- ['help-message'] = 'tog-showrollbackconfirmation-prerelease-warning';
- }
}
}
}
/**
- * Main execution point
- *
- * @param string $subpage
+ * @param string|null $subpage
*/
public function execute( $subpage ) {
$this->rcSubpage = $subpage;
abstract class FormSpecialPage extends SpecialPage {
/**
* The sub-page of the special page.
- * @var string
+ * @var string|null
*/
protected $par = null;
/**
* Basic SpecialPage workflow: get a form, send it to the user; get some data back,
*
- * @param string $par Subpage string if one was specified
+ * @param string|null $par Subpage string if one was specified
*/
public function execute( $par ) {
$this->setParameter( $par );
/**
* Maybe do something interesting with the subpage parameter
- * @param string $par
+ * @param string|null $par
*/
protected function setParameter( $par ) {
$this->par = $par;
/**
* This is the actual workhorse. It does everything needed to make a
* real, honest-to-gosh query page.
- * @param string $par
+ * @param string|null $par
*/
public function execute( $par ) {
$user = $this->getUser();
}
/**
- * Show the special page
- *
- * @param string $par Parameter passed to the page or null
+ * @param string|null $par Parameter passed to the page or null
*/
public function execute( $par ) {
$out = $this->getOutput();
}
/**
- * Show the special page
- *
- * @param string $par Parameter passed to the page or null
+ * @param string|null $par Parameter passed to the page or null
*/
public function execute( $par ) {
$out = $this->getOutput();
}
/**
- * Main execution point
- *
- * @param string $par Title fragment
+ * @param string|null $par Title fragment
*/
public function execute( $par ) {
$this->setHeaders();
}
/**
- * Main execution point
- *
- * @param string $par Title fragment
+ * @param string|null $par Title fragment
*/
public function execute( $par ) {
$this->setHeaders();
}
/**
- * Show the special page
- *
- * @param string $isbn ISBN passed as a subpage parameter
+ * @param string|null $isbn ISBN passed as a subpage parameter
*/
public function execute( $isbn ) {
$out = $this->getOutput();
/**
* Show a form for filtering namespace and username
*
- * @param string $par
+ * @param string|null $par
* @return string
*/
public function execute( $par ) {
}
/**
- * Show the special page
- *
- * @param string $par (optional) A group to list users from
+ * @param string|null $par (optional) A group to list users from
*/
public function execute( $par ) {
$this->setHeaders();
parent::__construct( 'Newpages' );
}
+ /**
+ * @param string|null $par
+ */
protected function setup( $par ) {
$opts = new FormOptions();
$this->opts = $opts; // bind
$opts->validateIntBounds( 'limit', 0, 5000 );
}
+ /**
+ * @param string $par
+ */
protected function parseParams( $par ) {
$bits = preg_split( '/\s*,\s*/', trim( $par ) );
foreach ( $bits as $bit ) {
/**
* Show a form for filtering namespace and username
*
- * @param string $par
+ * @param string|null $par
*/
public function execute( $par ) {
$out = $this->getOutput();
}
/**
- * Main execution point
- *
- * @param string $subpage
+ * @param string|null $subpage
*/
public function execute( $subpage ) {
// Backwards-compatibility: redirect to new feed URLs
/**
* Entry point
*
- * @param string $par
+ * @param string|null $par
*/
public function execute( $par ) {
$request = $this->getRequest();
// parameter, but also as part of the primary url. This can have PII implications
// in releasing page view data. As such issue a 301 redirect to the correct
// URL.
- if ( strlen( $par ) && !strlen( $term ) ) {
+ if ( $par !== null && $par !== '' && $term === '' ) {
$query = $request->getValues();
unset( $query['title'] );
// Strip underscores from title parameter; most of the time we'll want
}
/**
- * Special page entry point
- * @param string $par
+ * @param string|null $par
* @throws ErrorPageError
* @throws Exception
* @throws FatalError
/**
* Execute page -- can output a file directly or show a listing of them.
*
- * @param string $subPage Subpage, e.g. in
+ * @param string|null $subPage Subpage, e.g. in
* https://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part
* @return bool Success
*/
"tog-useeditwarning": "Warn me when I leave an edit page with unsaved changes",
"tog-prefershttps": "Always use a secure connection while logged in",
"tog-showrollbackconfirmation": "Show a confirmation prompt when clicking on a rollback link",
- "tog-showrollbackconfirmation-prerelease-warning": "Please note: This feature is not available yet. If you set this preference now, your choice will be remembered [https://meta.wikimedia.org/wiki/WMDE_Technical_Wishes/Rollback#Status when the feature is released].",
"underline-always": "Always",
"underline-never": "Never",
"underline-default": "Skin or browser default",
"tog-useeditwarning": "Used as label for the checkbox in [[Special:Preferences#mw-prefsection-editing|Special:Preferences]].",
"tog-prefershttps": "Toggle option used in [[Special:Preferences]] that indicates if the user wants to use a secure connection when logged in",
"tog-showrollbackconfirmation": "Toggle option used in [[Special:Preferences]] to enable/disable rollback confirmation prompt. Should be visible only to users with rollback rights.",
- "tog-showrollbackconfirmation-prerelease-warning": "Notice for wikis where the option can be set before the feature is enabled.\n\nNote: This notice is temporary and will only appear before the rollback confirmation feature is released.",
"underline-always": "Used in [[Special:Preferences#mw-prefsection-rendering|Preferences]].\n\nThis option means \"always underline links\", there are also options {{msg-mw|Underline-never}} and {{msg-mw|Underline-default}}.\n\n{{Gender}}\n{{Identical|Always}}",
"underline-never": "Used in [[Special:Preferences#mw-prefsection-rendering|Preferences]].\n\nThis option means \"never underline links\", there are also options {{msg-mw|Underline-always}} and {{msg-mw|Underline-default}}.\n\n{{Gender}}\n{{Identical|Never}}",
"underline-default": "Used in [[Special:Preferences#mw-prefsection-rendering|Preferences]].\n\nThis option means \"underline links as in your user skin or your browser\", there are also options {{msg-mw|Underline-never}} and {{msg-mw|Underline-always}}.\n\n{{Gender}}\n{{Identical|Browser default}}",
"Via maxima",
"Uharteko",
"Taxandru",
- "Macofe"
+ "Macofe",
+ "Zoranzoki21"
]
},
"tog-underline": "Sutalìnia sos ligòngios",
"disclaimerpage": "Project:Avertèntzias generales",
"edithelp": "Agiudu pro su càmbiu o s'iscritura",
"helppage-top-gethelp": "Agiudu",
- "mainpage": "Pàgina Base",
+ "mainpage": "Pàgina printzipale",
"mainpage-description": "Pàgina printzipale",
"policy-url": "Project:Polìticas",
"portal": "Portale comunidade",
$rtl = true;
+$namespaceNames = [
+ NS_MEDIA => 'ߟߊߛߋߢߊߥߙߍ',
+ NS_SPECIAL => 'ߞߙߍߞߙߍߣߍ߲',
+ NS_TALK => 'ߢߊߝߐߞߣߍ',
+ NS_USER => 'ߟߊߓߊ߯ߙߟߊ',
+ NS_USER_TALK => 'ߟߊߓߊ߯ߙߟߊ ߟߊ߫ ߢߊߝߐߞߣߍ',
+ NS_PROJECT_TALK => '$1 ߢߊߝߐߞߣߍ',
+ NS_FILE => 'ߞߐߕߐ߮',
+ NS_FILE_TALK => 'ߞߐߕߐ߮ ߢߊߝߐߞߣߍ',
+ NS_MEDIAWIKI => 'ߡߘߌߦߊߥߞߌ',
+ NS_MEDIAWIKI_TALK => 'ߡߘߌߦߊߥߞߌ ߢߊߝߐߞߣߍ',
+ NS_TEMPLATE => 'ߞߙߊߞߏ',
+ NS_TEMPLATE_TALK => 'ߞߙߊߞߏ ߢߊߝߐߞߣߍ',
+ NS_HELP => 'ߡߊ߬ߘߍ߬ߡߍ߲߬ߠߌ߲',
+ NS_HELP_TALK => 'ߡߊ߬ߘߍ߬ߡߍ߲߬ߠߌ߲ ߢߊߝߐߞߣߍ',
+ NS_CATEGORY => 'ߦߌߟߡߊ',
+ NS_CATEGORY_TALK => 'ߦߌߟߡߊ ߢߊߝߐߞߣߍ',
+];
+
$digitTransformTable = [
'0' => '߀', # U+07C0
'1' => '߁', # U+07C1
$installer->execute();
$installer->writeConfigurationFile( $this->getOption( 'confpath', $IP ) );
}
+ $installer->showMessage(
+ 'config-install-success',
+ $installer->getVar( 'wgServer' ),
+ $installer->getVar( 'wgScriptPath' )
+ );
}
private function setDbPassOption() {
$queries = [];
$sqltotal = 0.0;
+ /** @var profile_point|false $last */
$last = false;
foreach ( $res as $o ) {
$next = new profile_point( $o->pf_name, $o->pf_count, $o->pf_time, $o->pf_memory );
}
}
- $s = new profile_point( 'SQL Queries', 0, $sqltotal, 0, 0 );
+ $s = new profile_point( 'SQL Queries', 0, $sqltotal, 0 );
foreach ( $queries as $q ) {
$s->add_child( $q );
}
'group' => 'jquery.ui',
],
'jquery.ui.spinner' => [
- 'deprecated' => 'Please use "jquery.spinner" instead.',
'scripts' => 'resources/lib/jquery.ui/jquery.ui.spinner.js',
'dependencies' => [
'jquery.ui.core',
'scripts' => 'resources/src/mediawiki.action/mediawiki.action.view.rightClickEdit.js',
],
'mediawiki.action.edit.editWarning' => [
+ 'targets' => [ 'desktop', 'mobile' ],
'scripts' => 'resources/src/mediawiki.action/mediawiki.action.edit.editWarning.js',
'dependencies' => [
'jquery.textSelection',
focusout: this.onBlur.bind( this )
} );
this.calendar.$element.on( {
+ focusout: this.onBlur.bind( this ),
click: this.onCalendarClick.bind( this ),
keypress: this.onCalendarKeyPress.bind( this )
} );
use MediaWikiCoversValidator;
- protected static function getContext() {
+ protected static function makeContext() {
$request = new FauxRequest( [
- 'lang' => 'zh',
- 'modules' => 'test.context',
+ 'lang' => 'qqx',
+ 'modules' => 'test.default',
'only' => 'scripts',
'skin' => 'fallback',
'target' => 'test',
return new ResourceLoaderContext( new ResourceLoader(), $request );
}
- public function testGetInherited() {
- $derived = new DerivativeResourceLoaderContext( self::getContext() );
-
- // Request parameters
- $this->assertEquals( $derived->getDebug(), false );
- $this->assertEquals( $derived->getLanguage(), 'zh' );
- $this->assertEquals( $derived->getModules(), [ 'test.context' ] );
- $this->assertEquals( $derived->getOnly(), 'scripts' );
- $this->assertEquals( $derived->getSkin(), 'fallback' );
- $this->assertEquals( $derived->getUser(), null );
-
- // Misc
- $this->assertEquals( $derived->getDirection(), 'ltr' );
- $this->assertEquals( $derived->getHash(), 'zh|fallback|||scripts|||||' );
- }
-
- public function testModules() {
- $derived = new DerivativeResourceLoaderContext( self::getContext() );
+ public function testChangeModules() {
+ $derived = new DerivativeResourceLoaderContext( self::makeContext() );
+ $this->assertSame( $derived->getModules(), [ 'test.default' ], 'inherit from parent' );
$derived->setModules( [ 'test.override' ] );
- $this->assertEquals( $derived->getModules(), [ 'test.override' ] );
- }
-
- public function testLanguage() {
- $context = self::getContext();
- $derived = new DerivativeResourceLoaderContext( $context );
-
- $derived->setLanguage( 'nl' );
- $this->assertEquals( $derived->getLanguage(), 'nl' );
+ $this->assertSame( $derived->getModules(), [ 'test.override' ] );
}
- public function testDirection() {
- $derived = new DerivativeResourceLoaderContext( self::getContext() );
+ public function testChangeLanguageAndDirection() {
+ $derived = new DerivativeResourceLoaderContext( self::makeContext() );
+ $this->assertSame( $derived->getLanguage(), 'qqx', 'inherit from parent' );
$derived->setLanguage( 'nl' );
- $this->assertEquals( $derived->getDirection(), 'ltr' );
+ $this->assertSame( $derived->getLanguage(), 'nl' );
+ $this->assertSame( $derived->getDirection(), 'ltr' );
+ // Changing the language must clear cache of computed direction
$derived->setLanguage( 'he' );
- $this->assertEquals( $derived->getDirection(), 'rtl' );
+ $this->assertSame( $derived->getDirection(), 'rtl' );
+ $this->assertSame( $derived->getLanguage(), 'he' );
+ // Overriding the direction explicitly is allowed
$derived->setDirection( 'ltr' );
- $this->assertEquals( $derived->getDirection(), 'ltr' );
+ $this->assertSame( $derived->getDirection(), 'ltr' );
+ $this->assertSame( $derived->getLanguage(), 'he' );
}
- public function testSkin() {
- $derived = new DerivativeResourceLoaderContext( self::getContext() );
+ public function testChangeSkin() {
+ $derived = new DerivativeResourceLoaderContext( self::makeContext() );
+ $this->assertSame( $derived->getSkin(), 'fallback', 'inherit from parent' );
- $derived->setSkin( 'override' );
- $this->assertEquals( $derived->getSkin(), 'override' );
+ $derived->setSkin( 'myskin' );
+ $this->assertSame( $derived->getSkin(), 'myskin' );
}
- public function testUser() {
- $derived = new DerivativeResourceLoaderContext( self::getContext() );
+ public function testChangeUser() {
+ $derived = new DerivativeResourceLoaderContext( self::makeContext() );
+ $this->assertSame( $derived->getUser(), null, 'inherit from parent' );
- $derived->setUser( 'Example' );
- $this->assertEquals( $derived->getUser(), 'Example' );
+ $derived->setUser( 'MyUser' );
+ $this->assertSame( $derived->getUser(), 'MyUser' );
}
- public function testDebug() {
- $derived = new DerivativeResourceLoaderContext( self::getContext() );
+ public function testChangeDebug() {
+ $derived = new DerivativeResourceLoaderContext( self::makeContext() );
+ $this->assertSame( $derived->getDebug(), false, 'inherit from parent' );
$derived->setDebug( true );
- $this->assertEquals( $derived->getDebug(), true );
+ $this->assertSame( $derived->getDebug(), true );
}
- public function testOnly() {
- $derived = new DerivativeResourceLoaderContext( self::getContext() );
+ public function testChangeOnly() {
+ $derived = new DerivativeResourceLoaderContext( self::makeContext() );
+ $this->assertSame( $derived->getOnly(), 'scripts', 'inherit from parent' );
$derived->setOnly( 'styles' );
- $this->assertEquals( $derived->getOnly(), 'styles' );
+ $this->assertSame( $derived->getOnly(), 'styles' );
$derived->setOnly( null );
- $this->assertEquals( $derived->getOnly(), null );
+ $this->assertSame( $derived->getOnly(), null );
}
- public function testVersion() {
- $derived = new DerivativeResourceLoaderContext( self::getContext() );
+ public function testChangeVersion() {
+ $derived = new DerivativeResourceLoaderContext( self::makeContext() );
+ $this->assertSame( $derived->getVersion(), null );
$derived->setVersion( 'hw1' );
- $this->assertEquals( $derived->getVersion(), 'hw1' );
+ $this->assertSame( $derived->getVersion(), 'hw1' );
}
- public function testRaw() {
- $derived = new DerivativeResourceLoaderContext( self::getContext() );
+ public function testChangeRaw() {
+ $derived = new DerivativeResourceLoaderContext( self::makeContext() );
+ $this->assertSame( $derived->getRaw(), false, 'inherit from parent' );
$derived->setRaw( true );
- $this->assertEquals( $derived->getRaw(), true );
+ $this->assertSame( $derived->getRaw(), true );
}
- public function testGetHash() {
- $derived = new DerivativeResourceLoaderContext( self::getContext() );
-
- $this->assertEquals( $derived->getHash(), 'zh|fallback|||scripts|||||' );
+ public function testChangeHash() {
+ $derived = new DerivativeResourceLoaderContext( self::makeContext() );
+ $this->assertSame( $derived->getHash(), 'qqx|fallback|||scripts|||||', 'inherit' );
$derived->setLanguage( 'nl' );
$derived->setUser( 'Example' );
// Assert that subclass is able to clear parent class "hash" member
- $this->assertEquals( $derived->getHash(), 'nl|fallback||Example|scripts|||||' );
+ $this->assertSame( $derived->getHash(), 'nl|fallback||Example|scripts|||||' );
}
- public function testContentOverrides() {
- $derived = new DerivativeResourceLoaderContext( self::getContext() );
-
- $this->assertNull( $derived->getContentOverrideCallback() );
+ public function testChangeContentOverrides() {
+ $derived = new DerivativeResourceLoaderContext( self::makeContext() );
+ $this->assertNull( $derived->getContentOverrideCallback(), 'default' );
$override = function ( Title $t ) {
return null;
};
$derived->setContentOverrideCallback( $override );
- $this->assertSame( $override, $derived->getContentOverrideCallback() );
+ $this->assertSame( $override, $derived->getContentOverrideCallback(), 'changed' );
$derived2 = new DerivativeResourceLoaderContext( $derived );
- $this->assertSame( $override, $derived2->getContentOverrideCallback() );
+ $this->assertSame(
+ $override,
+ $derived2->getContentOverrideCallback(),
+ 'change via a second derivative layer'
+ );
}
- public function testAccessors() {
- $context = self::getContext();
+ public function testImmutableAccessors() {
+ $context = self::makeContext();
$derived = new DerivativeResourceLoaderContext( $context );
$this->assertSame( $derived->getRequest(), $context->getRequest() );
$this->assertSame( $derived->getResourceLoader(), $context->getResourceLoader() );