revisionStore = $revisionStore; $this->slotRoleRegistry = $slotRoleRegistry; $this->languageFactory = $languageFactory; } /** * Create a new PageConfig. * * Note that Parsoid isn't supposed to use the user context by design; all * user-specific processing is expected to be introduced as a post-parse * transform. The $user parameter is therefore usually null, especially * in background job parsing, although there are corner cases during * extension processing where a non-null $user could affect the output. * * @param PageIdentity $pageId The page represented by the PageConfig. * @param ?UserIdentity $user User who is doing rendering (for parsing options). * @param int|RevisionRecord|null $revision Revision id or a revision record * @param ?string $unused * @param ?Bcp47Code $pageLanguageOverride * @param bool $ensureAccessibleContent If true, ensures that we can get content * from the newly constructed pageConfig's RevisionRecord and throws a * RevisionAccessException if not. * @return \Wikimedia\Parsoid\Config\PageConfig * @throws RevisionAccessException */ public function create( PageIdentity $pageId, ?UserIdentity $user = null, $revision = null, ?string $unused = null, /* Added to mollify CI with cross-repo uses */ ?Bcp47Code $pageLanguageOverride = null, bool $ensureAccessibleContent = false ): \Wikimedia\Parsoid\Config\PageConfig { $title = Title::newFromPageIdentity( $pageId ); if ( $unused !== null ) { wfDeprecated( __METHOD__ . ' with non-null 4th arg', '1.40' ); } if ( $revision === null ) { // Fetch the 'latest' revision for the given title. // Note: This initial fetch of the page context revision is // *not* using Parser::fetchCurrentRevisionRecordOfTitle() // (which usually invokes Parser::statelessFetchRevisionRecord // and from there RevisionStore::getKnownCurrentRevision) // because we don't have a Parser object to give to that callback. // We could create one if needed for greater compatibility. $revisionRecord = $this->revisionStore->getKnownCurrentRevision( $title ) ?: null; // Note that $revisionRecord could still be null here if no // page with that $title yet exists. } elseif ( !is_int( $revision ) ) { $revisionRecord = $revision; } else { if ( $revision === 0 ) { // The client may explicitly provide 0 as the revision ID to indicate that // the content doesn't belong to any saved revision, and provide wikitext // in some way. Calling code should handle this case and provide a (fake) // RevisionRecord based on the data in the request. If we get here, the // code processing the request didn't handle this case properly. throw new \UnexpectedValueException( "Got revision ID 0 indicating unsaved content. " . "Unsaved content must be provided as a RevisionRecord object." ); } // Fetch the correct revision record by the supplied id. // This accesses the replica DB and may (or may not) fail over to // the primary DB if the revision isn't found. $revisionRecord = $this->revisionStore->getRevisionById( $revision ); if ( $revisionRecord === null ) { // This revision really ought to exist. Check the primary DB. // This *could* cause two requests to the primary DB if there // were pending writes, but this codepath should be very rare. // [T259855] $revisionRecord = $this->revisionStore->getRevisionById( $revision, IDBAccessObject::READ_LATEST ); $success = ( $revisionRecord !== null ) ? 'success' : 'failure'; LoggerFactory::getInstance( 'Parsoid' )->error( "Retried revision fetch after failure: {$success}", [ 'id' => $revision, 'title' => $title->getPrefixedText(), ] ); } if ( $revisionRecord === null ) { throw new RevisionAccessException( "Can't find revision {$revision}" ); } } // If we have a revision record, check that we are allowed to see it. // Mirrors the check from RevisionRecord::getContent if ( $revisionRecord !== null && !$revisionRecord->audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) { throw new SuppressedDataException( 'Not an available content version.' ); } $parserOptions = $user ? ParserOptions::newFromUser( $user ) : ParserOptions::newFromAnon(); // Turn off some options since Parsoid/JS currently doesn't // do anything with this. As we proceed with closer integration, // we can figure out if there is any value to these limit reports. $parserOptions->setOption( 'enableLimitReport', false ); $slotRoleHandler = $this->slotRoleRegistry->getRoleHandler( SlotRecord::MAIN ); if ( $pageLanguageOverride ) { $pageLanguage = $this->languageFactory->getLanguage( $pageLanguageOverride ); } else { $pageLanguage = $title->getPageLanguage(); } $pageConfig = new PageConfig( $parserOptions, $slotRoleHandler, $title, $revisionRecord, $pageLanguage, $pageLanguage->getDir() ); if ( $ensureAccessibleContent ) { if ( $revisionRecord === null ) { // T234549 throw new RevisionAccessException( 'The specified revision does not exist.' ); } // Try to get the content so that we can fail early. Otherwise, // a RevisionAccessException is thrown. It's expensive, but the // result will be cached for later calls. $pageConfig->getRevisionContent()->getContent( SlotRecord::MAIN ); } return $pageConfig; } }