Merge "Show user ID on action=info"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 21 Oct 2016 00:36:21 +0000 (00:36 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 21 Oct 2016 00:36:21 +0000 (00:36 +0000)
includes/MovePage.php
includes/OutputPage.php
includes/Title.php
includes/exception/PermissionsError.php
includes/installer/DatabaseUpdater.php
includes/page/WikiPage.php
includes/resourceloader/ResourceLoaderWikiModule.php
includes/specialpage/FormSpecialPage.php
tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php

index 8ee562a..a8f6f8e 100644 (file)
@@ -539,8 +539,8 @@ class MovePage {
                        __METHOD__
                );
 
-               // clean up the old title before reset article id - bug 45348
                if ( !$redirectContent ) {
+                       // Clean up the old title *before* reset article id - bug 45348
                        WikiPage::onArticleDelete( $this->oldTitle );
                }
 
index 50f9eb9..dea5292 100644 (file)
@@ -2392,10 +2392,14 @@ class OutputPage extends ContextSource {
        /**
         * Output a standard permission error page
         *
-        * @param array $errors Error message keys
+        * @param array $errors Error message keys or [key, param...] arrays
         * @param string $action Action that was denied or null if unknown
         */
        public function showPermissionsErrorPage( array $errors, $action = null ) {
+               foreach ( $errors as $key => $error ) {
+                       $errors[$key] = (array)$error;
+               }
+
                // For some action (read, edit, create and upload), display a "login to do this action"
                // error if all of the following conditions are met:
                // 1. the user is not logged in
@@ -2688,15 +2692,13 @@ class OutputPage extends ContextSource {
                        $exemptStates = [];
                        $moduleStyles = $this->getModuleStyles( /*filter*/ true );
 
-                       // Batch preload getTitleInfo for isKnownEmpty() calls below
-                       $exemptModules = array_filter( $moduleStyles,
-                               function ( $name ) use ( $rl, &$exemptGroups ) {
-                                       $module = $rl->getModule( $name );
-                                       return $module && isset( $exemptGroups[ $module->getGroup() ] );
-                               }
-                       );
-                       ResourceLoaderWikiModule::preloadTitleInfo(
-                               $context, wfGetDB( DB_REPLICA ), $exemptModules );
+                       // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
+                       // Separate user-specific batch for improved cache-hit ratio.
+                       $userBatch = [ 'user.styles', 'user' ];
+                       $siteBatch = array_diff( $moduleStyles, $userBatch );
+                       $dbr = wfGetDB( DB_REPLICA );
+                       ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $siteBatch );
+                       ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $userBatch );
 
                        // Filter out modules handled by buildExemptModules()
                        $moduleStyles = array_filter( $moduleStyles,
index 5e1e8c6..213572b 100644 (file)
@@ -4391,16 +4391,19 @@ class Title implements LinkTarget {
        public function invalidateCache( $purgeTime = null ) {
                if ( wfReadOnly() ) {
                        return false;
-               }
-
-               if ( $this->mArticleID === 0 ) {
+               } elseif ( $this->mArticleID === 0 ) {
                        return true; // avoid gap locking if we know it's not there
                }
 
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->onTransactionPreCommitOrIdle( function () {
+                       ResourceLoaderWikiModule::invalidateModuleCache( $this, null, null, wfWikiID() );
+               } );
+
                $conds = $this->pageCond();
                DeferredUpdates::addUpdate(
                        new AutoCommitUpdate(
-                               wfGetDB( DB_MASTER ),
+                               $dbw,
                                __METHOD__,
                                function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
                                        $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
index bd0b120..e31374c 100644 (file)
@@ -29,12 +29,19 @@ class PermissionsError extends ErrorPageError {
        public $permission, $errors;
 
        /**
-        * @param string $permission A permission name.
-        * @param string[] $errors Error message keys
+        * @param string|null $permission A permission name or null if unknown
+        * @param array $errors Error message keys or [key, param...] arrays; must not be empty if
+        *   $permission is null
+        * @throws \InvalidArgumentException
         */
        public function __construct( $permission, $errors = [] ) {
                global $wgLang;
 
+               if ( $permission === null && !$errors ) {
+                       throw new \InvalidArgumentException( __METHOD__ .
+                               ': $permission and $errors cannot both be empty' );
+               }
+
                $this->permission = $permission;
 
                if ( !count( $errors ) ) {
index 6f066ce..6a702e9 100644 (file)
@@ -222,12 +222,11 @@ abstract class DatabaseUpdater {
         *
         * @since 1.17
         *
-        * @param array $update The update to run. Format is the following:
-        *                first item is the callback function, it also can be a
-        *                simple string with the name of a function in this class,
-        *                following elements are parameters to the function.
-        *                Note that callback functions will receive this object as
-        *                first parameter.
+        * @param array $update The update to run. Format is [ $callback, $params... ]
+        *   $callback is the method to call; either a DatabaseUpdater method name or a callable.
+        *   Must be serializable (ie. no anonymous functions allowed). The rest of the parameters
+        *   (if any) will be passed to the callback. The first parameter passed to the callback
+        *   is always this object.
         */
        public function addExtensionUpdate( array $update ) {
                $this->extensionUpdates[] = $update;
index 3dc41fb..54bc6f6 100644 (file)
@@ -2399,6 +2399,10 @@ class WikiPage implements Page, IDBAccessObject {
                } elseif ( $options['changed'] ) { // bug 50785
                        self::onArticleEdit( $this->mTitle, $revision );
                }
+
+               ResourceLoaderWikiModule::invalidateModuleCache(
+                       $this->mTitle, $options['oldrevision'], $revision, wfWikiID()
+               );
        }
 
        /**
@@ -2912,6 +2916,7 @@ class WikiPage implements Page, IDBAccessObject {
                // unless they actually try to catch exceptions (which is rare).
 
                // we need to remember the old content so we can use it to generate all deletion updates.
+               $revision = $this->getRevision();
                try {
                        $content = $this->getContent( Revision::RAW );
                } catch ( Exception $ex ) {
@@ -3011,7 +3016,7 @@ class WikiPage implements Page, IDBAccessObject {
 
                $dbw->endAtomic( __METHOD__ );
 
-               $this->doDeleteUpdates( $id, $content );
+               $this->doDeleteUpdates( $id, $content, $revision );
 
                Hooks::run( 'ArticleDeleteComplete', [
                        &$wikiPageBeforeDelete,
@@ -3058,11 +3063,12 @@ class WikiPage implements Page, IDBAccessObject {
         * Do some database updates after deletion
         *
         * @param int $id The page_id value of the page being deleted
-        * @param Content $content Optional page content to be used when determining
+        * @param Content|null $content Optional page content to be used when determining
         *   the required updates. This may be needed because $this->getContent()
         *   may already return null when the page proper was deleted.
+        * @param Revision|null $revision The latest page revision
         */
-       public function doDeleteUpdates( $id, Content $content = null ) {
+       public function doDeleteUpdates( $id, Content $content = null, Revision $revision = null ) {
                try {
                        $countable = $this->isCountable();
                } catch ( Exception $ex ) {
@@ -3090,6 +3096,9 @@ class WikiPage implements Page, IDBAccessObject {
 
                // Clear caches
                WikiPage::onArticleDelete( $this->mTitle );
+               ResourceLoaderWikiModule::invalidateModuleCache(
+                       $this->mTitle, $revision, null, wfWikiID()
+               );
 
                // Reset this object and the Title object
                $this->loadFromRow( false, self::READ_LATEST );
index 7cbec70..3deeb84 100644 (file)
@@ -26,7 +26,8 @@
  * Abstraction for ResourceLoader modules which pull from wiki pages
  *
  * This can only be used for wiki pages in the MediaWiki and User namespaces,
- * because of its dependence on the functionality of Title::isCssJsSubpage.
+ * because of its dependence on the functionality of Title::isCssJsSubpage
+ * and Title::isCssOrJsPage().
  *
  * This module supports being used as a placeholder for a module on a remote wiki.
  * To do so, getDB() must be overloaded to return a foreign database object that
@@ -143,7 +144,7 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
        }
 
        /**
-        * @param string $title
+        * @param string $titleText
         * @return null|string
         */
        protected function getContent( $titleText ) {
@@ -336,7 +337,7 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
         * @since 1.28
         * @param ResourceLoaderContext $context
         * @param IDatabase $db
-        * @param string[] $modules
+        * @param string[] $moduleNames
         */
        public static function preloadTitleInfo(
                ResourceLoaderContext $context, IDatabase $db, array $moduleNames
@@ -345,6 +346,7 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
                // getDB() can be overridden to point to a foreign database.
                // For now, only preload local. In the future, we could preload by wikiID.
                $allPages = [];
+               /** @var ResourceLoaderWikiModule[] $wikiModules */
                $wikiModules = [];
                foreach ( $moduleNames as $name ) {
                        $module = $rl->getModule( $name );
@@ -357,9 +359,29 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
                                }
                        }
                }
-               $allInfo = static::fetchTitleInfo( $db, array_keys( $allPages ), __METHOD__ );
-               foreach ( $wikiModules as $module ) {
-                       $pages = $module->getPages( $context );
+
+               $pageNames = array_keys( $allPages );
+               sort( $pageNames );
+               $hash = sha1( implode( '|', $pageNames ) );
+
+               // Avoid Zend bug where "static::" does not apply LSB in the closure
+               $func = [ static::class, 'fetchTitleInfo' ];
+               $fname = __METHOD__;
+
+               $cache = ObjectCache::getMainWANInstance();
+               $allInfo = $cache->getWithSetCallback(
+                       $cache->makeGlobalKey( 'resourceloader', 'titleinfo', $db->getWikiID(), $hash ),
+                       $cache::TTL_HOUR,
+                       function ( $curVal, &$ttl, array &$setOpts ) use ( $func, $pageNames, $db, $fname ) {
+                               $setOpts += Database::getCacheSetOptions( $db );
+
+                               return call_user_func( $func, $db, $pageNames, $fname );
+                       },
+                       [ 'checkKeys' => [ $cache->makeGlobalKey( 'resourceloader', 'titleinfo', $db->getWikiID() ) ] ]
+               );
+
+               foreach ( $wikiModules as $wikiModule ) {
+                       $pages = $wikiModule->getPages( $context );
                        // Before we intersect, map the names to canonical form (T145673).
                        $intersect = [];
                        foreach ( $pages as $page => $unused ) {
@@ -375,13 +397,41 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
                                }
                        }
                        $info = array_intersect_key( $allInfo, $intersect );
-
                        $pageNames = array_keys( $pages );
                        sort( $pageNames );
                        $key = implode( '|', $pageNames );
-                       $module->setTitleInfo( $key, $info );
+                       $wikiModule->setTitleInfo( $key, $info );
+               }
+       }
+
+       /**
+        * Clear the preloadTitleInfo() cache for all wiki modules on this wiki on
+        * page change if it was a JS or CSS page
+        *
+        * @param Title $title
+        * @param Revision|null $old Prior page revision
+        * @param Revision|null $new New page revision
+        * @param string $wikiId
+        * @since 1.28
+        */
+       public static function invalidateModuleCache(
+               Title $title, Revision $old = null, Revision $new = null, $wikiId
+       ) {
+               static $formats = [ CONTENT_FORMAT_CSS, CONTENT_FORMAT_JAVASCRIPT ];
+
+               if ( $old && in_array( $old->getContentFormat(), $formats ) ) {
+                       $purge = true;
+               } elseif ( $new && in_array( $new->getContentFormat(), $formats ) ) {
+                       $purge = true;
+               } else {
+                       $purge = ( $title->isCssOrJsPage() || $title->isCssJsSubpage() );
+               }
+
+               if ( $purge ) {
+                       $cache = ObjectCache::getMainWANInstance();
+                       $key = $cache->makeGlobalKey( 'resourceloader', 'titleinfo', $wikiId );
+                       $cache->touchCheckKey( $key );
                }
-               return $allInfo;
        }
 
        /**
index c28c456..66c7d47 100644 (file)
@@ -107,14 +107,15 @@ abstract class FormSpecialPage extends SpecialPage {
                        $form->addHeaderText( $headerMsg->parseAsBlock() );
                }
 
-               // Retain query parameters (uselang etc)
-               $params = array_diff_key(
-                       $this->getRequest()->getQueryValues(), [ 'title' => null ] );
-               $form->addHiddenField( 'redirectparams', wfArrayToCgi( $params ) );
-
                $form->addPreText( $this->preText() );
                $form->addPostText( $this->postText() );
                $this->alterForm( $form );
+               if ( $form->getMethod() == 'post' ) {
+                       // Retain query parameters (uselang etc) on POST requests
+                       $params = array_diff_key(
+                               $this->getRequest()->getQueryValues(), [ 'title' => null ] );
+                       $form->addHiddenField( 'redirectparams', wfArrayToCgi( $params ) );
+               }
 
                // Give hooks a chance to alter the form, adding extra fields or text etc
                Hooks::run( 'SpecialPageBeforeFormDisplay', [ $this->getName(), &$form ] );
index b12d235..a332528 100644 (file)
@@ -199,6 +199,12 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase {
                $rl->register( 'testmodule', $module );
                $context = new ResourceLoaderContext( $rl, new FauxRequest() );
 
+               TestResourceLoaderWikiModule::invalidateModuleCache(
+                       Title::newFromText( 'MediaWiki:Common.css' ),
+                       null,
+                       null,
+                       wfWikiID()
+               );
                TestResourceLoaderWikiModule::preloadTitleInfo(
                        $context,
                        wfGetDB( DB_REPLICA ),