23 use InvalidArgumentException;
39 use Wikimedia\Assert\Assert;
57 public const OPT_NO_CHECK_CACHE = 1;
60 public const OPT_FORCE_PARSE = self::OPT_NO_CHECK_CACHE;
65 public const OPT_NO_UPDATE_CACHE = 2;
72 public const OPT_NO_AUDIENCE_CHECK = 4;
78 public const OPT_NO_CACHE = self::OPT_NO_UPDATE_CACHE | self::OPT_NO_CHECK_CACHE;
84 public const OPT_LINKS_UPDATE = 8;
90 private const CACHE_PRIMARY =
'primary';
93 private const CACHE_SECONDARY =
'secondary';
102 private $localCache = [];
105 private $revisionLookup;
108 private $revisionRenderer;
111 private $statsDataFactory;
121 private $wikiPageFactory;
124 private $titleFormatter;
144 LoggerSpi $loggerSpi,
148 $this->parserCacheFactory = $parserCacheFactory;
149 $this->revisionLookup = $revisionLookup;
150 $this->revisionRenderer = $revisionRenderer;
151 $this->statsDataFactory = $statsDataFactory;
152 $this->lbFactory = $lbFactory;
153 $this->chronologyProtector = $chronologyProtector;
154 $this->loggerSpi = $loggerSpi;
155 $this->wikiPageFactory = $wikiPageFactory;
156 $this->titleFormatter = $titleFormatter;
167 private function shouldUseCache(
171 if ( $rev && !$rev->
getId() ) {
180 $wikiPage = $this->wikiPageFactory->newFromTitle( $page );
181 if ( !$page->
exists() || !$wikiPage->getContentHandler()->isParserCacheSupported() ) {
187 return self::CACHE_PRIMARY;
195 return self::CACHE_SECONDARY;
214 $isOld = $revision && $revision->getId() !== $page->getLatest();
215 $useCache = $this->shouldUseCache( $page, $revision );
216 $primaryCache = $this->getPrimaryCache( $parserOptions );
217 $classCacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions );
219 if ( $useCache === self::CACHE_PRIMARY ) {
220 if ( isset( $this->localCache[$classCacheKey] ) && !$isOld ) {
221 return $this->localCache[$classCacheKey];
223 $output = $primaryCache->get( $page, $parserOptions );
224 } elseif ( $useCache === self::CACHE_SECONDARY && $revision ) {
225 $secondaryCache = $this->getSecondaryCache( $parserOptions );
226 $output = $secondaryCache->get( $revision, $parserOptions );
231 if ( $output && !$isOld ) {
232 $this->localCache[$classCacheKey] = $output;
236 $this->statsDataFactory->increment(
"ParserOutputAccess.Cache.$useCache.hit" );
238 $this->statsDataFactory->increment(
"ParserOutputAccess.Cache.$useCache.miss" );
241 return $output ?:
null;
272 $error = $this->checkPreconditions( $page, $revision, $options );
274 $this->statsDataFactory->increment(
"ParserOutputAccess.Case.error" );
278 $isOld = $revision && $revision->getId() !== $page->
getLatest();
280 $this->statsDataFactory->increment(
'ParserOutputAccess.Case.old' );
282 $this->statsDataFactory->increment(
'ParserOutputAccess.Case.current' );
285 if ( !( $options & self::OPT_NO_CHECK_CACHE ) ) {
286 $output = $this->getCachedParserOutput( $page, $parserOptions, $revision );
294 $revision = $revId ? $this->revisionLookup->getRevisionById( $revId ) :
null;
297 $this->statsDataFactory->increment(
"ParserOutputAccess.Status.norev" );
302 $work = $this->newPoolWorkArticleView( $page, $parserOptions, $revision, $options );
304 $status = $work->execute();
305 $output = $status->getValue();
306 Assert::postcondition( $output || !$status->isOK(),
'Worker returned invalid status' );
308 if ( $output && !$isOld ) {
309 $primaryCache = $this->getPrimaryCache( $parserOptions );
310 $classCacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions );
311 $this->localCache[$classCacheKey] = $output;
314 if ( $status->isGood() ) {
315 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.good' );
316 } elseif ( $status->isOK() ) {
317 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.ok' );
319 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.error' );
332 private function checkPreconditions(
334 ?RevisionRecord $revision =
null,
337 if ( !$page->exists() ) {
338 return Status::newFatal(
'nopagetext' );
341 if ( !( $options & self::OPT_NO_UPDATE_CACHE ) && $revision && !$revision->getId() ) {
342 throw new InvalidArgumentException(
343 'The revision does not have a known ID. Use OPT_NO_CACHE.'
347 if ( $revision && $revision->getPageId() !== $page->
getId() ) {
348 throw new InvalidArgumentException(
349 'The revision does not belong to the given page.'
353 if ( $revision && !( $options & self::OPT_NO_AUDIENCE_CHECK ) ) {
356 if ( !$revision->audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
357 return Status::newFatal(
358 'missing-revision-permission',
360 $revision->getTimestamp(),
361 $this->titleFormatter->getPrefixedDBkey( $page )
377 private function newPoolWorkArticleView(
380 RevisionRecord $revision,
383 $useCache = $this->shouldUseCache( $page, $revision );
385 switch ( $useCache ) {
386 case self::CACHE_PRIMARY:
387 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Current' );
388 $primaryCache = $this->getPrimaryCache( $parserOptions );
389 $parserCacheMetadata = $primaryCache->getMetadata( $page );
390 $cacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions,
391 $parserCacheMetadata ? $parserCacheMetadata->getUsedOptions() :
null
394 $workKey = $cacheKey .
':revid:' . $revision->getId();
401 $this->revisionRenderer,
404 $this->chronologyProtector,
406 $this->wikiPageFactory,
407 !( $options & self::OPT_NO_UPDATE_CACHE ),
408 (
bool)( $options & self::OPT_LINKS_UPDATE )
411 case self::CACHE_SECONDARY:
412 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Old' );
413 $secondaryCache = $this->getSecondaryCache( $parserOptions );
414 $workKey = $secondaryCache->makeParserOutputKey( $revision, $parserOptions );
420 $this->revisionRenderer,
425 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Uncached' );
426 $secondaryCache = $this->getSecondaryCache( $parserOptions );
427 $workKey = $secondaryCache->makeParserOutputKeyOptionalRevId( $revision, $parserOptions );
432 $this->revisionRenderer,
441 if ( $pOpts->getUseParsoid() ) {
446 return $this->parserCacheFactory->getParserCache(
447 'parsoid-' . ParserCacheFactory::DEFAULT_NAME
451 return $this->parserCacheFactory->getParserCache(
452 ParserCacheFactory::DEFAULT_NAME
456 private function getSecondaryCache(
ParserOptions $pOpts ): RevisionOutputCache {
457 if ( $pOpts->getUseParsoid() ) {
458 return $this->parserCacheFactory->getRevisionOutputCache(
459 'parsoid-' . ParserCacheFactory::DEFAULT_RCACHE_NAME
463 return $this->parserCacheFactory->getRevisionOutputCache(
464 ParserCacheFactory::DEFAULT_RCACHE_NAME
if(!defined('MW_SETUP_CALLBACK'))
Service for getting rendered output of a given page.
__construct(ParserCacheFactory $parserCacheFactory, RevisionLookup $revisionLookup, RevisionRenderer $revisionRenderer, IBufferingStatsdDataFactory $statsDataFactory, ILBFactory $lbFactory, ChronologyProtector $chronologyProtector, LoggerSpi $loggerSpi, WikiPageFactory $wikiPageFactory, TitleFormatter $titleFormatter)
getCachedParserOutput(PageRecord $page, ParserOptions $parserOptions, ?RevisionRecord $revision=null, int $options=0)
Returns the rendered output for the given page if it is present in the cache.
getParserOutput(PageRecord $page, ParserOptions $parserOptions, ?RevisionRecord $revision=null, int $options=0)
Returns the rendered output for the given page.
Service for creating WikiPage objects.
Cache for ParserOutput objects corresponding to the latest page revisions.
Set options of the Parser.
Class for dealing with PoolCounters using class members.
PoolWorkArticleView for the current revision of a page, using ParserCache.
PoolWorkArticleView for an old revision of a page, using a simple cache.
PoolCounter protected work wrapping RenderedRevision->getRevisionParserOutput.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.
MediaWiki adaptation of StatsdDataFactory that provides buffering functionality.
exists()
Checks if the page currently exists.
getId( $wikiId=self::LOCAL)
Returns the page ID.