revisionStore = $revisionStore; $this->slotRoleRegistry = $slotRoleRegistry; } /** * 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 ?string $pagelanguageOverride * @param ?array $parsoidSettings Used to enable the debug API if requested * @return \Wikimedia\Parsoid\Config\PageConfig */ public function create( PageIdentity $pageId, ?UserIdentity $user = null, $revision = null, ?string $unused = null, /* Added to mollify CI with cross-repo uses */ ?string $pagelanguageOverride = null, ?array $parsoidSettings = null ): \Wikimedia\Parsoid\Config\PageConfig { $title = Title::castFromPageIdentity( $pageId ); '@phan-var Title $title'; if ( !empty( $parsoidSettings['debugApi'] ) ) { if ( $revision === null ) { throw new \InvalidArgumentException( "Revision not provided. Cannot lookup revision via debug API." ); } $content = $revision->getContent( SlotRecord::MAIN ); if ( $content instanceof WikitextContent ) { $wtContent = $content->getText(); return ApiPageConfig::fromSettings( $parsoidSettings, [ "title" => $title->getPrefixedText(), "pageContent" => $wtContent, "pageLanguage" => $pagelanguageOverride, "revid" => $revision->getId(), "loadData" => true, ] ); } else { throw new \UnexpectedValueException( "Non-wikitext content models not supported by debug API" ); } } 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, RevisionStore::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 RevisionAccessException( '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 ); return new PageConfig( $parserOptions, $slotRoleHandler, $title, $revisionRecord, $pagelanguageOverride ); } }