project.index
# MediaWiki install & usage
-cache/*.cdb
+cache
images/[0-9a-f]
images/archive
images/deleted
* SVG files will now show the actual width in the SVG's specified units
in the metadata box.
* Added ResourceLoader module "jquery.jStorage".
+* (bug 39273) Added AJAX support for "Show changes" (diff) in LivePreview.
=== Bug fixes in 1.20 ===
* (bug 30245) Use the correct way to construct a log page title.
* (bug 27111) Cascading foreign file repos now fetch shared descriptions properly.
* EXIF below sea level GPS altitude data is now shown correctly.
* (bug 39284) jquery.tablesorter should not consider "."" or "?"" to be a currency.
+* (bug 39273) "Show changes" should not be incorrectly displayed in the Live Preview state.
=== API changes in 1.20 ===
* (bug 34316) Add ability to retrieve maximum upload size from MediaWiki API.
return false;
}
# Get the article text
- $rev = Revision::newFromTitle( $nt );
+ $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
if( !is_object( $rev ) ){
return false;
}
if ( in_array( 'include_old', $options ) ) {
$old_cmp = '>=';
}
- if ( in_array( 'include_new', $options ) ) {\r
- $new_cmp = '<=';\r
- }\r
- if ( in_array( 'include_both', $options ) ) {\r
- $old_cmp = '>=';\r
+ if ( in_array( 'include_new', $options ) ) {
+ $new_cmp = '<=';
+ }
+ if ( in_array( 'include_both', $options ) ) {
+ $old_cmp = '>=';
$new_cmp = '<=';
}
// No DB query needed if $old and $new are the same or successive revisions:
return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
}
return ( $old->getRawUserText() === $new->getRawUserText() ) ? 1 : 2;
- }\r
+ }
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
array(
if ( $titleObj->isRedirect() ) {
$oldTitle = $titleObj;
- $titles = Title::newFromRedirectArray( Revision::newFromTitle( $oldTitle )->getText( Revision::FOR_THIS_USER ) );
+ $titles = Title::newFromRedirectArray(
+ Revision::newFromTitle(
+ $oldTitle, false, Revision::READ_LATEST
+ )->getText( Revision::FOR_THIS_USER )
+ );
// array_shift( $titles );
$redirValues = array();
if ( !is_null( $params['summary'] ) ) {
$requestArray['wpSummary'] = $params['summary'];
}
-
+
if ( !is_null( $params['sectiontitle'] ) ) {
$requestArray['wpSectionTitle'] = $params['sectiontitle'];
}
}
# Try loading it from the database
- $revision = Revision::newFromTitle( Title::makeTitle( NS_MEDIAWIKI, $title ) );
+ $revision = Revision::newFromTitle(
+ Title::makeTitle( NS_MEDIAWIKI, $title ), false, Revision::READ_LATEST
+ );
if ( $revision ) {
$message = $revision->getText();
if ($message === false) {
// Create a new temporary file with the same extension...
$ext = FileBackend::extensionFromPath( $params['src'] );
- $tmpFile = TempFSFile::factory( wfBaseName( $source ) . '_', $ext );
+ $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
if ( !$tmpFile ) {
return null;
}
// Actually attempt the operation batch on the master backend...
$masterStatus = $mbe->doOperations( $realOps, $opts );
$status->merge( $masterStatus );
- // Propagate the operations to the clone backends...
- foreach ( $this->backends as $index => $backend ) {
- if ( $index !== $this->masterIndex ) { // not done already
- $realOps = $this->substOpBatchPaths( $ops, $backend );
- $status->merge( $backend->doOperations( $realOps, $opts ) );
+ // Propagate the operations to the clone backends if there were no fatal errors.
+ // If $ops only had one operation, this might avoid backend inconsistencies.
+ if ( !count( $masterStatus->getErrorsArray() ) ) {
+ foreach ( $this->backends as $index => $backend ) {
+ if ( $index !== $this->masterIndex ) { // not done already
+ $realOps = $this->substOpBatchPaths( $ops, $backend );
+ $status->merge( $backend->doOperations( $realOps, $opts ) );
+ }
}
}
// Make 'success', 'successCount', and 'failCount' fields reflect
return null;
}
- # Check the recursion guard to avoid loops when filling metadata
- if ( empty( $params['nostat'] ) && !$this->fileExists( $params ) ) {
- return null;
- }
-
+ // Blindly create a tmp file and stream to it, catching any exception if the file does
+ // not exist. Also, doing a stat here will cause infinite loops when filling metadata.
$tmpFile = null;
try {
$sContObj = $this->getContainer( $srcCont );
// Get source file extension
$ext = FileBackend::extensionFromPath( $srcRel );
// Create a new temporary file...
- $tmpFile = TempFSFile::factory( wfBaseName( $srcRel ) . '_', $ext );
+ $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
if ( $tmpFile ) {
$handle = fopen( $tmpFile->getPath(), 'wb' );
if ( $handle ) {
}
} catch ( NoSuchContainerException $e ) {
$tmpFile = null;
+ } catch ( NoSuchObjectException $e ) {
+ $tmpFile = null;
} catch ( CloudFilesException $e ) { // some other exception?
$tmpFile = null;
$this->handleException( $e, null, __METHOD__, $params );
*/
public static function factory( $prefix, $extension = '' ) {
wfProfileIn( __METHOD__ );
- $base = wfTempDir() . '/' . $prefix . dechex( mt_rand( 0, 99999999 ) );
+ $base = wfTempDir() . '/' . $prefix . wfRandomString( 12 );
$ext = ( $extension != '' ) ? ".{$extension}" : "";
for ( $attempt = 1; true; $attempt++ ) {
$path = "{$base}-{$attempt}{$ext}";
return false;
}
- $targetRev = Revision::newFromTitle( $this->title );
+ $targetRev = Revision::newFromTitle( $this->title, false, Revision::READ_LATEST );
if ( !$targetRev ) {
wfDebug( __METHOD__.": target redirect already deleted, ignoring\n" );
return true;
* @ingroup Cache
*/
class APCBagOStuff extends BagOStuff {
-
/**
* @param $key string
* @return mixed
$val = apc_fetch( $key );
if ( is_string( $val ) ) {
- $val = unserialize( $val );
+ if ( $this->isInteger( $val ) ) {
+ $val = intval( $val );
+ } else {
+ $val = unserialize( $val );
+ }
}
return $val;
* @return bool
*/
public function set( $key, $value, $exptime = 0 ) {
- apc_store( $key, serialize( $value ), $exptime );
+ if ( !$this->isInteger( $value ) ) {
+ $value = serialize( $value );
+ }
+
+ apc_store( $key, $value, $exptime );
return true;
}
return true;
}
+ public function incr( $key, $value = 1 ) {
+ return apc_inc( $key, $value );
+ }
+
+ public function decr( $key, $value = 1 ) {
+ return apc_dec( $key, $value );
+ }
+
/**
* @return Array
*/
return $keys;
}
}
-
}
/**
+ * Increase stored value of $key by $value while preserving its TTL
* @param $key String: Key to increase
* @param $value Integer: Value to add to $key (Default 1)
* @return null if lock is not possible else $key value increased by $value
- * @return bool success
+ * @return integer
*/
public function incr( $key, $value = 1 ) {
if ( !$this->lock( $key ) ) {
}
/**
+ * Decrease stored value of $key by $value while preserving its TTL
* @param $key String
* @param $value Integer
- * @return bool success
+ * @return integer
*/
public function decr( $key, $value = 1 ) {
return $this->incr( $key, - $value );
return $exptime;
}
}
+
+ /**
+ * Check if a value is an integer
+ *
+ * @param $value mixed
+ * @return bool
+ */
+ protected function isInteger( $value ) {
+ return ( is_int( $value ) || ctype_digit( $value ) );
+ }
}
$params['dir'] = wfTempDir();
}
- $this->mFile = $params['dir']."/mw-cache-" . wfWikiID();
- $this->mFile .= '.db';
+ $this->mFile = $params['dir'] . '/mw-cache-' . wfWikiID() . '.db';
wfDebug( __CLASS__ . ": using cache file {$this->mFile}\n" );
$this->mHandler = $wgDBAhandler;
}
*
* @return string
*/
- function encode( $value, $expiry ) {
+ protected function encode( $value, $expiry ) {
# Convert to absolute time
$expiry = $this->convertExpiry( $expiry );
* @param $blob string
* @return array list containing value first and expiry second
*/
- function decode( $blob ) {
+ protected function decode( $blob ) {
if ( !is_string( $blob ) ) {
return array( null, 0 );
} else {
/**
* @return resource
*/
- function getReader() {
+ protected function getReader() {
if ( file_exists( $this->mFile ) ) {
$handle = dba_open( $this->mFile, 'rl', $this->mHandler );
} else {
/**
* @return resource
*/
- function getWriter() {
+ protected function getWriter() {
$handle = dba_open( $this->mFile, 'cl', $this->mHandler );
if ( !$handle ) {
* @param $key string
* @return mixed|null|string
*/
- function get( $key ) {
+ public function get( $key ) {
wfProfileIn( __METHOD__ );
wfDebug( __METHOD__ . "($key)\n" );
* @param $exptime int
* @return bool
*/
- function set( $key, $value, $exptime = 0 ) {
+ public function set( $key, $value, $exptime = 0 ) {
wfProfileIn( __METHOD__ );
wfDebug( __METHOD__ . "($key)\n" );
* @param $time int
* @return bool
*/
- function delete( $key, $time = 0 ) {
+ public function delete( $key, $time = 0 ) {
wfProfileIn( __METHOD__ );
wfDebug( __METHOD__ . "($key)\n" );
* @param $exptime int
* @return bool
*/
- function add( $key, $value, $exptime = 0 ) {
+ public function add( $key, $value, $exptime = 0 ) {
wfProfileIn( __METHOD__ );
$blob = $this->encode( $value, $exptime );
if ( !$ret ) {
list( $value, $expiry ) = $this->decode( dba_fetch( $key, $handle ) );
- if ( $expiry < time() ) {
+ if ( $expiry && $expiry < time() ) {
# Yes expired, delete and try again
dba_delete( $key, $handle );
$ret = dba_insert( $key, $blob, $handle );
}
/**
- * @return Array
+ * @param $key string
+ * @param $step integer
+ * @return integer|bool
*/
+ public function incr( $key, $step = 1 ) {
+ wfProfileIn( __METHOD__ );
+
+ $handle = $this->getWriter();
+
+ if ( !$handle ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ list( $value, $expiry ) = $this->decode( dba_fetch( $key, $handle ) );
+ if ( !is_null( $value ) ) {
+ if ( $expiry && $expiry < time() ) {
+ # Key is expired, delete it
+ dba_delete( $key, $handle );
+ wfDebug( __METHOD__ . ": $key expired\n" );
+ $value = null;
+ } else {
+ $value += $step;
+ $blob = $this->encode( $value, $expiry );
+
+ $ret = dba_replace( $key, $blob, $handle );
+ $value = $ret ? $value : null;
+ }
+ }
+
+ dba_close( $handle );
+
+ wfProfileOut( __METHOD__ );
+
+ return is_null( $value ) ? false : (int)$value;
+ }
+
function keys() {
$reader = $this->getReader();
$k1 = dba_firstkey( $reader );
return $result;
}
}
-
$val = xcache_get( $key );
if ( is_string( $val ) ) {
- $val = unserialize( $val );
+ if ( $this->isInteger( $val ) ) {
+ $val = intval( $val );
+ } else {
+ $val = unserialize( $val );
+ }
}
return $val;
* @return bool
*/
public function set( $key, $value, $expire = 0 ) {
- xcache_set( $key, serialize( $value ), $expire );
+ if ( !$this->isInteger( $value ) ) {
+ $value = serialize( $value );
+ }
+
+ xcache_set( $key, $value, $expire );
return true;
}
xcache_unset( $key );
return true;
}
-}
+ public function incr( $key, $value = 1 ) {
+ return xcache_inc( $key, $value );
+ }
+
+ public function decr( $key, $value = 1 ) {
+ return xcache_dec( $key, $value );
+ }
+}
if ( !$title->isCssJsSubpage() && !$title->isCssOrJsPage() ) {
return null;
}
- $revision = Revision::newFromTitle( $title );
+ $revision = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
if ( !$revision ) {
return null;
}
// We're dealing with a subclass that doesn't have a DB
return array();
}
-
+
$hash = $context->getHash();
if ( isset( $this->titleMtimes[$hash] ) ) {
return $this->titleMtimes[$hash];
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
</pluralRules>
+ <pluralRules locales="dsb">
+ <pluralRule count="one">n mod 100 is 1</pluralRule>
+ <pluralRule count="two">n mod 100 is 2</pluralRule>
+ <pluralRule count="few">n mod 100 in 3..4</pluralRule>
+ </pluralRules>
+ <pluralRules locales="cu">
+ <pluralRule count="one">n mod 10 is 1</pluralRule>
+ <pluralRule count="two">n mod 10 is 2</pluralRule>
+ <pluralRule count="few">n mod 10 in 3..4</pluralRule>
+ </pluralRules>
+ <!-- Plural form transformations
+ Based on this discussion: http://translatewiki.net/wiki/Thread:Support/New_plural_rules_for_Scots_Gaelic_(gd)
+ $forms[0] - 1
+ $forms[1] - 2
+ $forms[2] - 11
+ $forms[3] - 12
+ $forms[4] - 3-10, 13-19
+ $forms[5] - 0, 20, rest -->
+ <pluralRules locales="gd">
+ <pluralRule count="one">n is 1</pluralRule>
+ <pluralRule count="two">n is 2</pluralRule>
+ <pluralRule count="elevan">n is 11</pluralRule>
+ <pluralRule count="twelve">n is 12</pluralRule>
+ <pluralRule count="few">n in 3..10 or n in 13..19</pluralRule>
+ </pluralRules>
</plurals>
- <pluralRules locales="dsb">
- <pluralRule count="one">n mod 100 is 1</pluralRule>
- <pluralRule count="two">n mod 100 is 2</pluralRule>
- <pluralRule count="few">n mod 100 in 3..4</pluralRule>
- </pluralRules>
- <pluralRules locales="cu">
- <pluralRule count="one">n mod 10 is 1</pluralRule>
- <pluralRule count="two">n mod 10 is 2</pluralRule>
- <pluralRule count="few">n mod 10 in 3..4</pluralRule>
- </pluralRules>
- <!-- Plural form transformations
- Based on this discussion: http://translatewiki.net/wiki/Thread:Support/New_plural_rules_for_Scots_Gaelic_(gd)
- $forms[0] - 1
- $forms[1] - 2
- $forms[2] - 11
- $forms[3] - 12
- $forms[4] - 3-10, 13-19
- $forms[5] - 0, 20, rest -->
- <pluralRules locales="gd">
- <pluralRule count="one">n is 1</pluralRule>
- <pluralRule count="two">n is 2</pluralRule>
- <pluralRule count="elevan">n is 11</pluralRule>
- <pluralRule count="twelve">n is 12</pluralRule>
- <pluralRule count="few">n in 3..10 or n in 13..19</pluralRule>
- </pluralRules>
</supplementalData>
$( mw ).trigger( 'LivePreviewPrepare' );
var postData = $('#editform').formToArray();
- postData.push( { 'name' : 'wpPreview', 'value' : '1' } );
+ postData.push( { 'name' : e.target.name, 'value' : '1' } );
// Hide active diff, used templates, old preview if shown
- var copyElements = ['#wikiPreview', '.templatesUsed', '.hiddencats',
+ var copyElements = ['#wikiPreview', '#wikiDiff', '.templatesUsed', '.hiddencats',
'#catlinks', '#p-lang', '.mw-summary-preview'];
var copySelector = copyElements.join(',');
$( '.editCheckboxes' ).before( $( '<div>' ).addClass( 'mw-summary-preview' ) );
}
- $( '#wpPreview' ).click( doLivePreview );
+ // construct space for diff if missing. also load diff styles.
+ if ( !document.getElementById( 'wikiDiff' ) && document.getElementById( 'wikiPreview' ) ) {
+ $( '#wikiPreview' ).after( $( '<div>' ).attr( 'id', 'wikiDiff' ) );
+ // diff styles are by default only loaded when needed
+ // if there was no diff container, we can expect the styles not to be there either
+ mw.loader.load( 'mediawiki.action.history.diff' );
+ }
+
+ $( '#wpPreview, #wpDiff' ).click( doLivePreview );
} );
}) ( jQuery );
$this->options = new ParserOptions;
$this->options->setTemplateCallback( array( __CLASS__, 'statelessFetchTemplate' ) );
$this->parser = new Parser;
+
+ MagicWord::clearCache();
}
// Bug 8689 - Long numeric lines kill the parser
private static $oldContLang;
private static $oldLanguageCode;
private static $oldNamespaces;
+ private static $oldHTML5;
public function setUp() {
- global $wgLang, $wgContLang, $wgLanguageCode;
-
+ global $wgLang, $wgContLang, $wgLanguageCode, $wgHTML5;
+
+ // Save globals
self::$oldLang = $wgLang;
self::$oldContLang = $wgContLang;
self::$oldNamespaces = $wgContLang->getNamespaces();
self::$oldLanguageCode = $wgLanguageCode;
-
+ self::$oldHTML5 = $wgHTML5;
+
$wgLanguageCode = 'en';
$wgContLang = $wgLang = Language::factory( $wgLanguageCode );
101 => 'Custom_talk',
) );
}
-
+
public function tearDown() {
- global $wgLang, $wgContLang, $wgLanguageCode;
+ global $wgLang, $wgContLang, $wgLanguageCode, $wgHTML5;
+ // Restore globals
$wgContLang->setNamespaces( self::$oldNamespaces );
$wgLang = self::$oldLang;
$wgContLang = self::$oldContLang;
$wgLanguageCode = self::$oldLanguageCode;
+ $wgHTML5 = self::$oldHTML5;
+ }
+
+ /**
+ * Wrapper to easily set $wgHTML5 = true.
+ * Original value will be restored after test completion.
+ * @todo Move to MediaWikiTestCase
+ */
+ public function enableHTML5() {
+ global $wgHTML5;
+ $wgHTML5 = true;
+ }
+ /**
+ * Wrapper to easily set $wgHTML5 = false
+ * Original value will be restored after test completion.
+ * @todo Move to MediaWikiTestCase
+ */
+ public function disableHTML5() {
+ global $wgHTML5;
+ $wgHTML5 = false;
}
public function testExpandAttributesSkipsNullAndFalse() {
public abstract function elementInstancesProvider();
/**
- * Provides instances of the concrete class being tested.
+ * Returns the name of the concrete class being tested.
*
* @since 1.20
*
- * @return array
+ * @return string
*/
- public abstract function instanceProvider();
+ public abstract function getInstanceClass();
/**
- * Returns the name of the concrete class being tested.
+ * Provides instances of the concrete class being tested.
*
* @since 1.20
*
- * @return string
+ * @return array
*/
- public abstract function getInstanceClass();
+ public function instanceProvider() {
+ $instances = array();
+
+ foreach ( $this->elementInstancesProvider() as $elementInstances ) {
+ $instances[] = $this->getNew( $elementInstances );
+ }
+
+ return $this->arrayWrap( $instances );
+ }
/**
* @since 1.20