From d9022f63880ce039446fba8364f68e656b7bf4cb Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Thu, 3 May 2012 13:01:35 +0200 Subject: Update to MediaWiki 1.19.0 --- includes/cache/CacheDependency.php | 16 ++ includes/cache/FileCacheBase.php | 249 ++++++++++++++++++++++++++++++ includes/cache/GenderCache.php | 135 ++++++++++++++++ includes/cache/HTMLCacheUpdate.php | 17 ++- includes/cache/HTMLFileCache.php | 288 ++++++++++++++--------------------- includes/cache/LinkBatch.php | 20 ++- includes/cache/LinkCache.php | 55 ++++--- includes/cache/MessageCache.php | 54 +++++-- includes/cache/ObjectFileCache.php | 30 ++++ includes/cache/ResourceFileCache.php | 87 +++++++++++ 10 files changed, 726 insertions(+), 225 deletions(-) create mode 100644 includes/cache/FileCacheBase.php create mode 100644 includes/cache/GenderCache.php create mode 100644 includes/cache/ObjectFileCache.php create mode 100644 includes/cache/ResourceFileCache.php (limited to 'includes/cache') diff --git a/includes/cache/CacheDependency.php b/includes/cache/CacheDependency.php index aa020664..0df0cd89 100644 --- a/includes/cache/CacheDependency.php +++ b/includes/cache/CacheDependency.php @@ -28,6 +28,8 @@ class DependencyWrapper { /** * Returns true if any of the dependencies have expired + * + * @return bool */ function isExpired() { foreach ( $this->deps as $dep ) { @@ -51,6 +53,7 @@ class DependencyWrapper { /** * Get the user-defined value + * @return bool|\Mixed */ function getValue() { return $this->value; @@ -143,6 +146,9 @@ class FileDependency extends CacheDependency { $this->timestamp = $timestamp; } + /** + * @return array + */ function __sleep() { $this->loadDependencyValues(); return array( 'filename', 'timestamp' ); @@ -265,11 +271,15 @@ class TitleListDependency extends CacheDependency { /** * Construct a dependency on a list of titles + * @param $linkBatch LinkBatch */ function __construct( LinkBatch $linkBatch ) { $this->linkBatch = $linkBatch; } + /** + * @return array + */ function calculateTimestamps() { # Initialise values to false $timestamps = array(); @@ -314,6 +324,9 @@ class TitleListDependency extends CacheDependency { return array( 'timestamps' ); } + /** + * @return LinkBatch + */ function getLinkBatch() { if ( !isset( $this->linkBatch ) ) { $this->linkBatch = new LinkBatch; @@ -370,6 +383,9 @@ class GlobalDependency extends CacheDependency { * @return bool */ function isExpired() { + if( !isset($GLOBALS[$this->name]) ) { + return true; + } return $GLOBALS[$this->name] != $this->value; } } diff --git a/includes/cache/FileCacheBase.php b/includes/cache/FileCacheBase.php new file mode 100644 index 00000000..37401655 --- /dev/null +++ b/includes/cache/FileCacheBase.php @@ -0,0 +1,249 @@ +mUseGzip = (bool)$wgUseGzip; + } + + /** + * Get the base file cache directory + * @return string + */ + final protected function baseCacheDirectory() { + global $wgFileCacheDirectory; + return $wgFileCacheDirectory; + } + + /** + * Get the base cache directory (not specific to this file) + * @return string + */ + abstract protected function cacheDirectory(); + + /** + * Get the path to the cache file + * @return string + */ + protected function cachePath() { + if ( $this->mFilePath !== null ) { + return $this->mFilePath; + } + + $dir = $this->cacheDirectory(); + # Build directories (methods include the trailing "/") + $subDirs = $this->typeSubdirectory() . $this->hashSubdirectory(); + # Avoid extension confusion + $key = str_replace( '.', '%2E', urlencode( $this->mKey ) ); + # Build the full file path + $this->mFilePath = "{$dir}/{$subDirs}{$key}.{$this->mExt}"; + if ( $this->useGzip() ) { + $this->mFilePath .= '.gz'; + } + + return $this->mFilePath; + } + + /** + * Check if the cache file exists + * @return bool + */ + public function isCached() { + if ( $this->mCached === null ) { + $this->mCached = file_exists( $this->cachePath() ); + } + return $this->mCached; + } + + /** + * Get the last-modified timestamp of the cache file + * @return string|false TS_MW timestamp + */ + public function cacheTimestamp() { + $timestamp = filemtime( $this->cachePath() ); + return ( $timestamp !== false ) + ? wfTimestamp( TS_MW, $timestamp ) + : false; + } + + /** + * Check if up to date cache file exists + * @param $timestamp string MW_TS timestamp + * + * @return bool + */ + public function isCacheGood( $timestamp = '' ) { + global $wgCacheEpoch; + + if ( !$this->isCached() ) { + return false; + } + + $cachetime = $this->cacheTimestamp(); + $good = ( $timestamp <= $cachetime && $wgCacheEpoch <= $cachetime ); + wfDebug( __METHOD__ . ": cachetime $cachetime, touched '{$timestamp}' epoch {$wgCacheEpoch}, good $good\n" ); + + return $good; + } + + /** + * Check if the cache is gzipped + * @return bool + */ + protected function useGzip() { + return $this->mUseGzip; + } + + /** + * Get the uncompressed text from the cache + * @return string + */ + public function fetchText() { + // gzopen can transparently read from gziped or plain text + $fh = gzopen( $this->cachePath(), 'rb' ); + return stream_get_contents( $fh ); + } + + /** + * Save and compress text to the cache + * @return string compressed text + */ + public function saveText( $text ) { + global $wgUseFileCache; + + if ( !$wgUseFileCache ) { + return false; + } + + if ( $this->useGzip() ) { + $text = gzencode( $text ); + } + + $this->checkCacheDirs(); // build parent dir + if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) { + wfDebug( __METHOD__ . "() failed saving ". $this->cachePath() . "\n"); + $this->mCached = null; + return false; + } + + $this->mCached = true; + return $text; + } + + /** + * Clear the cache for this page + * @return void + */ + public function clearCache() { + wfSuppressWarnings(); + unlink( $this->cachePath() ); + wfRestoreWarnings(); + $this->mCached = false; + } + + /** + * Create parent directors of $this->cachePath() + * @return void + */ + protected function checkCacheDirs() { + wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ ); + } + + /** + * Get the cache type subdirectory (with trailing slash) + * An extending class could use that method to alter the type -> directory + * mapping. @see HTMLFileCache::typeSubdirectory() for an example. + * + * @return string + */ + protected function typeSubdirectory() { + return $this->mType . '/'; + } + + /** + * Return relative multi-level hash subdirectory (with trailing slash) + * or the empty string if not $wgFileCacheDepth + * @return string + */ + protected function hashSubdirectory() { + global $wgFileCacheDepth; + + $subdir = ''; + if ( $wgFileCacheDepth > 0 ) { + $hash = md5( $this->mKey ); + for ( $i = 1; $i <= $wgFileCacheDepth; $i++ ) { + $subdir .= substr( $hash, 0, $i ) . '/'; + } + } + + return $subdir; + } + + /** + * Roughly increments the cache misses in the last hour by unique visitors + * @param $request WebRequest + * @return void + */ + public function incrMissesRecent( WebRequest $request ) { + global $wgMemc; + if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) { + # Get a large IP range that should include the user even if that + # person's IP address changes + $ip = $request->getIP(); + if ( !IP::isValid( $ip ) ) { + return; + } + $ip = IP::isIPv6( $ip ) + ? IP::sanitizeRange( "$ip/32" ) + : IP::sanitizeRange( "$ip/16" ); + + # Bail out if a request already came from this range... + $key = wfMemcKey( get_class( $this ), 'attempt', $this->mType, $this->mKey, $ip ); + if ( $wgMemc->get( $key ) ) { + return; // possibly the same user + } + $wgMemc->set( $key, 1, self::MISS_TTL_SEC ); + + # Increment the number of cache misses... + $key = $this->cacheMissKey(); + if ( $wgMemc->get( $key ) === false ) { + $wgMemc->set( $key, 1, self::MISS_TTL_SEC ); + } else { + $wgMemc->incr( $key ); + } + } + } + + /** + * Roughly gets the cache misses in the last hour by unique visitors + * @return int + */ + public function getMissesRecent() { + global $wgMemc; + return self::MISS_FACTOR * $wgMemc->get( $this->cacheMissKey() ); + } + + /** + * @return string + */ + protected function cacheMissKey() { + return wfMemcKey( get_class( $this ), 'misses', $this->mType, $this->mKey ); + } +} diff --git a/includes/cache/GenderCache.php b/includes/cache/GenderCache.php new file mode 100644 index 00000000..342f8dba --- /dev/null +++ b/includes/cache/GenderCache.php @@ -0,0 +1,135 @@ +default === null ) { + $this->default = User::getDefaultOption( 'gender' ); + } + return $this->default; + } + + /** + * Returns the gender for given username. + * @param $username String: username + * @param $caller String: the calling method + * @return String + */ + public function getGenderOf( $username, $caller = '' ) { + global $wgUser; + + $username = strtr( $username, '_', ' ' ); + if ( !isset( $this->cache[$username] ) ) { + + if ( $this->misses >= $this->missLimit && $wgUser->getName() !== $username ) { + if( $this->misses === $this->missLimit ) { + $this->misses++; + wfDebug( __METHOD__ . ": too many misses, returning default onwards\n" ); + } + return $this->getDefault(); + + } else { + $this->misses++; + if ( !User::isValidUserName( $username ) ) { + $this->cache[$username] = $this->getDefault(); + } else { + $this->doQuery( $username, $caller ); + } + } + + } + + /* Undefined if there is a valid username which for some reason doesn't + * exist in the database. + */ + return isset( $this->cache[$username] ) ? $this->cache[$username] : $this->getDefault(); + } + + /** + * Wrapper for doQuery that processes raw LinkBatch data. + * + * @param $data + * @param $caller + */ + public function doLinkBatch( $data, $caller = '' ) { + $users = array(); + foreach ( $data as $ns => $pagenames ) { + if ( !MWNamespace::hasGenderDistinction( $ns ) ) continue; + foreach ( array_keys( $pagenames ) as $username ) { + if ( isset( $this->cache[$username] ) ) continue; + $users[$username] = true; + } + } + + $this->doQuery( array_keys( $users ), $caller ); + } + + /** + * Preloads genders for given list of users. + * @param $users List|String: usernames + * @param $caller String: the calling method + */ + public function doQuery( $users, $caller = '' ) { + $default = $this->getDefault(); + + foreach ( (array) $users as $index => $value ) { + $name = strtr( $value, '_', ' ' ); + if ( isset( $this->cache[$name] ) ) { + // Skip users whose gender setting we already know + unset( $users[$index] ); + } else { + $users[$index] = $name; + // For existing users, this value will be overwritten by the correct value + $this->cache[$name] = $default; + } + } + + if ( count( $users ) === 0 ) { + return; + } + + $dbr = wfGetDB( DB_SLAVE ); + $table = array( 'user', 'user_properties' ); + $fields = array( 'user_name', 'up_value' ); + $conds = array( 'user_name' => $users ); + $joins = array( 'user_properties' => + array( 'LEFT JOIN', array( 'user_id = up_user', 'up_property' => 'gender' ) ) ); + + $comment = __METHOD__; + if ( strval( $caller ) !== '' ) { + $comment .= "/$caller"; + } + $res = $dbr->select( $table, $fields, $conds, $comment, $joins, $joins ); + + foreach ( $res as $row ) { + $this->cache[$row->user_name] = $row->up_value ? $row->up_value : $default; + } + } + +} diff --git a/includes/cache/HTMLCacheUpdate.php b/includes/cache/HTMLCacheUpdate.php index d542800d..11e2ae74 100644 --- a/includes/cache/HTMLCacheUpdate.php +++ b/includes/cache/HTMLCacheUpdate.php @@ -23,8 +23,7 @@ * * @ingroup Cache */ -class HTMLCacheUpdate -{ +class HTMLCacheUpdate implements DeferrableUpdate { /** * @var Title */ @@ -33,6 +32,12 @@ class HTMLCacheUpdate public $mTable, $mPrefix, $mStart, $mEnd; public $mRowsPerJob, $mRowsPerQuery; + /** + * @param $titleTo + * @param $table + * @param $start bool + * @param $end bool + */ function __construct( $titleTo, $table, $start = false, $end = false ) { global $wgUpdateRowsPerJob, $wgUpdateRowsPerQuery; @@ -138,6 +143,9 @@ class HTMLCacheUpdate Job::batchInsert( $jobs ); } + /** + * @return mixed + */ protected function insertJobs() { $batches = $this->mCache->partition( $this->mTable, $this->mRowsPerJob ); if ( !$batches ) { @@ -157,6 +165,7 @@ class HTMLCacheUpdate /** * Invalidate an array (or iterator) of Title objects, right now + * @param $titleArray array */ protected function invalidateTitles( $titleArray ) { global $wgUseFileCache, $wgUseSquid; @@ -179,7 +188,7 @@ class HTMLCacheUpdate foreach ( $batches as $batch ) { $dbw->update( 'page', array( 'page_touched' => $timestamp ), - array( 'page_id IN (' . $dbw->makeList( $batch ) . ')' ), + array( 'page_id' => $batch ), __METHOD__ ); } @@ -197,9 +206,9 @@ class HTMLCacheUpdate } } } - } + /** * Job wrapper for HTMLCacheUpdate. Gets run whenever a related * job gets called from the queue. diff --git a/includes/cache/HTMLFileCache.php b/includes/cache/HTMLFileCache.php index 1095da2c..92130f69 100644 --- a/includes/cache/HTMLFileCache.php +++ b/includes/cache/HTMLFileCache.php @@ -4,170 +4,112 @@ * @file * @ingroup Cache */ - -/** - * Handles talking to the file cache, putting stuff in and taking it back out. - * Mostly called from Article.php for the emergency abort/fallback to cache. - * - * Global options that affect this module: - * - $wgCachePages - * - $wgCacheEpoch - * - $wgUseFileCache - * - $wgCacheDirectory - * - $wgFileCacheDirectory - * - $wgUseGzip - * - * @ingroup Cache - */ -class HTMLFileCache { - +class HTMLFileCache extends FileCacheBase { /** - * @var Title + * Construct an ObjectFileCache from a Title and an action + * @param $title Title|string Title object or prefixed DB key string + * @param $action string + * @return HTMLFileCache */ - var $mTitle; - var $mFileCache, $mType; + public static function newFromTitle( $title, $action ) { + $cache = new self(); - public function __construct( $title, $type = 'view' ) { - $this->mTitle = $title; - $this->mType = ($type == 'raw' || $type == 'view' ) ? $type : false; - $this->fileCacheName(); // init name - } - - public function fileCacheName() { - if( !$this->mFileCache ) { - global $wgCacheDirectory, $wgFileCacheDirectory, $wgFileCacheDepth; - - if ( $wgFileCacheDirectory ) { - $dir = $wgFileCacheDirectory; - } elseif ( $wgCacheDirectory ) { - $dir = "$wgCacheDirectory/html"; - } else { - throw new MWException( 'Please set $wgCacheDirectory in LocalSettings.php if you wish to use the HTML file cache' ); - } - - # Store raw pages (like CSS hits) elsewhere - $subdir = ($this->mType === 'raw') ? 'raw/' : ''; - - $key = $this->mTitle->getPrefixedDbkey(); - if ( $wgFileCacheDepth > 0 ) { - $hash = md5( $key ); - for ( $i = 1; $i <= $wgFileCacheDepth; $i++ ) { - $subdir .= substr( $hash, 0, $i ) . '/'; - } - } - # Avoid extension confusion - $key = str_replace( '.', '%2E', urlencode( $key ) ); - $this->mFileCache = "{$dir}/{$subdir}{$key}.html"; + $allowedTypes = self::cacheablePageActions(); + if ( !in_array( $action, $allowedTypes ) ) { + throw new MWException( "Invalid filecache type given." ); + } + $cache->mKey = ( $title instanceof Title ) + ? $title->getPrefixedDBkey() + : (string)$title; + $cache->mType = (string)$action; + $cache->mExt = 'html'; - if( $this->useGzip() ) { - $this->mFileCache .= '.gz'; - } + return $cache; + } - wfDebug( __METHOD__ . ": {$this->mFileCache}\n" ); - } - return $this->mFileCache; + /** + * Cacheable actions + * @return array + */ + protected static function cacheablePageActions() { + return array( 'view', 'history' ); } - public function isFileCached() { - if( $this->mType === false ) { - return false; - } - return file_exists( $this->fileCacheName() ); + /** + * Get the base file cache directory + * @return string + */ + protected function cacheDirectory() { + return $this->baseCacheDirectory(); // no subdir for b/c with old cache files } - public function fileCacheTime() { - return wfTimestamp( TS_MW, filemtime( $this->fileCacheName() ) ); + /** + * Get the cache type subdirectory (with the trailing slash) or the empty string + * Alter the type -> directory mapping to put action=view cache at the root. + * + * @return string + */ + protected function typeSubdirectory() { + if ( $this->mType === 'view' ) { + return ''; // b/c to not skip existing cache + } else { + return $this->mType . '/'; + } } /** * Check if pages can be cached for this request/user + * @param $context IContextSource * @return bool */ - public static function useFileCache() { - global $wgUser, $wgUseFileCache, $wgShowIPinHeader, $wgRequest, $wgLang, $wgContLang; - if( !$wgUseFileCache ) { + public static function useFileCache( IContextSource $context ) { + global $wgUseFileCache, $wgShowIPinHeader, $wgDebugToolbar, $wgContLang; + if ( !$wgUseFileCache ) { return false; } + if ( $wgShowIPinHeader || $wgDebugToolbar ) { + wfDebug( "HTML file cache skipped. Either \$wgShowIPinHeader and/or \$wgDebugToolbar on\n" ); + return false; + } + // Get all query values - $queryVals = $wgRequest->getValues(); - foreach( $queryVals as $query => $val ) { - if( $query == 'title' || $query == 'curid' ) { - continue; + $queryVals = $context->getRequest()->getValues(); + foreach ( $queryVals as $query => $val ) { + if ( $query === 'title' || $query === 'curid' ) { + continue; // note: curid sets title // Normal page view in query form can have action=view. - // Raw hits for pages also stored, like .css pages for example. - } elseif( $query == 'action' && $val == 'view' ) { - continue; - } elseif( $query == 'usemsgcache' && $val == 'yes' ) { + } elseif ( $query === 'action' && in_array( $val, self::cacheablePageActions() ) ) { continue; // Below are header setting params - } elseif( $query == 'maxage' || $query == 'smaxage' || $query == 'ctype' || $query == 'gen' ) { + } elseif ( $query === 'maxage' || $query === 'smaxage' ) { continue; - } else { - return false; } + return false; } + $user = $context->getUser(); // Check for non-standard user language; this covers uselang, // and extensions for auto-detecting user language. - $ulang = $wgLang->getCode(); + $ulang = $context->getLanguage()->getCode(); $clang = $wgContLang->getCode(); // Check that there are no other sources of variation - return !$wgShowIPinHeader && !$wgUser->getId() && !$wgUser->getNewtalk() && $ulang == $clang; + return !$user->getId() && !$user->getNewtalk() && $ulang == $clang; } /** - * Check if up to date cache file exists - * @param $timestamp string - * - * @return bool + * Read from cache to context output + * @param $context IContextSource + * @return void */ - public function isFileCacheGood( $timestamp = '' ) { - global $wgCacheEpoch; + public function loadFromFileCache( IContextSource $context ) { + global $wgMimeType, $wgLanguageCode; - if( !$this->isFileCached() ) { - return false; - } - - $cachetime = $this->fileCacheTime(); - $good = $timestamp <= $cachetime && $wgCacheEpoch <= $cachetime; - - wfDebug( __METHOD__ . ": cachetime $cachetime, touched '{$timestamp}' epoch {$wgCacheEpoch}, good $good\n"); - return $good; - } - - public function useGzip() { - global $wgUseGzip; - return $wgUseGzip; - } - - /* In handy string packages */ - public function fetchRawText() { - return file_get_contents( $this->fileCacheName() ); - } - - public function fetchPageText() { - if( $this->useGzip() ) { - /* Why is there no gzfile_get_contents() or gzdecode()? */ - return implode( '', gzfile( $this->fileCacheName() ) ); - } else { - return $this->fetchRawText(); - } - } - - /* Working directory to/from output */ - public function loadFromFileCache() { - global $wgOut, $wgMimeType, $wgLanguageCode; wfDebug( __METHOD__ . "()\n"); - $filename = $this->fileCacheName(); - // Raw pages should handle cache control on their own, - // even when using file cache. This reduces hits from clients. - if( $this->mType !== 'raw' ) { - $wgOut->sendCacheControl(); - header( "Content-Type: $wgMimeType; charset=UTF-8" ); - header( "Content-Language: $wgLanguageCode" ); - } - - if( $this->useGzip() ) { - if( wfClientAcceptsGzip() ) { + $filename = $this->cachePath(); + $context->getOutput()->sendCacheControl(); + header( "Content-Type: $wgMimeType; charset=UTF-8" ); + header( "Content-Language: $wgLanguageCode" ); + if ( $this->useGzip() ) { + if ( wfClientAcceptsGzip() ) { header( 'Content-Encoding: gzip' ); } else { /* Send uncompressed */ @@ -176,74 +118,70 @@ class HTMLFileCache { } } readfile( $filename ); - $wgOut->disable(); // tell $wgOut that output is taken care of - } - - protected function checkCacheDirs() { - $filename = $this->fileCacheName(); - $mydir2 = substr($filename,0,strrpos($filename,'/')); # subdirectory level 2 - $mydir1 = substr($mydir2,0,strrpos($mydir2,'/')); # subdirectory level 1 - - wfMkdirParents( $mydir1 ); - wfMkdirParents( $mydir2 ); + $context->getOutput()->disable(); // tell $wgOut that output is taken care of } + /** + * Save this cache object with the given text. + * Use this as an ob_start() handler. + * @param $text string + * @return bool Whether $wgUseFileCache is enabled + */ public function saveToFileCache( $text ) { global $wgUseFileCache; - if( !$wgUseFileCache || strlen( $text ) < 512 ) { + + if ( !$wgUseFileCache || strlen( $text ) < 512 ) { // Disabled or empty/broken output (OOM and PHP errors) return $text; } wfDebug( __METHOD__ . "()\n", false); - $this->checkCacheDirs(); + $now = wfTimestampNow(); + if ( $this->useGzip() ) { + $text = str_replace( + '', '\n", $text ); + } else { + $text = str_replace( + '', '\n", $text ); + } - $f = fopen( $this->fileCacheName(), 'w' ); - if($f) { - $now = wfTimestampNow(); - if( $this->useGzip() ) { - $rawtext = str_replace( '', - '\n", - $text ); - $text = gzencode( $rawtext ); - } else { - $text = str_replace( '', - '\n", - $text ); - } - fwrite( $f, $text ); - fclose( $f ); - if( $this->useGzip() ) { - if( wfClientAcceptsGzip() ) { - header( 'Content-Encoding: gzip' ); - return $text; - } else { - return $rawtext; - } + // Store text to FS... + $compressed = $this->saveText( $text ); + if ( $compressed === false ) { + return $text; // error + } + + // gzip output to buffer as needed and set headers... + if ( $this->useGzip() ) { + // @TODO: ugly wfClientAcceptsGzip() function - use context! + if ( wfClientAcceptsGzip() ) { + header( 'Content-Encoding: gzip' ); + return $compressed; } else { return $text; } + } else { + return $text; } - return $text; } - public static function clearFileCache( $title ) { + /** + * Clear the file caches for a page for all actions + * @param $title Title + * @return bool Whether $wgUseFileCache is enabled + */ + public static function clearFileCache( Title $title ) { global $wgUseFileCache; if ( !$wgUseFileCache ) { return false; } - wfSuppressWarnings(); - - $fc = new self( $title, 'view' ); - unlink( $fc->fileCacheName() ); - - $fc = new self( $title, 'raw' ); - unlink( $fc->fileCacheName() ); - - wfRestoreWarnings(); + foreach ( self::cacheablePageActions() as $type ) { + $fc = self::newFromTitle( $title, $type ); + $fc->clearCache(); + } return true; } diff --git a/includes/cache/LinkBatch.php b/includes/cache/LinkBatch.php index 0bd869fc..17e8739b 100644 --- a/includes/cache/LinkBatch.php +++ b/includes/cache/LinkBatch.php @@ -86,7 +86,8 @@ class LinkBatch { /** * Do the query and add the results to the LinkCache object - * Return an array mapping PDBK to ID + * + * @return Array mapping PDBK to ID */ public function execute() { $linkCache = LinkCache::singleton(); @@ -96,12 +97,15 @@ class LinkBatch { /** * Do the query and add the results to a given LinkCache object * Return an array mapping PDBK to ID + * + * @param $cache LinkCache + * @return Array remaining IDs */ protected function executeInto( &$cache ) { wfProfileIn( __METHOD__ ); $res = $this->doQuery(); - $ids = $this->addResultToCache( $cache, $res ); $this->doGenderQuery(); + $ids = $this->addResultToCache( $cache, $res ); wfProfileOut( __METHOD__ ); return $ids; } @@ -112,8 +116,9 @@ class LinkBatch { * This function *also* stores extra fields of the title used for link * parsing to avoid extra DB queries. * - * @param $cache + * @param $cache LinkCache * @param $res + * @return Array of remaining titles */ public function addResultToCache( $cache, $res ) { if ( !$res ) { @@ -126,7 +131,7 @@ class LinkBatch { $remaining = $this->data; foreach ( $res as $row ) { $title = Title::makeTitle( $row->page_namespace, $row->page_title ); - $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect, $row->page_latest ); + $cache->addGoodLinkObjFromRow( $title, $row ); $ids[$title->getPrefixedDBkey()] = $row->page_id; unset( $remaining[$row->page_namespace][$row->page_title] ); } @@ -144,6 +149,7 @@ class LinkBatch { /** * Perform the existence test query, return a ResultWrapper with page_id fields + * @return Bool|ResultWrapper */ public function doQuery() { if ( $this->isEmpty() ) { @@ -168,6 +174,11 @@ class LinkBatch { return $res; } + /** + * Do (and cache) {{GENDER:...}} information for userpages in this LinkBatch + * + * @return bool whether the query was successful + */ public function doGenderQuery() { if ( $this->isEmpty() ) { return false; @@ -180,6 +191,7 @@ class LinkBatch { $genderCache = GenderCache::singleton(); $genderCache->dolinkBatch( $this->data, $this->caller ); + return true; } /** diff --git a/includes/cache/LinkCache.php b/includes/cache/LinkCache.php index aeb10eb0..a73eaaa4 100644 --- a/includes/cache/LinkCache.php +++ b/includes/cache/LinkCache.php @@ -9,8 +9,10 @@ class LinkCache { // becomes incompatible with the new version. private $mClassVer = 4; - private $mGoodLinks, $mBadLinks; - private $mForUpdate; + private $mGoodLinks = array(); + private $mGoodLinkFields = array(); + private $mBadLinks = array(); + private $mForUpdate = false; /** * Get an instance of this class @@ -25,13 +27,6 @@ class LinkCache { return $instance; } - function __construct() { - $this->mForUpdate = false; - $this->mGoodLinks = array(); - $this->mGoodLinkFields = array(); - $this->mBadLinks = array(); - } - /** * General accessor to get/set whether SELECT FOR UPDATE should be used * @@ -95,6 +90,23 @@ class LinkCache { 'revision' => intval( $revision ) ); } + /** + * Same as above with better interface. + * @since 1.19 + * @param $title Title + * @param $row object which has the fields page_id, page_is_redirect, + * page_latest + */ + public function addGoodLinkObjFromRow( $title, $row ) { + $dbkey = $title->getPrefixedDbKey(); + $this->mGoodLinks[$dbkey] = intval( $row->page_id ); + $this->mGoodLinkFields[$dbkey] = array( + 'length' => intval( $row->page_len ), + 'redirect' => intval( $row->page_is_redirect ), + 'revision' => intval( $row->page_latest ), + ); + } + /** * @param $title Title */ @@ -114,15 +126,9 @@ class LinkCache { */ public function clearLink( $title ) { $dbkey = $title->getPrefixedDbKey(); - if( isset($this->mBadLinks[$dbkey]) ) { - unset($this->mBadLinks[$dbkey]); - } - if( isset($this->mGoodLinks[$dbkey]) ) { - unset($this->mGoodLinks[$dbkey]); - } - if( isset($this->mGoodLinkFields[$dbkey]) ) { - unset($this->mGoodLinkFields[$dbkey]); - } + unset( $this->mBadLinks[$dbkey] ); + unset( $this->mGoodLinks[$dbkey] ); + unset( $this->mGoodLinkFields[$dbkey] ); } public function getGoodLinks() { return $this->mGoodLinks; } @@ -188,22 +194,13 @@ class LinkCache { __METHOD__, $options ); # Set fields... if ( $s !== false ) { + $this->addGoodLinkObjFromRow( $nt, $s ); $id = intval( $s->page_id ); - $len = intval( $s->page_len ); - $redirect = intval( $s->page_is_redirect ); - $revision = intval( $s->page_latest ); } else { + $this->addBadLinkObj( $nt ); $id = 0; - $len = -1; - $redirect = 0; - $revision = 0; } - if ( $id == 0 ) { - $this->addBadLinkObj( $nt ); - } else { - $this->addGoodLinkObj( $id, $nt, $len, $redirect, $revision ); - } wfProfileOut( __METHOD__ ); return $id; } diff --git a/includes/cache/MessageCache.php b/includes/cache/MessageCache.php index 79883844..146edd28 100644 --- a/includes/cache/MessageCache.php +++ b/includes/cache/MessageCache.php @@ -176,7 +176,7 @@ class MessageCache { global $wgCacheDirectory; $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code"; - wfMkdirParents( $wgCacheDirectory ); // might fail + wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail wfSuppressWarnings(); $file = fopen( $filename, 'w' ); @@ -199,7 +199,7 @@ class MessageCache { $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code"; $tempFilename = $filename . '.tmp'; - wfMkdirParents( $wgCacheDirectory ); // might fail + wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail wfSuppressWarnings(); $file = fopen( $tempFilename, 'w' ); @@ -499,10 +499,10 @@ class MessageCache { $codes = array_keys( Language::getLanguageNames() ); } - global $parserMemc; + global $wgMemc; foreach ( $codes as $code ) { $sidebarKey = wfMemcKey( 'sidebar', $code ); - $parserMemc->delete( $sidebarKey ); + $wgMemc->delete( $sidebarKey ); } // Update the message in the message blob store @@ -553,6 +553,8 @@ class MessageCache { /** * Represents a write lock on the messages key * + * @param $key string + * * @return Boolean: success */ function lock( $key ) { @@ -585,6 +587,8 @@ class MessageCache { * fallback). * @param $isFullKey Boolean: specifies whether $key is a two part key * "msg/lang". + * + * @return string|false */ function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) { global $wgLanguageCode, $wgContLang; @@ -691,6 +695,8 @@ class MessageCache { * * @param $title String: Message cache key with initial uppercase letter. * @param $code String: code denoting the language to try. + * + * @return string|false */ function getMsgFromNamespace( $title, $code ) { global $wgAdaptiveMessageCache; @@ -814,10 +820,9 @@ class MessageCache { /** * @param $text string - * @param $string Title|string * @param $title Title - * @param $interface bool * @param $linestart bool + * @param $interface bool * @param $language * @return ParserOutput */ @@ -828,13 +833,8 @@ class MessageCache { $parser = $this->getParser(); $popts = $this->getParserOptions(); - - if ( $interface ) { - $popts->setInterfaceMessage( true ); - } - if ( $language !== null ) { - $popts->setTargetLanguage( $language ); - } + $popts->setInterfaceMessage( $interface ); + $popts->setTargetLanguage( $language ); wfProfileIn( __METHOD__ ); if ( !$title || !$title instanceof Title ) { @@ -878,6 +878,10 @@ class MessageCache { $this->mLoadedLanguages = array(); } + /** + * @param $key + * @return array + */ public function figureMessage( $key ) { global $wgLanguageCode; $pieces = explode( '/', $key ); @@ -935,6 +939,9 @@ class MessageCache { wfProfileOut( __METHOD__ ); } + /** + * @return array + */ public function getMostUsedMessages() { wfProfileIn( __METHOD__ ); $cachekey = wfMemcKey( 'message-profiling' ); @@ -968,4 +975,25 @@ class MessageCache { return array_keys( $list ); } + /** + * Get all message keys stored in the message cache for a given language. + * If $code is the content language code, this will return all message keys + * for which MediaWiki:msgkey exists. If $code is another language code, this + * will ONLY return message keys for which MediaWiki:msgkey/$code exists. + * @param $code string + * @return array of message keys (strings) + */ + public function getAllMessageKeys( $code ) { + global $wgContLang; + $this->load( $code ); + if ( !isset( $this->mCache[$code] ) ) { + // Apparently load() failed + return null; + } + $cache = $this->mCache[$code]; // Copy the cache + unset( $cache['VERSION'] ); // Remove the VERSION key + $cache = array_diff( $cache, array( '!NONEXISTENT' ) ); // Remove any !NONEXISTENT keys + // Keys may appear with a capital first letter. lcfirst them. + return array_map( array( $wgContLang, 'lcfirst' ), array_keys( $cache ) ); + } } diff --git a/includes/cache/ObjectFileCache.php b/includes/cache/ObjectFileCache.php new file mode 100644 index 00000000..3356f1fc --- /dev/null +++ b/includes/cache/ObjectFileCache.php @@ -0,0 +1,30 @@ +mKey = (string)$key; + $cache->mType = (string)$type; + + return $cache; + } + + /** + * Get the base file cache directory + * @return string + */ + protected function cacheDirectory() { + return $this->baseCacheDirectory() . '/object'; + } +} diff --git a/includes/cache/ResourceFileCache.php b/includes/cache/ResourceFileCache.php new file mode 100644 index 00000000..e73fc2d7 --- /dev/null +++ b/includes/cache/ResourceFileCache.php @@ -0,0 +1,87 @@ +getOnly() === 'styles' ) { + $cache->mType = 'css'; + } else { + $cache->mType = 'js'; + } + $modules = array_unique( $context->getModules() ); // remove duplicates + sort( $modules ); // normalize the order (permutation => combination) + $cache->mKey = sha1( $context->getHash() . implode( '|', $modules ) ); + if ( count( $modules ) == 1 ) { + $cache->mCacheWorthy = true; // won't take up much space + } + + return $cache; + } + + /** + * Check if an RL request can be cached. + * Caller is responsible for checking if any modules are private. + * @param $context ResourceLoaderContext + * @return bool + */ + public static function useFileCache( ResourceLoaderContext $context ) { + global $wgUseFileCache, $wgDefaultSkin, $wgLanguageCode; + if ( !$wgUseFileCache ) { + return false; + } + // Get all query values + $queryVals = $context->getRequest()->getValues(); + foreach ( $queryVals as $query => $val ) { + if ( $query === 'modules' || $query === 'version' || $query === '*' ) { + continue; // note: &* added as IE fix + } elseif ( $query === 'skin' && $val === $wgDefaultSkin ) { + continue; + } elseif ( $query === 'lang' && $val === $wgLanguageCode ) { + continue; + } elseif ( $query === 'only' && in_array( $val, array( 'styles', 'scripts' ) ) ) { + continue; + } elseif ( $query === 'debug' && $val === 'false' ) { + continue; + } + return false; + } + return true; // cacheable + } + + /** + * Get the base file cache directory + * @return string + */ + protected function cacheDirectory() { + return $this->baseCacheDirectory() . '/resources'; + } + + /** + * Item has many recent cache misses + * @return bool + */ + public function isCacheWorthy() { + if ( $this->mCacheWorthy === null ) { + $this->mCacheWorthy = ( + $this->isCached() || // even stale cache indicates it was cache worthy + $this->getMissesRecent() >= self::MISS_THRESHOLD // many misses + ); + } + return $this->mCacheWorthy; + } +} -- cgit v1.2.3-54-g00ecf