getRevision()->getContentModel(); $format = $page->getRevision()->getContentFormat(); $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 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->getRevision() ? $this->page->getRevision()->getRevisionRecord() : null; } 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\n" ); return false; } else { wfDebug( __METHOD__ . ": parser cache hit\n" ); return true; } } /** * @return bool */ public function fallback() { $this->parserOutput = $this->parserCache->getDirty( $this->page, $this->parserOptions ); if ( $this->parserOutput === false ) { wfDebugLog( 'dirty', 'dirty missing' ); wfDebug( __METHOD__ . ": no dirty cache\n" ); return false; } else { wfDebug( __METHOD__ . ": sending dirty output\n" ); wfDebugLog( 'dirty', "dirty output {$this->cacheKey}" ); $this->isDirty = true; return true; } } /** * @param Status $status * @return bool */ public function error( $status ) { $this->error = $status; return false; } }