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';
137 private LoggerSpi $loggerSpi;
148 LoggerSpi $loggerSpi,
152 $this->parserCacheFactory = $parserCacheFactory;
153 $this->revisionLookup = $revisionLookup;
154 $this->revisionRenderer = $revisionRenderer;
155 $this->statsDataFactory = $statsDataFactory;
156 $this->lbFactory = $lbFactory;
157 $this->chronologyProtector = $chronologyProtector;
158 $this->loggerSpi = $loggerSpi;
159 $this->wikiPageFactory = $wikiPageFactory;
160 $this->titleFormatter = $titleFormatter;
173 private function shouldUseCache(
177 if ( $rev && !$rev->
getId() ) {
186 $wikiPage = $this->wikiPageFactory->newFromTitle( $page );
187 if ( !$page->
exists() || !$wikiPage->getContentHandler()->isParserCacheSupported() ) {
193 return self::CACHE_PRIMARY;
196 if ( !$rev->
audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
201 return self::CACHE_SECONDARY;
220 $isOld = $revision && $revision->getId() !== $page->getLatest();
221 $useCache = $this->shouldUseCache( $page, $revision );
222 $primaryCache = $this->getPrimaryCache( $parserOptions );
223 $classCacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions );
225 if ( $useCache === self::CACHE_PRIMARY ) {
226 if ( $this->localCache->hasField( $classCacheKey, $page->
getLatest() ) && !$isOld ) {
227 return $this->localCache->getField( $classCacheKey, $page->
getLatest() );
229 $output = $primaryCache->get( $page, $parserOptions );
230 } elseif ( $useCache === self::CACHE_SECONDARY && $revision ) {
231 $secondaryCache = $this->getSecondaryCache( $parserOptions );
232 $output = $secondaryCache->get( $revision, $parserOptions );
237 $notHitReason =
'miss';
239 $output && !( $options & self::OPT_IGNORE_PROFILE_VERSION ) &&
242 $pageBundleData = $output->getExtensionData(
243 PageBundleParserOutputConverter::PARSOID_PAGE_BUNDLE_KEY
246 $cachedVersion = $pageBundleData[
'version'] ??
null;
248 $cachedVersion !==
null &&
249 $cachedVersion !== Parsoid::defaultHTMLVersion()
251 $notHitReason =
'obsolete';
256 if ( $output && !$isOld ) {
257 $this->localCache->setField( $classCacheKey, $page->
getLatest(), $output );
261 $this->statsDataFactory->increment(
"ParserOutputAccess.Cache.$useCache.hit" );
263 $this->statsDataFactory->increment(
"ParserOutputAccess.Cache.$useCache.$notHitReason" );
266 return $output ?:
null;
297 $error = $this->checkPreconditions( $page, $revision, $options );
299 $this->statsDataFactory->increment(
"ParserOutputAccess.Case.error" );
305 $this->statsDataFactory->increment(
'ParserOutputAccess.Case.old' );
307 $this->statsDataFactory->increment(
'ParserOutputAccess.Case.current' );
310 if ( !( $options & self::OPT_NO_CHECK_CACHE ) ) {
311 $output = $this->getCachedParserOutput( $page, $parserOptions, $revision );
313 return Status::newGood( $output );
319 $revision = $revId ? $this->revisionLookup->getRevisionById( $revId ) :
null;
322 $this->statsDataFactory->increment(
"ParserOutputAccess.Status.norev" );
323 return Status::newFatal(
'missing-revision', $revId );
327 if ( $options & self::OPT_FOR_ARTICLE_VIEW ) {
328 $work = $this->newPoolWorkArticleView( $page, $parserOptions, $revision, $options );
330 $status = $work->execute();
332 $status = $this->renderRevision( $page, $parserOptions, $revision, $options );
335 $output = $status->getValue();
336 Assert::postcondition( $output || !$status->isOK(),
'Inconsistent status' );
338 if ( $output && !$isOld ) {
339 $primaryCache = $this->getPrimaryCache( $parserOptions );
340 $classCacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions );
341 $this->localCache->setField( $classCacheKey, $page->
getLatest(), $output );
344 if ( $status->isGood() ) {
345 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.good' );
346 } elseif ( $status->isOK() ) {
347 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.ok' );
349 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.error' );
366 private function renderRevision(
369 RevisionRecord $revision,
372 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.None' );
374 $renderedRev = $this->revisionRenderer->getRenderedRevision(
378 [
'audience' => RevisionRecord::RAW ]
381 $output = $renderedRev->getRevisionParserOutput();
383 if ( !( $options & self::OPT_NO_UPDATE_CACHE ) && $output->isCacheable() ) {
384 $useCache = $this->shouldUseCache( $page, $revision );
386 if ( $useCache === self::CACHE_PRIMARY ) {
387 $primaryCache = $this->getPrimaryCache( $parserOptions );
388 $primaryCache->save( $output, $page, $parserOptions );
389 } elseif ( $useCache === self::CACHE_SECONDARY ) {
390 $secondaryCache = $this->getSecondaryCache( $parserOptions );
391 $secondaryCache->save( $output, $revision, $parserOptions );
395 if ( $options & self::OPT_LINKS_UPDATE ) {
396 $this->wikiPageFactory->newFromTitle( $page )
397 ->triggerOpportunisticLinksUpdate( $output );
400 return Status::newGood( $output );
410 private function checkPreconditions(
412 ?RevisionRecord $revision =
null,
415 if ( !$page->exists() ) {
416 return Status::newFatal(
'nopagetext' );
419 if ( !( $options & self::OPT_NO_UPDATE_CACHE ) && $revision && !$revision->getId() ) {
420 throw new InvalidArgumentException(
421 'The revision does not have a known ID. Use OPT_NO_CACHE.'
425 if ( $revision && $revision->getPageId() !== $page->getId() ) {
426 throw new InvalidArgumentException(
427 'The revision does not belong to the given page.'
431 if ( $revision && !( $options & self::OPT_NO_AUDIENCE_CHECK ) ) {
434 if ( !$revision->audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
435 return Status::newFatal(
436 'missing-revision-permission',
438 $revision->getTimestamp(),
439 $this->titleFormatter->getPrefixedDBkey( $page )
461 $useCache = $this->shouldUseCache( $page, $revision );
463 switch ( $useCache ) {
464 case self::CACHE_PRIMARY:
465 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Current' );
466 $primaryCache = $this->getPrimaryCache( $parserOptions );
467 $parserCacheMetadata = $primaryCache->getMetadata( $page );
468 $cacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions,
469 $parserCacheMetadata ? $parserCacheMetadata->getUsedOptions() : null
472 $workKey = $cacheKey .
':revid:' . $revision->
getId();
479 $this->revisionRenderer,
482 $this->chronologyProtector,
484 $this->wikiPageFactory,
485 !( $options & self::OPT_NO_UPDATE_CACHE ),
486 (
bool)( $options & self::OPT_LINKS_UPDATE )
489 case self::CACHE_SECONDARY:
490 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Old' );
491 $secondaryCache = $this->getSecondaryCache( $parserOptions );
492 $workKey = $secondaryCache->makeParserOutputKey( $revision, $parserOptions );
498 $this->revisionRenderer,
503 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Uncached' );
504 $secondaryCache = $this->getSecondaryCache( $parserOptions );
505 $workKey = $secondaryCache->makeParserOutputKeyOptionalRevId( $revision, $parserOptions );
510 $this->revisionRenderer,
519 if ( $pOpts->getUseParsoid() ) {
520 return $this->parserCacheFactory->getParserCache(
521 self::PARSOID_PCACHE_NAME
525 return $this->parserCacheFactory->getParserCache(
526 ParserCacheFactory::DEFAULT_NAME
530 private function getSecondaryCache(
ParserOptions $pOpts ): RevisionOutputCache {
531 if ( $pOpts->getUseParsoid() ) {
532 return $this->parserCacheFactory->getRevisionOutputCache(
533 self::PARSOID_RCACHE_NAME
537 return $this->parserCacheFactory->getRevisionOutputCache(
538 ParserCacheFactory::DEFAULT_RCACHE_NAME
__construct(ParserCacheFactory $parserCacheFactory, RevisionLookup $revisionLookup, RevisionRenderer $revisionRenderer, IBufferingStatsdDataFactory $statsDataFactory, ILBFactory $lbFactory, ChronologyProtector $chronologyProtector, LoggerSpi $loggerSpi, WikiPageFactory $wikiPageFactory, TitleFormatter $titleFormatter)