=== Configuration changes in 1.25 ===
* $wgPageShowWatchingUsers was removed.
* $wgLocalVirtualHosts has been added to replace $wgConf->localVHosts.
+* $wgAntiLockFlags was removed.
=== New features in 1.25 ===
* (bug 58139) ResourceLoaderFileModule now supports language fallback
*/
$wgAllowSchemaUpdates = true;
-/**
- * Anti-lock flags - bitfield
- * - ALF_NO_LINK_LOCK:
- * Don't use locking reads when updating the link table. This is
- * necessary for wikis with a high edit rate for performance
- * reasons, but may cause link table inconsistency
- */
-$wgAntiLockFlags = 0;
-
/**
* Maximum article size in kilobytes
*/
// Check we added the last chunk:
if ( $this->mParams['offset'] + $chunkSize == $this->mParams['filesize'] ) {
if ( $this->mParams['async'] ) {
- $progress = UploadBase::getSessionStatus( $filekey );
+ $progress = UploadBase::getSessionStatus( $this->getUser(), $filekey );
if ( $progress && $progress['result'] === 'Poll' ) {
$this->dieUsage( "Chunk assembly already in progress.", 'stashfailed' );
}
UploadBase::setSessionStatus(
+ $this->getUser(),
$filekey,
array( 'result' => 'Poll',
'stage' => 'queued', 'status' => Status::newGood() )
// Status report for "upload to stash"/"upload from stash"
if ( $this->mParams['filekey'] && $this->mParams['checkstatus'] ) {
- $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
+ $progress = UploadBase::getSessionStatus( $this->getUser(), $this->mParams['filekey'] );
if ( !$progress ) {
$this->dieUsage( 'No result in status data', 'missingresult' );
} elseif ( !$progress['status']->isGood() ) {
// No errors, no warnings: do the upload
if ( $this->mParams['async'] ) {
- $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
+ $progress = UploadBase::getSessionStatus( $this->getUser(), $this->mParams['filekey'] );
if ( $progress && $progress['result'] === 'Poll' ) {
$this->dieUsage( "Upload from stash already in progress.", 'publishfailed' );
}
UploadBase::setSessionStatus(
+ $this->getUser(),
$this->mParams['filekey'],
array( 'result' => 'Poll', 'stage' => 'queued', 'status' => Status::newGood() )
);
* @return int
*/
public function addLinkObj( $nt ) {
- global $wgAntiLockFlags, $wgContentHandlerUseDB;
+ global $wgContentHandlerUseDB;
wfProfileIn( __METHOD__ );
# Some fields heavily used for linking...
if ( $this->mForUpdate ) {
$db = wfGetDB( DB_MASTER );
- if ( !( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) ) {
- $options = array( 'FOR UPDATE' );
- } else {
- $options = array();
- }
} else {
$db = wfGetDB( DB_SLAVE );
- $options = array();
}
$f = array( 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
$s = $db->selectRow( 'page', $f,
array( 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ),
- __METHOD__, $options );
+ __METHOD__ );
# Set fields...
if ( $s !== false ) {
$this->addGoodLinkObjFromRow( $nt, $s );
protected $mDb;
/** @var array SELECT options to be used (array) */
- protected $mOptions;
+ protected $mOptions = array();
/** @var bool Whether a transaction is open on this object (internal use only!) */
private $mHasTransaction;
* transaction is already in progress, see beginTransaction() for details.
*/
public function __construct( $withTransaction = true ) {
- global $wgAntiLockFlags;
-
parent::__construct();
- if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) {
- $this->mOptions = array();
- } else {
- $this->mOptions = array( 'FOR UPDATE' );
- }
-
// @todo Get connection only when it's needed? Make sure that doesn't
// break anything, especially transactions!
$this->mDb = wfGetDB( DB_MASTER );
public function run() {
$scope = RequestContext::importScopedSession( $this->params['session'] );
$context = RequestContext::getMain();
+ $user = $context->getUser();
try {
- $user = $context->getUser();
if ( !$user->isLoggedIn() ) {
$this->setLastError( "Could not load the author user from session." );
return false;
}
- if ( count( $_SESSION ) === 0 ) {
- // Empty session probably indicates that we didn't associate
- // with the session correctly. Note that being able to load
- // the user does not necessarily mean the session was loaded.
- // Most likely cause by suhosin.session.encrypt = On.
- $this->setLastError( "Error associating with user session. " .
- "Try setting suhosin.session.encrypt = Off" );
-
- return false;
- }
-
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array( 'result' => 'Poll', 'stage' => 'assembling', 'status' => Status::newGood() )
);
$status = $upload->concatenateChunks();
if ( !$status->isGood() ) {
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array( 'result' => 'Failure', 'stage' => 'assembling', 'status' => $status )
);
// Cache the info so the user doesn't have to wait forever to get the final info
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array(
'result' => 'Success',
);
} catch ( MWException $e ) {
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array(
'result' => 'Failure',
public function run() {
$scope = RequestContext::importScopedSession( $this->params['session'] );
$context = RequestContext::getMain();
+ $user = $context->getUser();
try {
- $user = $context->getUser();
if ( !$user->isLoggedIn() ) {
$this->setLastError( "Could not load the author user from session." );
return false;
}
- if ( count( $_SESSION ) === 0 ) {
- // Empty session probably indicates that we didn't associate
- // with the session correctly. Note that being able to load
- // the user does not necessarily mean the session was loaded.
- // Most likely cause by suhosin.session.encrypt = On.
- $this->setLastError( "Error associating with user session. " .
- "Try setting suhosin.session.encrypt = Off" );
-
- return false;
- }
-
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array( 'result' => 'Poll', 'stage' => 'publish', 'status' => Status::newGood() )
);
$status = Status::newFatal( 'verification-error' );
$status->value = array( 'verification' => $verification );
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
);
);
if ( !$status->isGood() ) {
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
);
// Cache the info so the user doesn't have to wait forever to get the final info
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array(
'result' => 'Success',
);
} catch ( MWException $e ) {
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array(
'result' => 'Failure',
'contexttitle',
60,
$title,
- array( 'autofocus' => true )
+ array( 'autofocus' => true, 'class' => 'mw-ui-input-inline' )
) . '</p>';
$form .= '<p>' . Xml::label(
$this->msg( 'expand_templates_input' )->text(),
const WINDOWS_NONASCII_FILENAME = 13;
const FILENAME_TOO_LONG = 14;
- const SESSION_STATUS_KEY = 'wsUploadStatusData';
-
/**
* @param int $error
* @return string
$sizes = $wgUploadThumbnailRenderMap;
rsort( $sizes );
+ $file = $this->getLocalFile();
+
foreach ( $sizes as $size ) {
- $jobs[] = new ThumbnailRenderJob( $this->getLocalFile()->getTitle(), array(
- 'transformParams' => array( 'width' => $size ),
- ) );
+ if ( $file->isVectorized()
+ || $file->getWidth() > $size ) {
+ $jobs[] = new ThumbnailRenderJob( $file->getTitle(), array(
+ 'transformParams' => array( 'width' => $size ),
+ ) );
+ }
}
- JobQueueGroup::singleton()->push( $jobs );
+ if ( $jobs ) {
+ JobQueueGroup::singleton()->push( $jobs );
+ }
}
/**
}
/**
- * Get the current status of a chunked upload (used for polling).
- * The status will be read from the *current* user session.
+ * Get the current status of a chunked upload (used for polling)
+ *
+ * The value will be read from cache.
+ *
+ * @param User $user
* @param string $statusKey
* @return Status[]|bool
*/
- public static function getSessionStatus( $statusKey ) {
- return isset( $_SESSION[self::SESSION_STATUS_KEY][$statusKey] )
- ? $_SESSION[self::SESSION_STATUS_KEY][$statusKey]
- : false;
+ public static function getSessionStatus( User $user, $statusKey ) {
+ $key = wfMemcKey( 'uploadstatus', $user->getId() ?: md5( $user->getName() ), $statusKey );
+
+ return wfGetCache( CACHE_ANYTHING )->get( $key );
}
/**
- * Set the current status of a chunked upload (used for polling).
- * The status will be stored in the *current* user session.
+ * Set the current status of a chunked upload (used for polling)
+ *
+ * The value will be set in cache for 1 day
+ *
+ * @param User $user
* @param string $statusKey
* @param array|bool $value
* @return void
*/
- public static function setSessionStatus( $statusKey, $value ) {
+ public static function setSessionStatus( User $user, $statusKey, $value ) {
+ $key = wfMemcKey( 'uploadstatus', $user->getId() ?: md5( $user->getName() ), $statusKey );
+
+ $cache = wfGetCache( CACHE_ANYTHING );
if ( $value === false ) {
- unset( $_SESSION[self::SESSION_STATUS_KEY][$statusKey] );
+ $cache->delete( $key );
} else {
- $_SESSION[self::SESSION_STATUS_KEY][$statusKey] = $value;
+ $cache->set( $key, $value, 86400 );
}
}
}
parent::__construct();
$this->mDescription = 'Find registered files with no corresponding file.';
- $this->addOption( 'start', 'Starting file name', false, true );
+ $this->addOption( 'start', 'Start after this file name', false, true );
$this->addOption( 'mtimeafter', 'Only include files changed since this time', false, true );
$this->addOption( 'mtimebefore', 'Only includes files changed before this time', false, true );
$this->setBatchSize( 300 );
$mtime1 = $dbr->timestampOrNull( $this->getOption( 'mtimeafter', null ) );
$mtime2 = $dbr->timestampOrNull( $this->getOption( 'mtimebefore', null ) );
- $joinTables = array( 'image' );
- $joinConds = array( 'image' => array( 'INNER JOIN', 'img_name = page_title' ) );
+ $joinTables = array();
+ $joinConds = array();
if ( $mtime1 || $mtime2 ) {
+ $joinTables[] = 'page';
+ $joinConds['page'] = array( 'INNER JOIN',
+ array( 'page_title = img_name', 'page_namespace' => NS_FILE ) );
$joinTables[] = 'logging';
$on = array( 'log_page = page_id', 'log_type' => array( 'upload', 'move', 'delete' ) );
if ( $mtime1 ) {
do {
$res = $dbr->select(
- array_merge( array( 'page' ), $joinTables ),
+ array_merge( array( 'image' ), $joinTables ),
array( 'name' => 'img_name' ),
- array( 'page_namespace' => NS_FILE,
- "page_title >= " . $dbr->addQuotes( $lastName ) ),
+ array( "img_name > " . $dbr->addQuotes( $lastName ) ),
__METHOD__,
// DISTINCT causes a pointless filesort
array( 'ORDER BY' => 'name', 'GROUP BY' => 'name',
}
if ( tooltip ) {
- $link.attr( 'title', tooltip ).updateTooltipAccessKeys();
+ $link.attr( 'title', tooltip );
}
if ( nextnode ) {
+ // Case: nextnode is a DOM element (was the only option before MW 1.17, in wikibits.js)
+ // Case: nextnode is a CSS selector for jQuery
if ( nextnode.nodeType || typeof nextnode === 'string' ) {
- // nextnode is a DOM element (was the only option before MW 1.17, in wikibits.js)
- // or nextnode is a CSS selector for jQuery
nextnode = $ul.find( nextnode );
- } else if ( !nextnode.jquery || ( nextnode.length && nextnode[0].parentNode !== $ul[0] ) ) {
- // Fallback
- $ul.append( $item );
- return $item[0];
+ } else if ( !nextnode.jquery ) {
+ // Error: Invalid nextnode
+ nextnode = undefined;
}
- if ( nextnode.length === 1 ) {
- // nextnode is a jQuery object that represents exactly one element
- nextnode.before( $item );
- return $item[0];
+ if ( nextnode && ( nextnode.length !== 1 || nextnode[0].parentNode !== $ul[0] ) ) {
+ // Error: nextnode must resolve to a single node
+ // Error: nextnode must have the associated <ul> as its parent
+ nextnode = undefined;
}
}
- // Fallback (this is the default behavior)
- $ul.append( $item );
- return $item[0];
+ // Case: nextnode is a jQuery-wrapped DOM element
+ if ( nextnode ) {
+ nextnode.before( $item );
+ } else {
+ // Fallback (this is the default behavior)
+ $ul.append( $item );
+ }
+
+ // Update tooltip for the access key after inserting into DOM
+ // to get a localized access key label (bug 67946).
+ $link.updateTooltipAccessKeys();
+ return $item[0];
},
/**
);
assert.equal( $tbMW.closest( '.portlet' ).attr( 'id' ), 'p-test-tb', 'Link was inserted within correct portlet' );
- assert.strictEqual( $tbMW.next()[0], tbRL, 'Link is in the correct position (by passing nextnode)' );
+ assert.strictEqual( $tbMW.next()[0], tbRL, 'Link is in the correct position (nextnode as Node object)' );
cuQuux = mw.util.addPortletLink( 'p-test-custom', '#', 'Quux', null, 'Example [shift-x]', 'q' );
$cuQuux = $( cuQuux );
tbRLDM = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM',
'Default modules', 't-rldm', 'List of all default modules ', 'd', '#t-rl' );
- assert.equal( $( tbRLDM ).next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing CSS selector)' );
+ assert.strictEqual( $( tbRLDM ).next()[0], tbRL, 'Link is in the correct position (CSS selector as nextnode)' );
caFoo = mw.util.addPortletLink( 'p-test-views', '#', 'Foo' );
assert.strictEqual( $( caFoo ).find( 'span' ).length, 1, 'A <span> element should be added for porlets with vectorTabs class.' );
addedAfter = mw.util.addPortletLink( 'p-test-tb', '#', 'After foo', 'post-foo', 'After foo', null, $( tbRL ) );
- assert.strictEqual( $( addedAfter ).next()[0], tbRL, 'Link is in the correct position (by passing a jQuery object as nextnode)' );
+ assert.strictEqual( $( addedAfter ).next()[0], tbRL, 'Link is in the correct position (jQuery object as nextnode)' );
// test case - nonexistent id as next node
tbRLDMnonexistentid = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM',
'Default modules', 't-rldm-nonexistent', 'List of all default modules ', 'd', '#t-rl-nonexistent' );
- assert.equal( tbRLDMnonexistentid, $( '#p-test-tb li:last' )[0], 'Nonexistent id as nextnode adds the portlet at end' );
+ assert.equal( tbRLDMnonexistentid, $( '#p-test-tb li:last' )[0], 'Fallback to adding at the end (nextnode non-matching CSS selector)' );
// test case - empty jquery object as next node
tbRLDMemptyjquery = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM',
'Default modules', 't-rldm-empty-jquery', 'List of all default modules ', 'd', $( '#t-rl-nonexistent' ) );
- assert.equal( tbRLDMemptyjquery, $( '#p-test-tb li:last' )[0], 'Empty jquery as nextnode adds the portlet at end' );
+ assert.equal( tbRLDMemptyjquery, $( '#p-test-tb li:last' )[0], 'Fallback to adding at the end (nextnode as empty jQuery object)' );
} );
QUnit.test( 'jsMessage', 1, function ( assert ) {