getRevisionRecord(); $mainSlot = $revRecord->getSlot( SlotRecord::MAIN, RevisionRecord::RAW ); $modelId = $mainSlot->getModel(); $format = $mainSlot->getFormat(); if ( $format === null ) { $format = MediaWikiServices::getInstance() ->getContentHandlerFactory() ->getContentHandler( $modelId ) ->getDefaultFormat(); } $revision = ContentHandler::makeContent( $revision, $page->getTitle(), $modelId, $format ); } if ( $revision instanceof Content ) { // BC: old style call $content = $revision; $revision = new MutableRevisionRecord( $page->getTitle() ); $revision->setId( $revid ); $revision->setPageId( $page->getId() ); $revision->setContent( SlotRecord::MAIN, $content ); } if ( $revision ) { // Check that the RevisionRecord matches $revid and $page, but still allow // fake RevisionRecords coming from errors or hooks in Article to be rendered. if ( $revision->getId() && $revision->getId() !== $revid ) { throw new InvalidArgumentException( '$revid parameter mismatches $revision parameter' ); } if ( $revision->getPageId() && $revision->getPageId() !== $page->getTitle()->getArticleID() ) { throw new InvalidArgumentException( '$page parameter mismatches $revision parameter' ); } } // TODO: DI: inject services $this->renderer = MediaWikiServices::getInstance()->getRevisionRenderer(); $this->revisionStore = MediaWikiServices::getInstance()->getRevisionStore(); $this->parserCache = MediaWikiServices::getInstance()->getParserCache(); $this->page = $page; $this->revid = $revid; $this->cacheable = $useParserCache; $this->parserOptions = $parserOptions; $this->revision = $revision; $this->audience = $audience; $this->cacheKey = $this->parserCache->getKey( $page, $parserOptions ); $keyPrefix = $this->cacheKey ?: ObjectCache::getLocalClusterInstance()->makeKey( 'articleview', 'missingcachekey' ); parent::__construct( 'ArticleView', $keyPrefix . ':revid:' . $revid ); } /** * Get the ParserOutput from this object, or false in case of failure * * @return ParserOutput|bool */ public function getParserOutput() { return $this->parserOutput; } /** * Get whether the ParserOutput is a dirty one (i.e. expired) * * @return bool */ public function getIsDirty() { return $this->isDirty; } /** * Get whether the ParserOutput was retrieved in fast stale mode * * @return bool */ public function getIsFastStale() { return $this->isFast; } /** * Get a Status object in case of error or false otherwise * * @return Status|bool */ public function getError() { return $this->error; } /** * @return bool */ public function doWork() { global $wgUseFileCache; // @todo several of the methods called on $this->page are not declared in Page, but present // in WikiPage and delegated by Article. $isCurrent = $this->revid === $this->page->getLatest(); // The current revision cannot be hidden so we can skip some checks. $audience = $isCurrent ? RevisionRecord::RAW : $this->audience; if ( $this->revision !== null ) { $rev = $this->revision; } elseif ( $isCurrent ) { $rev = $this->page->getRevisionRecord(); } else { $rev = $this->revisionStore->getRevisionByTitle( $this->page->getTitle(), $this->revid ); } if ( !$rev ) { // couldn't load return false; } $renderedRevision = $this->renderer->getRenderedRevision( $rev, $this->parserOptions, null, [ 'audience' => $audience ] ); if ( !$renderedRevision ) { // audience check failed return false; } // Reduce effects of race conditions for slow parses (T48014) $cacheTime = wfTimestampNow(); $time = - microtime( true ); $this->parserOutput = $renderedRevision->getRevisionParserOutput(); $time += microtime( true ); // Timing hack if ( $time > 3 ) { // TODO: Use Parser's logger (once it has one) $logger = MediaWiki\Logger\LoggerFactory::getInstance( 'slow-parse' ); $logger->info( '{time} {title}', [ 'time' => number_format( $time, 2 ), 'title' => $this->page->getTitle()->getPrefixedDBkey(), 'ns' => $this->page->getTitle()->getNamespace(), 'trigger' => 'view', ] ); } if ( $this->cacheable && $this->parserOutput->isCacheable() && $isCurrent ) { $this->parserCache->save( $this->parserOutput, $this->page, $this->parserOptions, $cacheTime, $this->revid ); } // Make sure file cache is not used on uncacheable content. // Output that has magic words in it can still use the parser cache // (if enabled), though it will generally expire sooner. if ( !$this->parserOutput->isCacheable() ) { $wgUseFileCache = false; } if ( $isCurrent ) { $this->page->triggerOpportunisticLinksUpdate( $this->parserOutput ); } return true; } /** * @return bool */ public function getCachedWork() { $this->parserOutput = $this->parserCache->get( $this->page, $this->parserOptions ); if ( $this->parserOutput === false ) { wfDebug( __METHOD__ . ": parser cache miss" ); return false; } else { wfDebug( __METHOD__ . ": parser cache hit" ); return true; } } /** * @param bool $fast Fast stale request * @return bool */ public function fallback( $fast ) { $this->parserOutput = $this->parserCache->getDirty( $this->page, $this->parserOptions ); $fastMsg = ''; if ( $this->parserOutput && $fast ) { /* Check if the stale response is from before the last write to the * DB by this user. Declining to return a stale response in this * case ensures that the user will see their own edit after page * save. * * Note that the CP touch time is the timestamp of the shutdown of * the save request, so there is a bias towards avoiding fast stale * responses of potentially several seconds. */ $lastWriteTime = MediaWikiServices::getInstance()->getDBLoadBalancerFactory() ->getChronologyProtectorTouched(); $cacheTime = MWTimestamp::convert( TS_UNIX, $this->parserOutput->getCacheTime() ); if ( $lastWriteTime && $cacheTime <= $lastWriteTime ) { wfDebugLog( 'dirty', "declining to send dirty output since cache time " . $cacheTime . " is before last write time $lastWriteTime" ); // Forget this ParserOutput -- we will request it again if // necessary in slow mode. There might be a newer entry // available by that time. $this->parserOutput = false; return false; } $this->isFast = true; $fastMsg = 'fast '; } if ( $this->parserOutput === false ) { wfDebugLog( 'dirty', 'dirty missing' ); return false; } else { wfDebugLog( 'dirty', "{$fastMsg}dirty output {$this->cacheKey}" ); $this->isDirty = true; return true; } } /** * @param Status $status * @return bool */ public function error( $status ) { $this->error = $status; return false; } }