parsoidOutputStash = $parsoidOutputStash; $this->stats = $statsDataFactory; $this->parsoidOutputAccess = $parsoidOutputAccess; } /** * @param PageIdentity $page * @param array $parameters * @param User $user * @param RevisionRecord|null $revision * @param Language|null $pageLanguage */ public function init( PageIdentity $page, array $parameters, User $user, ?RevisionRecord $revision = null, ?Language $pageLanguage = null ) { $this->page = $page; $this->user = $user; $this->revision = $revision; $this->pageLanguage = $pageLanguage; $this->stash = $parameters['stash']; $this->flavor = $parameters['stash'] ? 'stash' : 'view'; // more to come, T308743 } /** * @return ParserOutput a tuple with html and content-type * @throws LocalizedHttpException */ public function getHtml(): ParserOutput { $parserOutput = $this->getParserOutput(); if ( $this->stash ) { if ( $this->user->pingLimiter( 'stashbasehtml' ) ) { throw new LocalizedHttpException( MessageValue::new( 'parsoid-stash-rate-limit-error' ), // See https://www.rfc-editor.org/rfc/rfc6585#section-4 429, [ 'reason' => 'Rate limiter tripped, wait for a few minutes and try again' ] ); } $parsoidStashKey = ParsoidRenderID::newFromKey( $this->parsoidOutputAccess->getParsoidRenderID( $parserOutput ) ); $stashSuccess = $this->parsoidOutputStash->set( $parsoidStashKey, PageBundleParserOutputConverter::pageBundleFromParserOutput( $parserOutput ) ); if ( !$stashSuccess ) { $this->stats->increment( 'htmloutputrendererhelper.stash.fail' ); throw new LocalizedHttpException( MessageValue::new( 'rest-html-backend-error' ), 500, [ 'reason' => 'Failed to stash parser output' ] ); } $this->stats->increment( 'htmloutputrendererhelper.stash.save' ); } return $parserOutput; } /** * Returns an ETag uniquely identifying the HTML output. * * @param string $suffix A suffix to attach to the etag. * * @return string|null */ public function getETag( string $suffix = '' ): ?string { $parserOutput = $this->getParserOutput(); $renderID = $this->parsoidOutputAccess->getParsoidRenderID( $parserOutput )->getKey(); if ( $suffix !== '' ) { $eTag = "$renderID/{$this->flavor}/$suffix"; } else { $eTag = "$renderID/{$this->flavor}"; } return "\"{$eTag}\""; } /** * Returns the time at which the HTML was rendered. * * @return string|null */ public function getLastModified(): ?string { return $this->getParserOutput()->getCacheTime(); } /** * @return array */ public function getParamSettings(): array { return [ 'stash' => [ Handler::PARAM_SOURCE => 'query', ParamValidator::PARAM_TYPE => 'boolean', ParamValidator::PARAM_DEFAULT => false, ParamValidator::PARAM_REQUIRED => false, ] ]; } /** * @return ParserOutput */ private function getParserOutput(): ParserOutput { if ( !$this->parserOutput ) { $parserOptions = ParserOptions::newFromAnon(); if ( $this->pageLanguage ) { $parserOptions->setTargetLanguage( $this->pageLanguage ); } // If we have a revision and the ID is 0 or null, then it's a fake revision // representing a preview. $fakeRevision = ( $this->revision && $this->revision->getId() < 1 ); // NOTE: ParsoidOutputAccess::getParserOutput() should be used for revisions // that comes from the database. Either this revision is null to indicate // the current revision or the revision must have an ID. if ( $this->page instanceof PageRecord && !$fakeRevision ) { $status = $this->parsoidOutputAccess->getParserOutput( $this->page, $parserOptions, $this->revision ); } else { // Here, we have a revision object that has no revision, so it's a fake revision // for example: on page previews. $status = $this->parsoidOutputAccess->parse( $this->page, $parserOptions, $this->revision ); } if ( !$status->isOK() ) { if ( $status->hasMessage( 'parsoid-client-error' ) ) { throw new LocalizedHttpException( MessageValue::new( 'rest-html-backend-error' ), 400, [ 'reason' => $status->getErrors() ] ); } elseif ( $status->hasMessage( 'parsoid-resource-limit-exceeded' ) ) { throw new LocalizedHttpException( MessageValue::new( 'rest-resource-limit-exceeded' ), 413, [ 'reason' => $status->getErrors() ] ); } else { throw new LocalizedHttpException( MessageValue::new( 'rest-html-backend-error' ), 500, [ 'reason' => $status->getErrors() ] ); } } $this->parserOutput = $status->getValue(); } return $this->parserOutput; } /** * The content language of the HTML output after parsing. * * @return string */ public function getHtmlOutputContentLanguage(): string { if ( $this->parserOutput ) { $pageBundleData = $this->parserOutput->getExtensionData( PageBundleParserOutputConverter::PARSOID_PAGE_BUNDLE_KEY ); } else { $pageBundleData = $this->getHtml()->getExtensionData( PageBundleParserOutputConverter::PARSOID_PAGE_BUNDLE_KEY ); } // XXX: We need a canonical way of getting the output language from // ParserOutput since we may not be getting parser outputs from // Parsoid always in the future. if ( !isset( $pageBundleData['headers']['content-language'] ) ) { throw new LogicException( 'Failed to find content language in page bundle data' ); } return $pageBundleData['headers']['content-language']; } }