64 public const OPT_NO_CHECK_CACHE = 1;
67 public const OPT_FORCE_PARSE = self::OPT_NO_CHECK_CACHE;
72 public const OPT_NO_UPDATE_CACHE = 2;
79 public const OPT_NO_AUDIENCE_CHECK = 4;
85 public const OPT_NO_CACHE = self::OPT_NO_UPDATE_CACHE | self::OPT_NO_CHECK_CACHE;
91 public const OPT_LINKS_UPDATE = 8;
111 private const CACHE_PRIMARY =
'primary';
114 private const CACHE_SECONDARY =
'secondary';
126 private $revisionLookup;
129 private $revisionRenderer;
132 private $statsDataFactory;
142 private $wikiPageFactory;
145 private $titleFormatter;
165 LoggerSpi $loggerSpi,
169 $this->parserCacheFactory = $parserCacheFactory;
170 $this->revisionLookup = $revisionLookup;
171 $this->revisionRenderer = $revisionRenderer;
172 $this->statsDataFactory = $statsDataFactory;
173 $this->lbFactory = $lbFactory;
174 $this->chronologyProtector = $chronologyProtector;
175 $this->loggerSpi = $loggerSpi;
176 $this->wikiPageFactory = $wikiPageFactory;
177 $this->titleFormatter = $titleFormatter;
190 private function shouldUseCache(
194 if ( $rev && !$rev->
getId() ) {
203 $wikiPage = $this->wikiPageFactory->newFromTitle( $page );
204 if ( !$page->
exists() || !$wikiPage->getContentHandler()->isParserCacheSupported() ) {
210 return self::CACHE_PRIMARY;
213 if ( !$rev->
audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
218 return self::CACHE_SECONDARY;
237 $isOld = $revision && $revision->getId() !== $page->getLatest();
238 $useCache = $this->shouldUseCache( $page, $revision );
239 $primaryCache = $this->getPrimaryCache( $parserOptions );
240 $classCacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions );
242 if ( $useCache === self::CACHE_PRIMARY ) {
243 if ( $this->localCache->hasField( $classCacheKey, $page->
getLatest() ) && !$isOld ) {
244 return $this->localCache->getField( $classCacheKey, $page->
getLatest() );
246 $output = $primaryCache->get( $page, $parserOptions );
247 } elseif ( $useCache === self::CACHE_SECONDARY && $revision ) {
248 $secondaryCache = $this->getSecondaryCache( $parserOptions );
249 $output = $secondaryCache->get( $revision, $parserOptions );
254 if ( $output && !$isOld ) {
255 $this->localCache->setField( $classCacheKey, $page->
getLatest(), $output );
259 $this->statsDataFactory->increment(
"ParserOutputAccess.Cache.$useCache.hit" );
261 $this->statsDataFactory->increment(
"ParserOutputAccess.Cache.$useCache.miss" );
264 return $output ?:
null;
295 $error = $this->checkPreconditions( $page, $revision, $options );
297 $this->statsDataFactory->increment(
"ParserOutputAccess.Case.error" );
303 $this->statsDataFactory->increment(
'ParserOutputAccess.Case.old' );
305 $this->statsDataFactory->increment(
'ParserOutputAccess.Case.current' );
308 if ( !( $options & self::OPT_NO_CHECK_CACHE ) ) {
309 $output = $this->getCachedParserOutput( $page, $parserOptions, $revision );
311 return Status::newGood( $output );
317 $revision = $revId ? $this->revisionLookup->getRevisionById( $revId ) :
null;
320 $this->statsDataFactory->increment(
"ParserOutputAccess.Status.norev" );
321 return Status::newFatal(
'missing-revision', $revId );
325 if ( $options & self::OPT_FOR_ARTICLE_VIEW ) {
326 $work = $this->newPoolWorkArticleView( $page, $parserOptions, $revision, $options );
328 $status = $work->execute();
330 $status = $this->renderRevision( $page, $parserOptions, $revision, $options );
333 $output = $status->getValue();
334 Assert::postcondition( $output || !$status->isOK(),
'Inconsistent status' );
336 if ( $output && !$isOld ) {
337 $primaryCache = $this->getPrimaryCache( $parserOptions );
338 $classCacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions );
339 $this->localCache->setField( $classCacheKey, $page->
getLatest(), $output );
342 if ( $status->isGood() ) {
343 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.good' );
344 } elseif ( $status->isOK() ) {
345 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.ok' );
347 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.error' );
364 private function renderRevision(
367 RevisionRecord $revision,
370 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.None' );
372 $renderedRev = $this->revisionRenderer->getRenderedRevision(
376 [
'audience' => RevisionRecord::RAW ]
379 $output = $renderedRev->getRevisionParserOutput();
381 if ( !( $options & self::OPT_NO_UPDATE_CACHE ) && $output->isCacheable() ) {
382 $useCache = $this->shouldUseCache( $page, $revision );
384 if ( $useCache === self::CACHE_PRIMARY ) {
385 $primaryCache = $this->getPrimaryCache( $parserOptions );
386 $primaryCache->save( $output, $page, $parserOptions );
387 } elseif ( $useCache === self::CACHE_SECONDARY ) {
388 $secondaryCache = $this->getSecondaryCache( $parserOptions );
389 $secondaryCache->save( $output, $revision, $parserOptions );
393 if ( $options & self::OPT_LINKS_UPDATE ) {
394 $this->wikiPageFactory->newFromTitle( $page )
395 ->triggerOpportunisticLinksUpdate( $output );
398 return Status::newGood( $output );
408 private function checkPreconditions(
410 ?RevisionRecord $revision =
null,
413 if ( !$page->exists() ) {
414 return Status::newFatal(
'nopagetext' );
417 if ( !( $options & self::OPT_NO_UPDATE_CACHE ) && $revision && !$revision->getId() ) {
418 throw new InvalidArgumentException(
419 'The revision does not have a known ID. Use OPT_NO_CACHE.'
423 if ( $revision && $revision->getPageId() !== $page->getId() ) {
424 throw new InvalidArgumentException(
425 'The revision does not belong to the given page.'
429 if ( $revision && !( $options & self::OPT_NO_AUDIENCE_CHECK ) ) {
432 if ( !$revision->audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
433 return Status::newFatal(
434 'missing-revision-permission',
436 $revision->getTimestamp(),
437 $this->titleFormatter->getPrefixedDBkey( $page )
459 $useCache = $this->shouldUseCache( $page, $revision );
461 switch ( $useCache ) {
462 case self::CACHE_PRIMARY:
463 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Current' );
464 $primaryCache = $this->getPrimaryCache( $parserOptions );
465 $parserCacheMetadata = $primaryCache->getMetadata( $page );
466 $cacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions,
467 $parserCacheMetadata ? $parserCacheMetadata->getUsedOptions() : null
470 $workKey = $cacheKey .
':revid:' . $revision->
getId();
477 $this->revisionRenderer,
480 $this->chronologyProtector,
482 $this->wikiPageFactory,
483 !( $options & self::OPT_NO_UPDATE_CACHE ),
484 (
bool)( $options & self::OPT_LINKS_UPDATE )
487 case self::CACHE_SECONDARY:
488 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Old' );
489 $secondaryCache = $this->getSecondaryCache( $parserOptions );
490 $workKey = $secondaryCache->makeParserOutputKey( $revision, $parserOptions );
496 $this->revisionRenderer,
501 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Uncached' );
502 $secondaryCache = $this->getSecondaryCache( $parserOptions );
503 $workKey = $secondaryCache->makeParserOutputKeyOptionalRevId( $revision, $parserOptions );
508 $this->revisionRenderer,
517 if ( $pOpts->getUseParsoid() ) {
518 return $this->parserCacheFactory->getParserCache(
519 self::PARSOID_PCACHE_NAME
523 return $this->parserCacheFactory->getParserCache(
524 ParserCacheFactory::DEFAULT_NAME
528 private function getSecondaryCache(
ParserOptions $pOpts ): RevisionOutputCache {
529 if ( $pOpts->getUseParsoid() ) {
530 return $this->parserCacheFactory->getRevisionOutputCache(
531 self::PARSOID_RCACHE_NAME
535 return $this->parserCacheFactory->getRevisionOutputCache(
536 ParserCacheFactory::DEFAULT_RCACHE_NAME
__construct(ParserCacheFactory $parserCacheFactory, RevisionLookup $revisionLookup, RevisionRenderer $revisionRenderer, IBufferingStatsdDataFactory $statsDataFactory, ILBFactory $lbFactory, ChronologyProtector $chronologyProtector, LoggerSpi $loggerSpi, WikiPageFactory $wikiPageFactory, TitleFormatter $titleFormatter)