Merge "mediawiki.toolbar: Properly deprecate #init"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 3 Oct 2014 18:05:43 +0000 (18:05 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 3 Oct 2014 18:05:44 +0000 (18:05 +0000)
12 files changed:
RELEASE-NOTES-1.25
includes/DefaultSettings.php
includes/api/ApiUpload.php
includes/cache/LinkCache.php
includes/deferred/SqlDataUpdate.php
includes/jobqueue/jobs/AssembleUploadChunksJob.php
includes/jobqueue/jobs/PublishStashedFileJob.php
includes/specials/SpecialExpandTemplates.php
includes/upload/UploadBase.php
maintenance/findMissingFiles.php
resources/src/mediawiki/mediawiki.util.js
tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js

index db4c124..95ff2d3 100644 (file)
@@ -11,6 +11,7 @@ production.
 === 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
index c6e91cf..f2453e8 100644 (file)
@@ -1997,15 +1997,6 @@ $wgAllowSlowParserFunctions = false;
  */
 $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
  */
index 657181b..2770bdc 100644 (file)
@@ -223,11 +223,12 @@ class ApiUpload extends ApiBase {
                // 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() )
@@ -327,7 +328,7 @@ class ApiUpload extends ApiBase {
 
                // 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() ) {
@@ -612,11 +613,12 @@ class ApiUpload extends ApiBase {
 
                // 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() )
                        );
index 6925df9..82fb12d 100644 (file)
@@ -216,7 +216,7 @@ class LinkCache {
         * @return int
         */
        public function addLinkObj( $nt ) {
-               global $wgAntiLockFlags, $wgContentHandlerUseDB;
+               global $wgContentHandlerUseDB;
 
                wfProfileIn( __METHOD__ );
 
@@ -242,14 +242,8 @@ class LinkCache {
                # 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' );
@@ -259,7 +253,7 @@ class LinkCache {
 
                $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 );
index 9c58503..7ec61ea 100644 (file)
@@ -35,7 +35,7 @@ abstract class SqlDataUpdate extends DataUpdate {
        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;
@@ -51,16 +51,8 @@ abstract class SqlDataUpdate extends DataUpdate {
         *   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 );
index 9e9bda6..cc28a01 100644 (file)
@@ -35,26 +35,16 @@ class AssembleUploadChunksJob extends Job {
        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() )
                        );
@@ -70,6 +60,7 @@ class AssembleUploadChunksJob extends Job {
                        $status = $upload->concatenateChunks();
                        if ( !$status->isGood() ) {
                                UploadBase::setSessionStatus(
+                                       $user,
                                        $this->params['filekey'],
                                        array( 'result' => 'Failure', 'stage' => 'assembling', 'status' => $status )
                                );
@@ -93,6 +84,7 @@ class AssembleUploadChunksJob extends Job {
 
                        // 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',
@@ -104,6 +96,7 @@ class AssembleUploadChunksJob extends Job {
                        );
                } catch ( MWException $e ) {
                        UploadBase::setSessionStatus(
+                               $user,
                                $this->params['filekey'],
                                array(
                                        'result' => 'Failure',
index 918a392..55215b3 100644 (file)
@@ -35,26 +35,16 @@ class PublishStashedFileJob extends Job {
        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() )
                        );
@@ -72,6 +62,7 @@ class PublishStashedFileJob extends Job {
                                $status = Status::newFatal( 'verification-error' );
                                $status->value = array( 'verification' => $verification );
                                UploadBase::setSessionStatus(
+                                       $user,
                                        $this->params['filekey'],
                                        array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
                                );
@@ -89,6 +80,7 @@ class PublishStashedFileJob extends Job {
                        );
                        if ( !$status->isGood() ) {
                                UploadBase::setSessionStatus(
+                                       $user,
                                        $this->params['filekey'],
                                        array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
                                );
@@ -106,6 +98,7 @@ class PublishStashedFileJob extends Job {
 
                        // 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',
@@ -117,6 +110,7 @@ class PublishStashedFileJob extends Job {
                        );
                } catch ( MWException $e ) {
                        UploadBase::setSessionStatus(
+                               $user,
                                $this->params['filekey'],
                                array(
                                        'result' => 'Failure',
index 269dff6..2d99d60 100644 (file)
@@ -151,7 +151,7 @@ class SpecialExpandTemplates extends SpecialPage {
                        '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(),
index eb54e58..079c7f8 100644 (file)
@@ -69,8 +69,6 @@ abstract class UploadBase {
        const WINDOWS_NONASCII_FILENAME = 13;
        const FILENAME_TOO_LONG = 14;
 
-       const SESSION_STATUS_KEY = 'wsUploadStatusData';
-
        /**
         * @param int $error
         * @return string
@@ -768,13 +766,20 @@ abstract class UploadBase {
                $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 );
+               }
        }
 
        /**
@@ -1979,29 +1984,38 @@ abstract class UploadBase {
        }
 
        /**
-        * 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 );
                }
        }
 }
index add7108..5818ee2 100644 (file)
@@ -26,7 +26,7 @@ class FindMissingFiles extends Maintenance {
                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 );
@@ -42,9 +42,12 @@ class FindMissingFiles extends Maintenance {
                $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 ) {
@@ -58,10 +61,9 @@ class FindMissingFiles extends Maintenance {
 
                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',
index 2662913..3a06a02 100644 (file)
                        }
 
                        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];
                },
 
                /**
index 4401ead..9b620de 100644 (file)
                );
 
                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 ) {