/** * Purge the objectcache table */ public function purgeCache() { global $wgLocalisationCacheConf; # We can't guarantee that the user will be able to use TRUNCATE, # but we know that DELETE is available to us $this->output("Purging caches..."); $this->db->delete('objectcache', '*', __METHOD__); if ($wgLocalisationCacheConf['manualRecache']) { $this->rebuildLocalisationCache(); } MessageBlobStore::getInstance()->clear(); $this->output("done.\n"); }
/** * Updates cache as necessary when message page is changed * * @param string $title Name of the page changed. * @param mixed $text New contents of the page. */ public function replace($title, $text) { global $wgMaxMsgCacheEntrySize; wfProfileIn(__METHOD__); if ($this->mDisable) { wfProfileOut(__METHOD__); return; } list($msg, $code) = $this->figureMessage($title); $cacheKey = wfMemcKey('messages', $code); $this->load($code); $this->lock($cacheKey); $titleKey = wfMemcKey('messages', 'individual', $title); if ($text === false) { # Article was deleted $this->mCache[$code][$title] = '!NONEXISTENT'; $this->mMemc->delete($titleKey); } elseif (strlen($text) > $wgMaxMsgCacheEntrySize) { # Check for size $this->mCache[$code][$title] = '!TOO BIG'; $this->mMemc->set($titleKey, ' ' . $text, $this->mExpiry); } else { $this->mCache[$code][$title] = ' ' . $text; $this->mMemc->delete($titleKey); } # Update caches $this->saveToCaches($this->mCache[$code], 'all', $code); $this->unlock($cacheKey); // Also delete cached sidebar... just in case it is affected $codes = array($code); if ($code === 'en') { // Delete all sidebars, like for example on action=purge on the // sidebar messages $codes = array_keys(Language::fetchLanguageNames()); } global $wgMemc; foreach ($codes as $code) { $sidebarKey = wfMemcKey('sidebar', $code); $wgMemc->delete($sidebarKey); } // Update the message in the message blob store global $wgContLang; MessageBlobStore::getInstance()->updateMessage($wgContLang->lcfirst($msg)); wfRunHooks('MessageCacheReplace', array($title, $text)); wfProfileOut(__METHOD__); }
/** * Generate code for a response. * * @param ResourceLoaderContext $context Context in which to generate a response * @param array $modules List of module objects keyed by module name * @param array $missing List of requested module names that are unregistered (optional) * @return string Response data */ public function makeModuleResponse(ResourceLoaderContext $context, array $modules, array $missing = array()) { $out = ''; $exceptions = ''; $states = array(); if (!count($modules) && !count($missing)) { return "/* This file is the Web entry point for MediaWiki's ResourceLoader:\n <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,\n no modules were requested. Max made me put this here. */"; } wfProfileIn(__METHOD__); // Pre-fetch blobs if ($context->shouldIncludeMessages()) { try { $blobs = MessageBlobStore::getInstance()->get($this, $modules, $context->getLanguage()); } catch (Exception $e) { MWExceptionHandler::logException($e); wfDebugLog('resourceloader', __METHOD__ . ": pre-fetching blobs from MessageBlobStore failed: {$e}"); $this->hasErrors = true; // Add exception to the output as a comment $exceptions .= self::formatException($e); } } else { $blobs = array(); } foreach ($missing as $name) { $states[$name] = 'missing'; } // Generate output $isRaw = false; foreach ($modules as $name => $module) { /** * @var $module ResourceLoaderModule */ wfProfileIn(__METHOD__ . '-' . $name); try { $scripts = ''; if ($context->shouldIncludeScripts()) { // If we are in debug mode, we'll want to return an array of URLs if possible // However, we can't do this if the module doesn't support it // We also can't do this if there is an only= parameter, because we have to give // the module a way to return a load.php URL without causing an infinite loop if ($context->getDebug() && !$context->getOnly() && $module->supportsURLLoading()) { $scripts = $module->getScriptURLsForDebug($context); } else { $scripts = $module->getScript($context); // rtrim() because there are usually a few line breaks // after the last ';'. A new line at EOF, a new line // added by ResourceLoaderFileModule::readScriptFiles, etc. if (is_string($scripts) && strlen($scripts) && substr(rtrim($scripts), -1) !== ';') { // Append semicolon to prevent weird bugs caused by files not // terminating their statements right (bug 27054) $scripts .= ";\n"; } } } // Styles $styles = array(); if ($context->shouldIncludeStyles()) { // Don't create empty stylesheets like array( '' => '' ) for modules // that don't *have* any stylesheets (bug 38024). $stylePairs = $module->getStyles($context); if (count($stylePairs)) { // If we are in debug mode without &only= set, we'll want to return an array of URLs // See comment near shouldIncludeScripts() for more details if ($context->getDebug() && !$context->getOnly() && $module->supportsURLLoading()) { $styles = array('url' => $module->getStyleURLsForDebug($context)); } else { // Minify CSS before embedding in mw.loader.implement call // (unless in debug mode) if (!$context->getDebug()) { foreach ($stylePairs as $media => $style) { // Can be either a string or an array of strings. if (is_array($style)) { $stylePairs[$media] = array(); foreach ($style as $cssText) { if (is_string($cssText)) { $stylePairs[$media][] = $this->filter('minify-css', $cssText); } } } elseif (is_string($style)) { $stylePairs[$media] = $this->filter('minify-css', $style); } } } // Wrap styles into @media groups as needed and flatten into a numerical array $styles = array('css' => self::makeCombinedStyles($stylePairs)); } } } // Messages $messagesBlob = isset($blobs[$name]) ? $blobs[$name] : '{}'; // Append output switch ($context->getOnly()) { case 'scripts': if (is_string($scripts)) { // Load scripts raw... $out .= $scripts; } elseif (is_array($scripts)) { // ...except when $scripts is an array of URLs $out .= self::makeLoaderImplementScript($name, $scripts, array(), array()); } break; case 'styles': // We no longer seperate into media, they are all combined now with // custom media type groups into @media .. {} sections as part of the css string. // Module returns either an empty array or a numerical array with css strings. $out .= isset($styles['css']) ? implode('', $styles['css']) : ''; break; case 'messages': $out .= self::makeMessageSetScript(new XmlJsCode($messagesBlob)); break; default: $out .= self::makeLoaderImplementScript($name, $scripts, $styles, new XmlJsCode($messagesBlob)); break; } } catch (Exception $e) { MWExceptionHandler::logException($e); wfDebugLog('resourceloader', __METHOD__ . ": generating module package failed: {$e}"); $this->hasErrors = true; // Add exception to the output as a comment $exceptions .= self::formatException($e); // Respond to client with error-state instead of module implementation $states[$name] = 'error'; unset($modules[$name]); } $isRaw |= $module->isRaw(); wfProfileOut(__METHOD__ . '-' . $name); } // Update module states if ($context->shouldIncludeScripts() && !$context->getRaw() && !$isRaw) { if (count($modules) && $context->getOnly() === 'scripts') { // Set the state of modules loaded as only scripts to ready as // they don't have an mw.loader.implement wrapper that sets the state foreach ($modules as $name => $module) { $states[$name] = 'ready'; } } // Set the state of modules we didn't respond to with mw.loader.implement if (count($states)) { $out .= self::makeLoaderStateScript($states); } } else { if (count($states)) { $exceptions .= self::makeComment('Problematic modules: ' . FormatJson::encode($states, ResourceLoader::inDebugMode())); } } if (!$context->getDebug()) { if ($context->getOnly() === 'styles') { $out = $this->filter('minify-css', $out); } else { $out = $this->filter('minify-js', $out); } } wfProfileOut(__METHOD__); return $exceptions . $out; }
/** * Load localisation data for a given language for both core and extensions * and save it to the persistent cache store and the process cache * @param string $code * @throws MWException */ public function recache($code) { global $wgExtensionMessagesFiles, $wgMessagesDirs; wfProfileIn(__METHOD__); if (!$code) { wfProfileOut(__METHOD__); throw new MWException("Invalid language code requested"); } $this->recachedLangs[$code] = true; # Initial values $initialData = array_combine(self::$allKeys, array_fill(0, count(self::$allKeys), null)); $coreData = $initialData; $deps = array(); # Load the primary localisation from the source file $data = $this->readSourceFilesAndRegisterDeps($code, $deps); if ($data === false) { wfDebug(__METHOD__ . ": no localisation file for {$code}, using fallback to en\n"); $coreData['fallback'] = 'en'; } else { wfDebug(__METHOD__ . ": got localisation for {$code} from source\n"); # Merge primary localisation foreach ($data as $key => $value) { $this->mergeItem($key, $coreData[$key], $value); } } # Fill in the fallback if it's not there already if (is_null($coreData['fallback'])) { $coreData['fallback'] = $code === 'en' ? false : 'en'; } if ($coreData['fallback'] === false) { $coreData['fallbackSequence'] = array(); } else { $coreData['fallbackSequence'] = array_map('trim', explode(',', $coreData['fallback'])); $len = count($coreData['fallbackSequence']); # Ensure that the sequence ends at en if ($coreData['fallbackSequence'][$len - 1] !== 'en') { $coreData['fallbackSequence'][] = 'en'; } } $codeSequence = array_merge(array($code), $coreData['fallbackSequence']); wfProfileIn(__METHOD__ . '-fallbacks'); # Load non-JSON localisation data for extensions $extensionData = array_combine($codeSequence, array_fill(0, count($codeSequence), $initialData)); foreach ($wgExtensionMessagesFiles as $extension => $fileName) { if (isset($wgMessagesDirs[$extension])) { # This extension has JSON message data; skip the PHP shim continue; } $data = $this->readPHPFile($fileName, 'extension'); $used = false; foreach ($data as $key => $item) { foreach ($codeSequence as $csCode) { if (isset($item[$csCode])) { $this->mergeItem($key, $extensionData[$csCode][$key], $item[$csCode]); $used = true; } } } if ($used) { $deps[] = new FileDependency($fileName); } } # Load the localisation data for each fallback, then merge it into the full array $allData = $initialData; foreach ($codeSequence as $csCode) { $csData = $initialData; # Load core messages and the extension localisations. foreach ($wgMessagesDirs as $dirs) { foreach ((array) $dirs as $dir) { $fileName = "{$dir}/{$csCode}.json"; $data = $this->readJSONFile($fileName); foreach ($data as $key => $item) { $this->mergeItem($key, $csData[$key], $item); } $deps[] = new FileDependency($fileName); } } # Merge non-JSON extension data if (isset($extensionData[$csCode])) { foreach ($extensionData[$csCode] as $key => $item) { $this->mergeItem($key, $csData[$key], $item); } } if ($csCode === $code) { # Merge core data into extension data foreach ($coreData as $key => $item) { $this->mergeItem($key, $csData[$key], $item); } } else { # Load the secondary localisation from the source file to # avoid infinite cycles on cyclic fallbacks $fbData = $this->readSourceFilesAndRegisterDeps($csCode, $deps); if ($fbData !== false) { # Only merge the keys that make sense to merge foreach (self::$allKeys as $key) { if (!isset($fbData[$key])) { continue; } if (is_null($coreData[$key]) || $this->isMergeableKey($key)) { $this->mergeItem($key, $csData[$key], $fbData[$key]); } } } } # Allow extensions an opportunity to adjust the data for this # fallback wfRunHooks('LocalisationCacheRecacheFallback', array($this, $csCode, &$csData)); # Merge the data for this fallback into the final array if ($csCode === $code) { $allData = $csData; } else { foreach (self::$allKeys as $key) { if (!isset($csData[$key])) { continue; } if (is_null($allData[$key]) || $this->isMergeableKey($key)) { $this->mergeItem($key, $allData[$key], $csData[$key]); } } } } wfProfileOut(__METHOD__ . '-fallbacks'); # Add cache dependencies for any referenced globals $deps['wgExtensionMessagesFiles'] = new GlobalDependency('wgExtensionMessagesFiles'); $deps['wgMessagesDirs'] = new GlobalDependency('wgMessagesDirs'); $deps['version'] = new ConstantDependency('LocalisationCache::VERSION'); # Add dependencies to the cache entry $allData['deps'] = $deps; # Replace spaces with underscores in namespace names $allData['namespaceNames'] = str_replace(' ', '_', $allData['namespaceNames']); # And do the same for special page aliases. $page is an array. foreach ($allData['specialPageAliases'] as &$page) { $page = str_replace(' ', '_', $page); } # Decouple the reference to prevent accidental damage unset($page); # If there were no plural rules, return an empty array if ($allData['pluralRules'] === null) { $allData['pluralRules'] = array(); } if ($allData['compiledPluralRules'] === null) { $allData['compiledPluralRules'] = array(); } # If there were no plural rule types, return an empty array if ($allData['pluralRuleTypes'] === null) { $allData['pluralRuleTypes'] = array(); } # Set the list keys $allData['list'] = array(); foreach (self::$splitKeys as $key) { $allData['list'][$key] = array_keys($allData[$key]); } # Run hooks $purgeBlobs = true; wfRunHooks('LocalisationCacheRecache', array($this, $code, &$allData, &$purgeBlobs)); if (is_null($allData['namespaceNames'])) { wfProfileOut(__METHOD__); throw new MWException(__METHOD__ . ': Localisation data failed sanity check! ' . 'Check that your languages/messages/MessagesEn.php file is intact.'); } # Set the preload key $allData['preload'] = $this->buildPreload($allData); # Save to the process cache and register the items loaded $this->data[$code] = $allData; foreach ($allData as $key => $item) { $this->loadedItems[$code][$key] = true; } # Save to the persistent cache wfProfileIn(__METHOD__ . '-write'); $this->store->startWrite($code); foreach ($allData as $key => $value) { if (in_array($key, self::$splitKeys)) { foreach ($value as $subkey => $subvalue) { $this->store->set("{$key}:{$subkey}", $subvalue); } } else { $this->store->set($key, $value); } } $this->store->finishWrite(); wfProfileOut(__METHOD__ . '-write'); # Clear out the MessageBlobStore # HACK: If using a null (i.e. disabled) storage backend, we # can't write to the MessageBlobStore either if ($purgeBlobs && !$this->store instanceof LCStoreNull) { MessageBlobStore::getInstance()->clear(); } wfProfileOut(__METHOD__); }