public static function getBlackList() { $cache = \ObjectCache::getMainWANInstance(); return $cache->getWithSetCallback(wfMemcKey('flowthread', 'spamblacklist'), 60, function () { return self::buildBlacklist(); }); }
/** * Check whether feed's cache should be cleared; for changes feeds * If the feed should be purged; $timekey and $key will be removed from cache * * @param string $timekey Cache key of the timestamp of the last item * @param string $key Cache key of feed's content */ public static function checkPurge($timekey, $key) { global $wgRequest, $wgUser; $purge = $wgRequest->getVal('action') === 'purge'; if ($purge && $wgUser->isAllowed('purge')) { $cache = ObjectCache::getMainWANInstance(); $cache->delete($timekey, 1); $cache->delete($key, 1); } }
public function testGetLinkClasses() { $wanCache = ObjectCache::getMainWANInstance(); $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter(); $linkCache = new LinkCache($titleFormatter, $wanCache); $foobarTitle = new TitleValue(NS_MAIN, 'FooBar'); $redirectTitle = new TitleValue(NS_MAIN, 'Redirect'); $userTitle = new TitleValue(NS_USER, 'Someuser'); $linkCache->addGoodLinkObj(1, $foobarTitle, 10, 0); $linkCache->addGoodLinkObj(2, $redirectTitle, 10, 1); $linkCache->addGoodLinkObj(3, $userTitle, 10, 0); $linkRenderer = new LinkRenderer($titleFormatter, $linkCache); $linkRenderer->setStubThreshold(0); $this->assertEquals('', $linkRenderer->getLinkClasses($foobarTitle)); $linkRenderer->setStubThreshold(20); $this->assertEquals('stub', $linkRenderer->getLinkClasses($foobarTitle)); $linkRenderer->setStubThreshold(0); $this->assertEquals('mw-redirect', $linkRenderer->getLinkClasses($redirectTitle)); $linkRenderer->setStubThreshold(20); $this->assertEquals('', $linkRenderer->getLinkClasses($userTitle)); }
/** * Invalidates image redirect cache related to that image * * @param Title $title Title of page * @return void */ function invalidateImageRedirect(Title $title) { $key = $this->getSharedCacheKey('image_redirect', md5($title->getDBkey())); if ($key) { $this->getMasterDB()->onTransactionPreCommitOrIdle(function () use($key) { ObjectCache::getMainWANInstance()->delete($key); }); } }
/** * Get the diff table body, without header * * @return mixed (string/false) */ public function getDiffBody() { $this->mCacheHit = true; // Check if the diff should be hidden from this user if (!$this->loadRevisionData()) { return false; } elseif ($this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT, $this->getUser())) { return false; } elseif ($this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT, $this->getUser())) { return false; } // Short-circuit if ($this->mOldRev === false || $this->mOldRev && $this->mNewRev && $this->mOldRev->getID() == $this->mNewRev->getID()) { return ''; } // Cacheable? $key = false; $cache = ObjectCache::getMainWANInstance(); if ($this->mOldid && $this->mNewid) { $key = $this->getDiffBodyCacheKey(); // Try cache if (!$this->mRefreshCache) { $difftext = $cache->get($key); if ($difftext) { wfIncrStats('diff_cache.hit'); $difftext = $this->localiseLineNumbers($difftext); $difftext .= "\n<!-- diff cache key {$key} -->\n"; return $difftext; } } // don't try to load but save the result } $this->mCacheHit = false; // Loadtext is permission safe, this just clears out the diff if (!$this->loadText()) { return false; } $difftext = $this->generateContentDiffBody($this->mOldContent, $this->mNewContent); // Save to cache for 7 days if (!Hooks::run('AbortDiffCache', array(&$this))) { wfIncrStats('diff_cache.uncacheable'); } elseif ($key !== false && $difftext !== false) { wfIncrStats('diff_cache.miss'); $cache->set($key, $difftext, 7 * 86400); } else { wfIncrStats('diff_cache.uncacheable'); } // Replace line numbers with the text in the user's language if ($difftext !== false) { $difftext = $this->localiseLineNumbers($difftext); } return $difftext; }
/** * @param BagOStuff $memCached A cache instance. If none, fall back to CACHE_NONE. * @param bool $useDB * @param int $expiry Lifetime for cache. @see $mExpiry. */ function __construct($memCached, $useDB, $expiry) { global $wgUseLocalMessageCache; if (!$memCached) { $memCached = wfGetCache(CACHE_NONE); } $this->mMemc = $memCached; $this->mDisable = !$useDB; $this->mExpiry = $expiry; if ($wgUseLocalMessageCache) { $this->localCache = ObjectCache::newAccelerator(CACHE_NONE); } else { $this->localCache = wfGetCache(CACHE_NONE); } $this->wanCache = ObjectCache::getMainWANInstance(); }
/** * Returns a map of any tags used on the wiki to number of edits * tagged with them, ordered descending by the hitcount. * This does not include tags defined somewhere that have never been applied. * * Keeps a short-term cache in memory, so calling this multiple times in the * same request should be fine. * * @return array Array of string => int */ public static function tagUsageStatistics() { static $cachedStats = null; // Process cache to avoid I/O and repeated regens during holdoff if ($cachedStats !== null) { return $cachedStats; } $fname = __METHOD__; $cachedStats = ObjectCache::getMainWANInstance()->getWithSetCallback(wfMemcKey('change-tag-statistics'), 300, function ($oldValue, &$ttl, array &$setOpts) use($fname) { $dbr = wfGetDB(DB_SLAVE, 'vslow'); $setOpts += Database::getCacheSetOptions($dbr); $res = $dbr->select('change_tag', array('ct_tag', 'hitcount' => 'count(*)'), array(), $fname, array('GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC')); $out = array(); foreach ($res as $row) { $out[$row->ct_tag] = $row->hitcount; } return $out; }, array('checkKeys' => array(wfMemcKey('change-tag-statistics')), 'lockTSE' => INF)); return $cachedStats; }
/** * Additional parameters include: * - cluster : The name of an external cluster registered via LBFactory. * If not specified, the primary DB cluster for the wiki will be used. * This can be overridden with a custom cluster so that DB handles will * be retrieved via LBFactory::getExternalLB() and getConnection(). * @param array $params */ protected function __construct(array $params) { parent::__construct($params); $this->cluster = isset($params['cluster']) ? $params['cluster'] : false; $this->cache = ObjectCache::getMainWANInstance(); }
/** * Load restrictions from the page_restrictions table * * @param string $oldFashionedRestrictions Comma-separated list of page * restrictions from page table (pre 1.10) */ public function loadRestrictions($oldFashionedRestrictions = null) { if ($this->mRestrictionsLoaded) { return; } $id = $this->getArticleID(); if ($id) { $cache = ObjectCache::getMainWANInstance(); $rows = $cache->getWithSetCallback($cache->makeKey('page-restrictions', $id, $this->getLatestRevID()), $cache::TTL_DAY, function ($curValue, &$ttl, array &$setOpts) { $dbr = wfGetDB(DB_REPLICA); $setOpts += Database::getCacheSetOptions($dbr); return iterator_to_array($dbr->select('page_restrictions', ['pr_type', 'pr_expiry', 'pr_level', 'pr_cascade'], ['pr_page' => $this->getArticleID()], __METHOD__)); }); $this->loadRestrictionsFromRows($rows, $oldFashionedRestrictions); } else { $title_protection = $this->getTitleProtection(); if ($title_protection) { $now = wfTimestampNow(); $expiry = wfGetDB(DB_REPLICA)->decodeExpiry($title_protection['expiry']); if (!$expiry || $expiry > $now) { // Apply the restrictions $this->mRestrictionsExpiry['create'] = $expiry; $this->mRestrictions['create'] = explode(',', trim($title_protection['permission'])); } else { // Get rid of the old restrictions $this->mTitleProtection = false; } } else { $this->mRestrictionsExpiry['create'] = 'infinity'; } $this->mRestrictionsLoaded = true; } }
private static function getBlackList() { $cache = \ObjectCache::getMainWANInstance(); return $cache->getWithSetCallback(wfMemcKey('flowthread', 'spamblacklist'), 60, function () { $source = wfMessage('flowthread-blacklist')->inContentLanguage(); if ($source->isDisabled()) { return array(); } $lines = explode("\n", $source->text()); return self::parseLines($lines); }); }
/** * Get an array of extended metadata. (See the imageinfo API for format.) * * @param File $file File to use * @return array [<property name> => ['value' => <value>]], or [] on error * @since 1.23 */ public function fetchExtendedMetadata(File $file) { $cache = ObjectCache::getMainWANInstance(); // If revision deleted, exit immediately if ($file->isDeleted(File::DELETED_FILE)) { return []; } $cacheKey = wfMemcKey('getExtendedMetadata', $this->getLanguage()->getCode(), (int) $this->singleLang, $file->getSha1()); $cachedValue = $cache->get($cacheKey); if ($cachedValue && Hooks::run('ValidateExtendedMetadataCache', [$cachedValue['timestamp'], $file])) { $extendedMetadata = $cachedValue['data']; } else { $maxCacheTime = $file instanceof ForeignAPIFile ? 60 * 60 * 12 : 60 * 60 * 24 * 30; $fileMetadata = $this->getExtendedMetadataFromFile($file); $extendedMetadata = $this->getExtendedMetadataFromHook($file, $fileMetadata, $maxCacheTime); if ($this->singleLang) { $this->resolveMultilangMetadata($extendedMetadata); } $this->discardMultipleValues($extendedMetadata); // Make sure the metadata won't break the API when an XML format is used. // This is an API-specific function so it would be cleaner to call it from // outside fetchExtendedMetadata, but this way we don't need to redo the // computation on a cache hit. $this->sanitizeArrayForAPI($extendedMetadata); $valueToCache = ['data' => $extendedMetadata, 'timestamp' => wfTimestampNow()]; $cache->set($cacheKey, $valueToCache, $maxCacheTime); } return $extendedMetadata; }
/** * Lazy-load the revision's text. * Currently hardcoded to the 'text' table storage engine. * * @return string|bool The revision's text, or false on failure */ protected function loadText() { // Caching may be beneficial for massive use of external storage global $wgRevisionCacheExpiry; $cache = ObjectCache::getMainWANInstance(); $textId = $this->getTextId(); $key = wfMemcKey('revisiontext', 'textid', $textId); if ($wgRevisionCacheExpiry) { $text = $cache->get($key); if (is_string($text)) { wfDebug(__METHOD__ . ": got id {$textId} from cache\n"); return $text; } } // If we kept data for lazy extraction, use it now... if ($this->mTextRow !== null) { $row = $this->mTextRow; $this->mTextRow = null; } else { $row = null; } if (!$row) { // Text data is immutable; check slaves first. $dbr = wfGetDB(DB_SLAVE); $row = $dbr->selectRow('text', array('old_text', 'old_flags'), array('old_id' => $textId), __METHOD__); } // Fallback to the master in case of slave lag. Also use FOR UPDATE if it was // used to fetch this revision to avoid missing the row due to REPEATABLE-READ. $forUpdate = $this->mQueryFlags & self::READ_LOCKING == self::READ_LOCKING; if (!$row && ($forUpdate || wfGetLB()->getServerCount() > 1)) { $dbw = wfGetDB(DB_MASTER); $row = $dbw->selectRow('text', array('old_text', 'old_flags'), array('old_id' => $textId), __METHOD__, $forUpdate ? array('FOR UPDATE') : array()); } if (!$row) { wfDebugLog('Revision', "No text row with ID '{$textId}' (revision {$this->getId()})."); } $text = self::getRevisionText($row); if ($row && $text === false) { wfDebugLog('Revision', "No blob for text row '{$textId}' (revision {$this->getId()})."); } # No negative caching -- negative hits on text rows may be due to corrupted slave servers if ($wgRevisionCacheExpiry && $text !== false) { $cache->set($key, $text, $wgRevisionCacheExpiry); } return $text; }
/** * Build an array that represents the sidebar(s), the navigation bar among them. * * BaseTemplate::getSidebar can be used to simplify the format and id generation in new skins. * * The format of the returned array is array( heading => content, ... ), where: * - heading is the heading of a navigation portlet. It is either: * - magic string to be handled by the skins ('SEARCH' / 'LANGUAGES' / 'TOOLBOX' / ...) * - a message name (e.g. 'navigation'), the message should be HTML-escaped by the skin * - plain text, which should be HTML-escaped by the skin * - content is the contents of the portlet. It is either: * - HTML text (<ul><li>...</li>...</ul>) * - array of link data in a format accepted by BaseTemplate::makeListItem() * - (for a magic string as a key, any value) * * Note that extensions can control the sidebar contents using the SkinBuildSidebar hook * and can technically insert anything in here; skin creators are expected to handle * values described above. * * @return array */ function buildSidebar() { global $wgEnableSidebarCache, $wgSidebarCacheExpiry; $cache = ObjectCache::getMainWANInstance(); $key = wfMemcKey('sidebar', $this->getLanguage()->getCode()); if ($wgEnableSidebarCache) { $cachedsidebar = $cache->get($key); if ($cachedsidebar) { Hooks::run('SidebarBeforeOutput', array($this, &$cachedsidebar)); return $cachedsidebar; } } $bar = array(); $this->addToSidebar($bar, 'sidebar'); Hooks::run('SkinBuildSidebar', array($this, &$bar)); if ($wgEnableSidebarCache) { $cache->set($key, $bar, $wgSidebarCacheExpiry); } Hooks::run('SidebarBeforeOutput', array($this, &$bar)); return $bar; }
/** * Build an array that represents the sidebar(s), the navigation bar among them. * * BaseTemplate::getSidebar can be used to simplify the format and id generation in new skins. * * The format of the returned array is array( heading => content, ... ), where: * - heading is the heading of a navigation portlet. It is either: * - magic string to be handled by the skins ('SEARCH' / 'LANGUAGES' / 'TOOLBOX' / ...) * - a message name (e.g. 'navigation'), the message should be HTML-escaped by the skin * - plain text, which should be HTML-escaped by the skin * - content is the contents of the portlet. It is either: * - HTML text (<ul><li>...</li>...</ul>) * - array of link data in a format accepted by BaseTemplate::makeListItem() * - (for a magic string as a key, any value) * * Note that extensions can control the sidebar contents using the SkinBuildSidebar hook * and can technically insert anything in here; skin creators are expected to handle * values described above. * * @return array */ function buildSidebar() { global $wgEnableSidebarCache, $wgSidebarCacheExpiry; $that = $this; $callback = function () use($that) { $bar = array(); $that->addToSidebar($bar, 'sidebar'); Hooks::run('SkinBuildSidebar', array($that, &$bar)); return $bar; }; if ($wgEnableSidebarCache) { $cache = ObjectCache::getMainWANInstance(); $sidebar = $cache->getWithSetCallback($cache->makeKey('sidebar', $this->getLanguage()->getCode()), $wgSidebarCacheExpiry, $callback, array('lockTSE' => 30)); } else { $sidebar = $callback(); } // Apply post-processing to the cached value Hooks::run('SidebarBeforeOutput', array($this, &$sidebar)); return $sidebar; }
/** * @param BagOStuff $memCached A cache instance. If none, fall back to CACHE_NONE. * @param bool $useDB * @param int $expiry Lifetime for cache. @see $mExpiry. */ function __construct($memCached, $useDB, $expiry) { global $wgUseLocalMessageCache; if (!$memCached) { $memCached = wfGetCache(CACHE_NONE); } $this->mMemc = $memCached; $this->mDisable = !$useDB; $this->mExpiry = $expiry; if ($wgUseLocalMessageCache) { $this->localCache = MediaWikiServices::getInstance()->getLocalServerObjectCache(); } else { $this->localCache = new EmptyBagOStuff(); } $this->wanCache = ObjectCache::getMainWANInstance(); }
/** * HTTP GET request to a mediawiki API (with caching) * @param string $target Used in cache key creation, mostly * @param array $query The query parameters for the API request * @param int $cacheTTL Time to live for the memcached caching * @return string|null */ public function httpGetCached($target, $query, $cacheTTL = 3600) { if ($this->mApiBase) { $url = wfAppendQuery($this->mApiBase, $query); } else { $url = $this->makeUrl($query, 'api'); } $cache = ObjectCache::getMainWANInstance(); return $cache->getWithSetCallback($this->getLocalCacheKey(get_class($this), $target, md5($url)), $cacheTTL, function ($curValue, &$ttl) use($url, $cache) { $html = self::httpGet($url, 'default', [], $mtime); if ($html !== false) { $ttl = $mtime ? $cache->adaptiveTTL($mtime, $ttl) : $ttl; } else { $ttl = $cache->adaptiveTTL($mtime, $ttl); $html = null; // caches negatives } return $html; }, ['pcTTL' => $cache::TTL_PROC_LONG]); }
/** * @param ResourceLoader $rl * @param LoggerInterface $logger */ public function __construct(ResourceLoader $rl = null, LoggerInterface $logger = null) { $this->resourceloader = $rl; $this->logger = $logger ?: new NullLogger(); $this->wanCache = ObjectCache::getMainWANInstance(); }
/** * @param string $name * @return mixed */ private function getCachedConfigVar($name) { // @TODO: cleanup this whole method with a proper config system if ($this->wiki === wfWikiID()) { return $GLOBALS[$name]; // common case } else { $wiki = $this->wiki; $cache = ObjectCache::getMainWANInstance(); $value = $cache->getWithSetCallback($cache->makeGlobalKey('jobqueue', 'configvalue', $wiki, $name), $cache::TTL_DAY + mt_rand(0, $cache::TTL_DAY), function () use($wiki, $name) { global $wgConf; return array('v' => $wgConf->getConfig($wiki, $name)); }, array('pcTTL' => 30)); return $value['v']; } }
/** * @param array $options */ function purgeThumbnails($options = array()) { $key = $this->repo->getLocalCacheKey('ForeignAPIRepo', 'ThumbUrl', $this->getName()); ObjectCache::getMainWANInstance()->delete($key); $files = $this->getThumbnails(); // Give media handler a chance to filter the purge list $handler = $this->getHandler(); if ($handler) { $handler->filterThumbnailPurgeList($files, $options); } $dir = $this->getThumbPath($this->getName()); $purgeList = array(); foreach ($files as $file) { $purgeList[] = "{$dir}{$file}"; } # Delete the thumbnails $this->repo->quickPurgeBatch($purgeList); # Clear out the thumbnail directory if empty $this->repo->quickCleanDir($dir); }
/** * Returns page information in an easily-manipulated format. Array keys are used so extensions * may add additional information in arbitrary positions. Array values are arrays with one * element to be rendered as a header, arrays with two elements to be rendered as a table row. * * @return array */ protected function pageInfo() { global $wgContLang; $user = $this->getUser(); $lang = $this->getLanguage(); $title = $this->getTitle(); $id = $title->getArticleID(); $config = $this->context->getConfig(); $cache = ObjectCache::getMainWANInstance(); $memcKey = wfMemcKey('infoaction', sha1($title->getPrefixedText()), $this->page->getLatest()); $pageCounts = $cache->get($memcKey); $version = isset($pageCounts['cacheversion']) ? $pageCounts['cacheversion'] : false; if ($pageCounts === false || $version !== self::CACHE_VERSION) { // Get page information that would be too "expensive" to retrieve by normal means $pageCounts = $this->pageCounts($title); $pageCounts['cacheversion'] = self::CACHE_VERSION; $cache->set($memcKey, $pageCounts); } // Get page properties $dbr = wfGetDB(DB_SLAVE); $result = $dbr->select('page_props', array('pp_propname', 'pp_value'), array('pp_page' => $id), __METHOD__); $pageProperties = array(); foreach ($result as $row) { $pageProperties[$row->pp_propname] = $row->pp_value; } // Basic information $pageInfo = array(); $pageInfo['header-basic'] = array(); // Display title $displayTitle = $title->getPrefixedText(); if (isset($pageProperties['displaytitle'])) { $displayTitle = $pageProperties['displaytitle']; } $pageInfo['header-basic'][] = array($this->msg('pageinfo-display-title'), $displayTitle); // Is it a redirect? If so, where to? if ($title->isRedirect()) { $pageInfo['header-basic'][] = array($this->msg('pageinfo-redirectsto'), Linker::link($this->page->getRedirectTarget()) . $this->msg('word-separator')->escaped() . $this->msg('parentheses')->rawParams(Linker::link($this->page->getRedirectTarget(), $this->msg('pageinfo-redirectsto-info')->escaped(), array(), array('action' => 'info')))->escaped()); } // Default sort key $sortKey = $title->getCategorySortkey(); if (isset($pageProperties['defaultsort'])) { $sortKey = $pageProperties['defaultsort']; } $sortKey = htmlspecialchars($sortKey); $pageInfo['header-basic'][] = array($this->msg('pageinfo-default-sort'), $sortKey); // Page length (in bytes) $pageInfo['header-basic'][] = array($this->msg('pageinfo-length'), $lang->formatNum($title->getLength())); // Page ID (number not localised, as it's a database ID) $pageInfo['header-basic'][] = array($this->msg('pageinfo-article-id'), $id); // Language in which the page content is (supposed to be) written $pageLang = $title->getPageLanguage()->getCode(); if ($config->get('PageLanguageUseDB') && $this->getTitle()->userCan('pagelang', $this->getUser())) { // Link to Special:PageLanguage with pre-filled page title if user has permissions $titleObj = SpecialPage::getTitleFor('PageLanguage', $title->getPrefixedText()); $langDisp = Linker::link($titleObj, $this->msg('pageinfo-language')->escaped()); } else { // Display just the message $langDisp = $this->msg('pageinfo-language')->escaped(); } $pageInfo['header-basic'][] = array($langDisp, Language::fetchLanguageName($pageLang, $lang->getCode()) . ' ' . $this->msg('parentheses', $pageLang)->escaped()); // Content model of the page $pageInfo['header-basic'][] = array($this->msg('pageinfo-content-model'), htmlspecialchars(ContentHandler::getLocalizedName($title->getContentModel()))); // Search engine status $pOutput = new ParserOutput(); if (isset($pageProperties['noindex'])) { $pOutput->setIndexPolicy('noindex'); } if (isset($pageProperties['index'])) { $pOutput->setIndexPolicy('index'); } // Use robot policy logic $policy = $this->page->getRobotPolicy('view', $pOutput); $pageInfo['header-basic'][] = array($this->msg('pageinfo-robot-policy'), $this->msg("pageinfo-robot-{$policy['index']}")); $unwatchedPageThreshold = $config->get('UnwatchedPageThreshold'); if ($user->isAllowed('unwatchedpages') || $unwatchedPageThreshold !== false && $pageCounts['watchers'] >= $unwatchedPageThreshold) { // Number of page watchers $pageInfo['header-basic'][] = array($this->msg('pageinfo-watchers'), $lang->formatNum($pageCounts['watchers'])); if ($config->get('ShowUpdatedMarker') && isset($pageCounts['visitingWatchers'])) { $minToDisclose = $config->get('UnwatchedPageSecret'); if ($pageCounts['visitingWatchers'] > $minToDisclose || $user->isAllowed('unwatchedpages')) { $pageInfo['header-basic'][] = array($this->msg('pageinfo-visiting-watchers'), $lang->formatNum($pageCounts['visitingWatchers'])); } else { $pageInfo['header-basic'][] = array($this->msg('pageinfo-visiting-watchers'), $this->msg('pageinfo-few-visiting-watchers')); } } } elseif ($unwatchedPageThreshold !== false) { $pageInfo['header-basic'][] = array($this->msg('pageinfo-watchers'), $this->msg('pageinfo-few-watchers')->numParams($unwatchedPageThreshold)); } // Redirects to this page $whatLinksHere = SpecialPage::getTitleFor('Whatlinkshere', $title->getPrefixedText()); $pageInfo['header-basic'][] = array(Linker::link($whatLinksHere, $this->msg('pageinfo-redirects-name')->escaped(), array(), array('hidelinks' => 1, 'hidetrans' => 1, 'hideimages' => $title->getNamespace() == NS_FILE)), $this->msg('pageinfo-redirects-value')->numParams(count($title->getRedirectsHere()))); // Is it counted as a content page? if ($this->page->isCountable()) { $pageInfo['header-basic'][] = array($this->msg('pageinfo-contentpage'), $this->msg('pageinfo-contentpage-yes')); } // Subpages of this page, if subpages are enabled for the current NS if (MWNamespace::hasSubpages($title->getNamespace())) { $prefixIndex = SpecialPage::getTitleFor('Prefixindex', $title->getPrefixedText() . '/'); $pageInfo['header-basic'][] = array(Linker::link($prefixIndex, $this->msg('pageinfo-subpages-name')->escaped()), $this->msg('pageinfo-subpages-value')->numParams($pageCounts['subpages']['total'], $pageCounts['subpages']['redirects'], $pageCounts['subpages']['nonredirects'])); } if ($title->inNamespace(NS_CATEGORY)) { $category = Category::newFromTitle($title); // $allCount is the total number of cat members, // not the count of how many members are normal pages. $allCount = (int) $category->getPageCount(); $subcatCount = (int) $category->getSubcatCount(); $fileCount = (int) $category->getFileCount(); $pagesCount = $allCount - $subcatCount - $fileCount; $pageInfo['category-info'] = array(array($this->msg('pageinfo-category-total'), $lang->formatNum($allCount)), array($this->msg('pageinfo-category-pages'), $lang->formatNum($pagesCount)), array($this->msg('pageinfo-category-subcats'), $lang->formatNum($subcatCount)), array($this->msg('pageinfo-category-files'), $lang->formatNum($fileCount))); } // Page protection $pageInfo['header-restrictions'] = array(); // Is this page affected by the cascading protection of something which includes it? if ($title->isCascadeProtected()) { $cascadingFrom = ''; $sources = $title->getCascadeProtectionSources(); // Array deferencing is in PHP 5.4 :( foreach ($sources[0] as $sourceTitle) { $cascadingFrom .= Html::rawElement('li', array(), Linker::linkKnown($sourceTitle)); } $cascadingFrom = Html::rawElement('ul', array(), $cascadingFrom); $pageInfo['header-restrictions'][] = array($this->msg('pageinfo-protect-cascading-from'), $cascadingFrom); } // Is out protection set to cascade to other pages? if ($title->areRestrictionsCascading()) { $pageInfo['header-restrictions'][] = array($this->msg('pageinfo-protect-cascading'), $this->msg('pageinfo-protect-cascading-yes')); } // Page protection foreach ($title->getRestrictionTypes() as $restrictionType) { $protectionLevel = implode(', ', $title->getRestrictions($restrictionType)); if ($protectionLevel == '') { // Allow all users $message = $this->msg('protect-default')->escaped(); } else { // Administrators only // Messages: protect-level-autoconfirmed, protect-level-sysop $message = $this->msg("protect-level-{$protectionLevel}"); if ($message->isDisabled()) { // Require "$1" permission $message = $this->msg("protect-fallback", $protectionLevel)->parse(); } else { $message = $message->escaped(); } } $expiry = $title->getRestrictionExpiry($restrictionType); $formattedexpiry = $this->msg('parentheses', $this->getLanguage()->formatExpiry($expiry))->escaped(); $message .= $this->msg('word-separator')->escaped() . $formattedexpiry; // Messages: restriction-edit, restriction-move, restriction-create, // restriction-upload $pageInfo['header-restrictions'][] = array($this->msg("restriction-{$restrictionType}"), $message); } if (!$this->page->exists()) { return $pageInfo; } // Edit history $pageInfo['header-edits'] = array(); $firstRev = $this->page->getOldestRevision(); $lastRev = $this->page->getRevision(); $batch = new LinkBatch(); if ($firstRev) { $firstRevUser = $firstRev->getUserText(Revision::FOR_THIS_USER); if ($firstRevUser !== '') { $batch->add(NS_USER, $firstRevUser); $batch->add(NS_USER_TALK, $firstRevUser); } } if ($lastRev) { $lastRevUser = $lastRev->getUserText(Revision::FOR_THIS_USER); if ($lastRevUser !== '') { $batch->add(NS_USER, $lastRevUser); $batch->add(NS_USER_TALK, $lastRevUser); } } $batch->execute(); if ($firstRev) { // Page creator $pageInfo['header-edits'][] = array($this->msg('pageinfo-firstuser'), Linker::revUserTools($firstRev)); // Date of page creation $pageInfo['header-edits'][] = array($this->msg('pageinfo-firsttime'), Linker::linkKnown($title, htmlspecialchars($lang->userTimeAndDate($firstRev->getTimestamp(), $user)), array(), array('oldid' => $firstRev->getId()))); } if ($lastRev) { // Latest editor $pageInfo['header-edits'][] = array($this->msg('pageinfo-lastuser'), Linker::revUserTools($lastRev)); // Date of latest edit $pageInfo['header-edits'][] = array($this->msg('pageinfo-lasttime'), Linker::linkKnown($title, htmlspecialchars($lang->userTimeAndDate($this->page->getTimestamp(), $user)), array(), array('oldid' => $this->page->getLatest()))); } // Total number of edits $pageInfo['header-edits'][] = array($this->msg('pageinfo-edits'), $lang->formatNum($pageCounts['edits'])); // Total number of distinct authors if ($pageCounts['authors'] > 0) { $pageInfo['header-edits'][] = array($this->msg('pageinfo-authors'), $lang->formatNum($pageCounts['authors'])); } // Recent number of edits (within past 30 days) $pageInfo['header-edits'][] = array($this->msg('pageinfo-recent-edits', $lang->formatDuration($config->get('RCMaxAge'))), $lang->formatNum($pageCounts['recent_edits'])); // Recent number of distinct authors $pageInfo['header-edits'][] = array($this->msg('pageinfo-recent-authors'), $lang->formatNum($pageCounts['recent_authors'])); // Array of MagicWord objects $magicWords = MagicWord::getDoubleUnderscoreArray(); // Array of magic word IDs $wordIDs = $magicWords->names; // Array of IDs => localized magic words $localizedWords = $wgContLang->getMagicWords(); $listItems = array(); foreach ($pageProperties as $property => $value) { if (in_array($property, $wordIDs)) { $listItems[] = Html::element('li', array(), $localizedWords[$property][1]); } } $localizedList = Html::rawElement('ul', array(), implode('', $listItems)); $hiddenCategories = $this->page->getHiddenCategories(); if (count($listItems) > 0 || count($hiddenCategories) > 0 || $pageCounts['transclusion']['from'] > 0 || $pageCounts['transclusion']['to'] > 0) { $options = array('LIMIT' => $config->get('PageInfoTransclusionLimit')); $transcludedTemplates = $title->getTemplateLinksFrom($options); if ($config->get('MiserMode')) { $transcludedTargets = array(); } else { $transcludedTargets = $title->getTemplateLinksTo($options); } // Page properties $pageInfo['header-properties'] = array(); // Magic words if (count($listItems) > 0) { $pageInfo['header-properties'][] = array($this->msg('pageinfo-magic-words')->numParams(count($listItems)), $localizedList); } // Hidden categories if (count($hiddenCategories) > 0) { $pageInfo['header-properties'][] = array($this->msg('pageinfo-hidden-categories')->numParams(count($hiddenCategories)), Linker::formatHiddenCategories($hiddenCategories)); } // Transcluded templates if ($pageCounts['transclusion']['from'] > 0) { if ($pageCounts['transclusion']['from'] > count($transcludedTemplates)) { $more = $this->msg('morenotlisted')->escaped(); } else { $more = null; } $pageInfo['header-properties'][] = array($this->msg('pageinfo-templates')->numParams($pageCounts['transclusion']['from']), Linker::formatTemplates($transcludedTemplates, false, false, $more)); } if (!$config->get('MiserMode') && $pageCounts['transclusion']['to'] > 0) { if ($pageCounts['transclusion']['to'] > count($transcludedTargets)) { $more = Linker::link($whatLinksHere, $this->msg('moredotdotdot')->escaped(), array(), array('hidelinks' => 1, 'hideredirs' => 1)); } else { $more = null; } $pageInfo['header-properties'][] = array($this->msg('pageinfo-transclusions')->numParams($pageCounts['transclusion']['to']), Linker::formatTemplates($transcludedTargets, false, false, $more)); } } return $pageInfo; }
/** * Get the user touched timestamp * * Use this value only to validate caches via inequalities * such as in the case of HTTP If-Modified-Since response logic * * @return string TS_MW Timestamp */ public function getTouched() { $this->load(); if ($this->mId) { if ($this->mQuickTouched === null) { $key = wfMemcKey('user-quicktouched', 'id', $this->mId); $cache = ObjectCache::getMainWANInstance(); $this->mQuickTouched = wfTimestamp(TS_MW, $cache->getCheckKeyTime($key)); } return max($this->mTouched, $this->mQuickTouched); } return $this->mTouched; }
/** * HTTP GET request to a mediawiki API (with caching) * @param string $target Used in cache key creation, mostly * @param array $query The query parameters for the API request * @param int $cacheTTL Time to live for the memcached caching * @return null */ public function httpGetCached($target, $query, $cacheTTL = 3600) { if ($this->mApiBase) { $url = wfAppendQuery($this->mApiBase, $query); } else { $url = $this->makeUrl($query, 'api'); } if (!isset($this->mQueryCache[$url])) { $data = ObjectCache::getMainWANInstance()->getWithSetCallback($this->getLocalCacheKey(get_class($this), $target, md5($url)), $cacheTTL, function () use($url) { return ForeignAPIRepo::httpGet($url); }); if (!$data) { return null; } if (count($this->mQueryCache) > 100) { // Keep the cache from growing infinitely $this->mQueryCache = []; } $this->mQueryCache[$url] = $data; } return $this->mQueryCache[$url]; }
/** * Returns the page's content model id (see the CONTENT_MODEL_XXX constants). * * Will use the revisions actual content model if the page exists, * and the page's default if the page doesn't exist yet. * * @return string * * @since 1.21 */ public function getContentModel() { if ($this->exists()) { $cache = ObjectCache::getMainWANInstance(); return $cache->getWithSetCallback($cache->makeKey('page', 'content-model', $this->getLatest()), $cache::TTL_MONTH, function () { $rev = $this->getRevision(); if ($rev) { // Look at the revision's actual content model return $rev->getContentModel(); } else { $title = $this->mTitle->getPrefixedDBkey(); wfWarn("Page {$title} exists but has no (visible) revisions!"); return $this->mTitle->getContentModel(); } }); } // use the default model for this page return $this->mTitle->getContentModel(); }
/** * Purge the file object/metadata cache */ function invalidateCache() { $key = $this->getCacheKey(); if (!$key) { return; } ObjectCache::getMainWANInstance()->delete($key); }
/** * Get the HTML text of the description page, if available * * @param bool|Language $lang Optional language to fetch description in * @return string */ function getDescriptionText($lang = false) { global $wgLang; if (!$this->repo || !$this->repo->fetchDescription) { return false; } $lang = $lang ?: $wgLang; $renderUrl = $this->repo->getDescriptionRenderUrl($this->getName(), $lang->getCode()); if ($renderUrl) { $cache = ObjectCache::getMainWANInstance(); $key = null; if ($this->repo->descriptionCacheExpiry > 0) { wfDebug("Attempting to get the description from cache..."); $key = $this->repo->getLocalCacheKey('RemoteFileDescription', 'url', $lang->getCode(), $this->getName()); $obj = $cache->get($key); if ($obj) { wfDebug("success!\n"); return $obj; } wfDebug("miss\n"); } wfDebug("Fetching shared description from {$renderUrl}\n"); $res = Http::get($renderUrl, array(), __METHOD__); if ($res && $key) { $cache->set($key, $res, $this->repo->descriptionCacheExpiry); } return $res; } else { return false; } }
/** * Returns a map of any tags used on the wiki to number of edits * tagged with them, ordered descending by the hitcount. * This does not include tags defined somewhere that have never been applied. * * Keeps a short-term cache in memory, so calling this multiple times in the * same request should be fine. * * @return array Array of string => int */ public static function tagUsageStatistics() { $fname = __METHOD__; return ObjectCache::getMainWANInstance()->getWithSetCallback(wfMemcKey('change-tag-statistics'), 300, function ($oldValue, &$ttl, array &$setOpts) use($fname) { $dbr = wfGetDB(DB_SLAVE, 'vslow'); $setOpts += Database::getCacheSetOptions($dbr); $res = $dbr->select('change_tag', array('ct_tag', 'hitcount' => 'count(*)'), array(), $fname, array('GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC')); $out = array(); foreach ($res as $row) { $out[$row->ct_tag] = $row->hitcount; } return $out; }, array('checkKeys' => array(wfMemcKey('change-tag-statistics')), 'lockTSE' => INF, 'pcTTL' => 30)); }
/** * Generate help for the specified modules * * Help is placed into the OutputPage object returned by * $context->getOutput(). * * Recognized options include: * - headerlevel: (int) Header tag level * - nolead: (bool) Skip the inclusion of api-help-lead * - noheader: (bool) Skip the inclusion of the top-level section headers * - submodules: (bool) Include help for submodules of the current module * - recursivesubmodules: (bool) Include help for submodules recursively * - helptitle: (string) Title to link for additional modules' help. Should contain $1. * - toc: (bool) Include a table of contents * * @param IContextSource $context * @param ApiBase[]|ApiBase $modules * @param array $options Formatting options (described above) * @return string */ public static function getHelp(IContextSource $context, $modules, array $options) { global $wgContLang; if (!is_array($modules)) { $modules = array($modules); } $out = $context->getOutput(); $out->addModuleStyles('mediawiki.hlist'); $out->addModuleStyles('mediawiki.apihelp'); if (!empty($options['toc'])) { $out->addModules('mediawiki.toc'); } $out->setPageTitle($context->msg('api-help-title')); $cache = ObjectCache::getMainWANInstance(); $cacheKey = null; if (count($modules) == 1 && $modules[0] instanceof ApiMain && $options['recursivesubmodules'] && $context->getLanguage() === $wgContLang) { $cacheHelpTimeout = $context->getConfig()->get('APICacheHelpTimeout'); if ($cacheHelpTimeout > 0) { // Get help text from cache if present $cacheKey = wfMemcKey('apihelp', $modules[0]->getModulePath(), (int) (!empty($options['toc'])), str_replace(' ', '_', SpecialVersion::getVersion('nodb'))); $cached = $cache->get($cacheKey); if ($cached) { $out->addHTML($cached); return; } } } if ($out->getHTML() !== '') { // Don't save to cache, there's someone else's content in the page // already $cacheKey = null; } $options['recursivesubmodules'] = !empty($options['recursivesubmodules']); $options['submodules'] = $options['recursivesubmodules'] || !empty($options['submodules']); // Prepend lead if (empty($options['nolead'])) { $msg = $context->msg('api-help-lead'); if (!$msg->isDisabled()) { $out->addHTML($msg->parseAsBlock()); } } $haveModules = array(); $html = self::getHelpInternal($context, $modules, $options, $haveModules); if (!empty($options['toc']) && $haveModules) { $out->addHTML(Linker::generateTOC($haveModules, $context->getLanguage())); } $out->addHTML($html); $helptitle = isset($options['helptitle']) ? $options['helptitle'] : null; $html = self::fixHelpLinks($out->getHTML(), $helptitle, $haveModules); $out->clearHTML(); $out->addHTML($html); if ($cacheKey !== null) { $cache->set($cacheKey, $out->getHTML(), $cacheHelpTimeout); } }
/** * Load the interwiki, trying first memcached then the DB * * @param string $prefix The interwiki prefix * @return Interwiki|bool Interwiki if $prefix is valid, otherwise false */ protected static function load($prefix) { global $wgInterwikiExpiry; $iwData = array(); if (!Hooks::run('InterwikiLoadPrefix', array($prefix, &$iwData))) { return Interwiki::loadFromArray($iwData); } $cache = ObjectCache::getMainWANInstance(); if (!$iwData) { $key = wfMemcKey('interwiki', $prefix); $iwData = $cache->get($key); if ($iwData === '!NONEXISTENT') { // negative cache hit return false; } } // is_array is hack for old keys if ($iwData && is_array($iwData)) { $iw = Interwiki::loadFromArray($iwData); if ($iw) { return $iw; } } $db = wfGetDB(DB_SLAVE); $row = $db->fetchRow($db->select('interwiki', self::selectFields(), array('iw_prefix' => $prefix), __METHOD__)); $iw = Interwiki::loadFromArray($row); if ($iw) { $mc = array('iw_url' => $iw->mURL, 'iw_api' => $iw->mAPI, 'iw_local' => $iw->mLocal, 'iw_trans' => $iw->mTrans); $cache->set($key, $mc, $wgInterwikiExpiry); return $iw; } // negative cache hit $cache->set($key, '!NONEXISTENT', $wgInterwikiExpiry); return false; }
/** * Get the backend object with a given name * * @param string $name * @return FileBackend * @throws FileBackendException */ public function get($name) { if (!isset($this->backends[$name])) { throw new FileBackendException("No backend defined with the name `{$name}`."); } // Lazy-load the actual backend instance if (!isset($this->backends[$name]['instance'])) { $class = $this->backends[$name]['class']; $config = $this->backends[$name]['config']; $config['wikiId'] = isset($config['wikiId']) ? $config['wikiId'] : wfWikiID(); // e.g. "my_wiki-en_" $config['lockManager'] = LockManagerGroup::singleton($config['wikiId'])->get($config['lockManager']); $config['fileJournal'] = isset($config['fileJournal']) ? FileJournal::factory($config['fileJournal'], $name) : FileJournal::factory(array('class' => 'NullFileJournal'), $name); $config['wanCache'] = ObjectCache::getMainWANInstance(); $this->backends[$name]['instance'] = new $class($config); } return $this->backends[$name]['instance']; }
/** * Purge the file object/metadata cache */ public function invalidateCache() { $key = $this->getCacheKey(); if (!$key) { return; } $this->repo->getMasterDB()->onTransactionPreCommitOrIdle(function () use($key) { ObjectCache::getMainWANInstance()->delete($key); }); }