Merge "Revert "Use display name in category page subheadings if provided""
[lhc/web/wiklou.git] / includes / resourceloader / ResourceLoaderWikiModule.php
index 8033843..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 ) {
@@ -276,6 +277,10 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
                return count( $revisions ) === 0;
        }
 
+       private function setTitleInfo( $key, array $titleInfo ) {
+               $this->titleInfo[$key] = $titleInfo;
+       }
+
        /**
         * Get the information about the wiki pages for a given context.
         * @param ResourceLoaderContext $context
@@ -288,35 +293,145 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
                        return [];
                }
 
-               $pages = $this->getPages( $context );
-               $key = implode( '|', array_keys( $pages ) );
+               $pageNames = array_keys( $this->getPages( $context ) );
+               sort( $pageNames );
+               $key = implode( '|', $pageNames );
                if ( !isset( $this->titleInfo[$key] ) ) {
-                       $this->titleInfo[$key] = [];
-                       $batch = new LinkBatch;
-                       foreach ( $pages as $titleText => $options ) {
-                               $batch->addObj( Title::newFromText( $titleText ) );
+                       $this->titleInfo[$key] = static::fetchTitleInfo( $dbr, $pageNames, __METHOD__ );
+               }
+               return $this->titleInfo[$key];
+       }
+
+       protected static function fetchTitleInfo( IDatabase $db, array $pages, $fname = __METHOD__ ) {
+               $titleInfo = [];
+               $batch = new LinkBatch;
+               foreach ( $pages as $titleText ) {
+                       $title = Title::newFromText( $titleText );
+                       if ( $title ) {
+                               // Page name may be invalid if user-provided (e.g. gadgets)
+                               $batch->addObj( $title );
+                       }
+               }
+               if ( !$batch->isEmpty() ) {
+                       $res = $db->select( 'page',
+                               // Include page_touched to allow purging if cache is poisoned (T117587, T113916)
+                               [ 'page_namespace', 'page_title', 'page_touched', 'page_len', 'page_latest' ],
+                               $batch->constructSet( 'page', $db ),
+                               $fname
+                       );
+                       foreach ( $res as $row ) {
+                               // Avoid including ids or timestamps of revision/page tables so
+                               // that versions are not wasted
+                               $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+                               $titleInfo[$title->getPrefixedText()] = [
+                                       'page_len' => $row->page_len,
+                                       'page_latest' => $row->page_latest,
+                                       'page_touched' => $row->page_touched,
+                               ];
                        }
+               }
+               return $titleInfo;
+       }
 
-                       if ( !$batch->isEmpty() ) {
-                               $res = $dbr->select( 'page',
-                                       // Include page_touched to allow purging if cache is poisoned (T117587, T113916)
-                                       [ 'page_namespace', 'page_title', 'page_touched', 'page_len', 'page_latest' ],
-                                       $batch->constructSet( 'page', $dbr ),
-                                       __METHOD__
-                               );
-                               foreach ( $res as $row ) {
-                                       // Avoid including ids or timestamps of revision/page tables so
-                                       // that versions are not wasted
-                                       $title = Title::makeTitle( $row->page_namespace, $row->page_title );
-                                       $this->titleInfo[$key][$title->getPrefixedText()] = [
-                                               'page_len' => $row->page_len,
-                                               'page_latest' => $row->page_latest,
-                                               'page_touched' => $row->page_touched,
-                                       ];
+       /**
+        * @since 1.28
+        * @param ResourceLoaderContext $context
+        * @param IDatabase $db
+        * @param string[] $moduleNames
+        */
+       public static function preloadTitleInfo(
+               ResourceLoaderContext $context, IDatabase $db, array $moduleNames
+       ) {
+               $rl = $context->getResourceLoader();
+               // 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 );
+                       if ( $module instanceof self ) {
+                               $mDB = $module->getDB();
+                               // Subclasses may disable getDB and implement getTitleInfo differently
+                               if ( $mDB && $mDB->getWikiID() === $db->getWikiID() ) {
+                                       $wikiModules[] = $module;
+                                       $allPages += $module->getPages( $context );
                                }
                        }
                }
-               return $this->titleInfo[$key];
+
+               $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 ) {
+                               $title = Title::newFromText( $page );
+                               if ( $title ) {
+                                       $intersect[ $title->getPrefixedText() ] = 1;
+                               } else {
+                                       // Page name may be invalid if user-provided (e.g. gadgets)
+                                       $rl->getLogger()->info(
+                                               'Invalid wiki page title "{title}" in ' . __METHOD__,
+                                               [ 'title' => $page ]
+                                       );
+                               }
+                       }
+                       $info = array_intersect_key( $allInfo, $intersect );
+                       $pageNames = array_keys( $pages );
+                       sort( $pageNames );
+                       $key = implode( '|', $pageNames );
+                       $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 );
+               }
        }
 
        /**