66 public const OPT_NO_CHECK_CACHE = 1;
69 public const OPT_FORCE_PARSE = self::OPT_NO_CHECK_CACHE;
74 public const OPT_NO_UPDATE_CACHE = 2;
81 public const OPT_NO_AUDIENCE_CHECK = 4;
87 public const OPT_NO_CACHE = self::OPT_NO_UPDATE_CACHE | self::OPT_NO_CHECK_CACHE;
93 public const OPT_LINKS_UPDATE = 8;
113 public const OPT_IGNORE_PROFILE_VERSION = 128;
119 private const CACHE_PRIMARY =
'primary';
122 private const CACHE_SECONDARY =
'secondary';
134 private $revisionLookup;
137 private $revisionRenderer;
140 private $statsDataFactory;
150 private $wikiPageFactory;
153 private $titleFormatter;
173 LoggerSpi $loggerSpi,
177 $this->parserCacheFactory = $parserCacheFactory;
178 $this->revisionLookup = $revisionLookup;
179 $this->revisionRenderer = $revisionRenderer;
180 $this->statsDataFactory = $statsDataFactory;
181 $this->lbFactory = $lbFactory;
182 $this->chronologyProtector = $chronologyProtector;
183 $this->loggerSpi = $loggerSpi;
184 $this->wikiPageFactory = $wikiPageFactory;
185 $this->titleFormatter = $titleFormatter;
198 private function shouldUseCache(
202 if ( $rev && !$rev->
getId() ) {
211 $wikiPage = $this->wikiPageFactory->newFromTitle( $page );
212 if ( !$page->
exists() || !$wikiPage->getContentHandler()->isParserCacheSupported() ) {
218 return self::CACHE_PRIMARY;
221 if ( !$rev->
audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
226 return self::CACHE_SECONDARY;
245 $isOld = $revision && $revision->getId() !== $page->getLatest();
246 $useCache = $this->shouldUseCache( $page, $revision );
247 $primaryCache = $this->getPrimaryCache( $parserOptions );
248 $classCacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions );
250 if ( $useCache === self::CACHE_PRIMARY ) {
251 if ( $this->localCache->hasField( $classCacheKey, $page->
getLatest() ) && !$isOld ) {
252 return $this->localCache->getField( $classCacheKey, $page->
getLatest() );
254 $output = $primaryCache->get( $page, $parserOptions );
255 } elseif ( $useCache === self::CACHE_SECONDARY && $revision ) {
256 $secondaryCache = $this->getSecondaryCache( $parserOptions );
257 $output = $secondaryCache->get( $revision, $parserOptions );
262 $notHitReason =
'miss';
264 $output && !( $options & self::OPT_IGNORE_PROFILE_VERSION ) &&
267 $pageBundleData = $output->getExtensionData(
268 PageBundleParserOutputConverter::PARSOID_PAGE_BUNDLE_KEY
271 $cachedVersion = $pageBundleData[
'version'] ??
null;
273 $cachedVersion !==
null &&
274 $cachedVersion !== Parsoid::defaultHTMLVersion()
276 $notHitReason =
'obsolete';
281 if ( $output && !$isOld ) {
282 $this->localCache->setField( $classCacheKey, $page->
getLatest(), $output );
286 $this->statsDataFactory->increment(
"ParserOutputAccess.Cache.$useCache.hit" );
288 $this->statsDataFactory->increment(
"ParserOutputAccess.Cache.$useCache.$notHitReason" );
291 return $output ?:
null;
322 $error = $this->checkPreconditions( $page, $revision, $options );
324 $this->statsDataFactory->increment(
"ParserOutputAccess.Case.error" );
330 $this->statsDataFactory->increment(
'ParserOutputAccess.Case.old' );
332 $this->statsDataFactory->increment(
'ParserOutputAccess.Case.current' );
335 if ( !( $options & self::OPT_NO_CHECK_CACHE ) ) {
336 $output = $this->getCachedParserOutput( $page, $parserOptions, $revision );
338 return Status::newGood( $output );
344 $revision = $revId ? $this->revisionLookup->getRevisionById( $revId ) :
null;
347 $this->statsDataFactory->increment(
"ParserOutputAccess.Status.norev" );
348 return Status::newFatal(
'missing-revision', $revId );
352 if ( $options & self::OPT_FOR_ARTICLE_VIEW ) {
353 $work = $this->newPoolWorkArticleView( $page, $parserOptions, $revision, $options );
355 $status = $work->execute();
357 $status = $this->renderRevision( $page, $parserOptions, $revision, $options );
360 $output = $status->getValue();
361 Assert::postcondition( $output || !$status->isOK(),
'Inconsistent status' );
363 if ( $output && !$isOld ) {
364 $primaryCache = $this->getPrimaryCache( $parserOptions );
365 $classCacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions );
366 $this->localCache->setField( $classCacheKey, $page->
getLatest(), $output );
369 if ( $status->isGood() ) {
370 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.good' );
371 } elseif ( $status->isOK() ) {
372 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.ok' );
374 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.error' );
391 private function renderRevision(
394 RevisionRecord $revision,
397 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.None' );
399 $renderedRev = $this->revisionRenderer->getRenderedRevision(
403 [
'audience' => RevisionRecord::RAW ]
406 $output = $renderedRev->getRevisionParserOutput();
408 if ( !( $options & self::OPT_NO_UPDATE_CACHE ) && $output->isCacheable() ) {
409 $useCache = $this->shouldUseCache( $page, $revision );
411 if ( $useCache === self::CACHE_PRIMARY ) {
412 $primaryCache = $this->getPrimaryCache( $parserOptions );
413 $primaryCache->save( $output, $page, $parserOptions );
414 } elseif ( $useCache === self::CACHE_SECONDARY ) {
415 $secondaryCache = $this->getSecondaryCache( $parserOptions );
416 $secondaryCache->save( $output, $revision, $parserOptions );
420 if ( $options & self::OPT_LINKS_UPDATE ) {
421 $this->wikiPageFactory->newFromTitle( $page )
422 ->triggerOpportunisticLinksUpdate( $output );
425 return Status::newGood( $output );
435 private function checkPreconditions(
437 ?RevisionRecord $revision =
null,
440 if ( !$page->exists() ) {
441 return Status::newFatal(
'nopagetext' );
444 if ( !( $options & self::OPT_NO_UPDATE_CACHE ) && $revision && !$revision->getId() ) {
445 throw new InvalidArgumentException(
446 'The revision does not have a known ID. Use OPT_NO_CACHE.'
450 if ( $revision && $revision->getPageId() !== $page->getId() ) {
451 throw new InvalidArgumentException(
452 'The revision does not belong to the given page.'
456 if ( $revision && !( $options & self::OPT_NO_AUDIENCE_CHECK ) ) {
459 if ( !$revision->audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
460 return Status::newFatal(
461 'missing-revision-permission',
463 $revision->getTimestamp(),
464 $this->titleFormatter->getPrefixedDBkey( $page )
486 $useCache = $this->shouldUseCache( $page, $revision );
488 switch ( $useCache ) {
489 case self::CACHE_PRIMARY:
490 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Current' );
491 $primaryCache = $this->getPrimaryCache( $parserOptions );
492 $parserCacheMetadata = $primaryCache->getMetadata( $page );
493 $cacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions,
494 $parserCacheMetadata ? $parserCacheMetadata->getUsedOptions() : null
497 $workKey = $cacheKey .
':revid:' . $revision->
getId();
504 $this->revisionRenderer,
507 $this->chronologyProtector,
509 $this->wikiPageFactory,
510 !( $options & self::OPT_NO_UPDATE_CACHE ),
511 (
bool)( $options & self::OPT_LINKS_UPDATE )
514 case self::CACHE_SECONDARY:
515 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Old' );
516 $secondaryCache = $this->getSecondaryCache( $parserOptions );
517 $workKey = $secondaryCache->makeParserOutputKey( $revision, $parserOptions );
523 $this->revisionRenderer,
528 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Uncached' );
529 $secondaryCache = $this->getSecondaryCache( $parserOptions );
530 $workKey = $secondaryCache->makeParserOutputKeyOptionalRevId( $revision, $parserOptions );
535 $this->revisionRenderer,
544 if ( $pOpts->getUseParsoid() ) {
545 return $this->parserCacheFactory->getParserCache(
546 self::PARSOID_PCACHE_NAME
550 return $this->parserCacheFactory->getParserCache(
551 ParserCacheFactory::DEFAULT_NAME
555 private function getSecondaryCache(
ParserOptions $pOpts ): RevisionOutputCache {
556 if ( $pOpts->getUseParsoid() ) {
557 return $this->parserCacheFactory->getRevisionOutputCache(
558 self::PARSOID_RCACHE_NAME
562 return $this->parserCacheFactory->getRevisionOutputCache(
563 ParserCacheFactory::DEFAULT_RCACHE_NAME
__construct(ParserCacheFactory $parserCacheFactory, RevisionLookup $revisionLookup, RevisionRenderer $revisionRenderer, IBufferingStatsdDataFactory $statsDataFactory, ILBFactory $lbFactory, ChronologyProtector $chronologyProtector, LoggerSpi $loggerSpi, WikiPageFactory $wikiPageFactory, TitleFormatter $titleFormatter)