MediaWiki master
ParsoidOutputAccess.php
Go to the documentation of this file.
1<?php
21
35use Wikimedia\Parsoid\Config\SiteConfig;
36use Wikimedia\Parsoid\Core\ClientError;
37use Wikimedia\Parsoid\Core\ResourceLimitExceededException;
38
53 private ParsoidParserFactory $parsoidParserFactory;
54 private PageLookup $pageLookup;
55 private RevisionLookup $revisionLookup;
56 private ParserOutputAccess $parserOutputAccess;
57 private SiteConfig $siteConfig;
58 private IContentHandlerFactory $contentHandlerFactory;
59
68 public function __construct(
69 ParsoidParserFactory $parsoidParserFactory,
70 ParserOutputAccess $parserOutputAccess,
71 PageLookup $pageLookup,
72 RevisionLookup $revisionLookup,
73 SiteConfig $siteConfig,
74 IContentHandlerFactory $contentHandlerFactory
75 ) {
76 $this->parsoidParserFactory = $parsoidParserFactory;
77 $this->parserOutputAccess = $parserOutputAccess;
78 $this->pageLookup = $pageLookup;
79 $this->revisionLookup = $revisionLookup;
80 $this->siteConfig = $siteConfig;
81 $this->contentHandlerFactory = $contentHandlerFactory;
82 }
83
89 public function supportsContentModel( string $model ): bool {
90 if ( $model === CONTENT_MODEL_WIKITEXT ) {
91 return true;
92 }
93
94 // Check if the content model serializes to wikitext.
95 // NOTE: We could use isSupportedFormat( CONTENT_FORMAT_WIKITEXT ) if PageContent::getContent()
96 // would specify the format when calling serialize().
97 try {
98 $handler = $this->contentHandlerFactory->getContentHandler( $model );
99 if ( $handler->getDefaultFormat() === CONTENT_FORMAT_WIKITEXT ) {
100 return true;
101 }
102 } catch ( MWUnknownContentModelException $ex ) {
103 // If the content model is not known, it can't be supported.
104 return false;
105 }
106
107 return $this->siteConfig->getContentModelHandler( $model ) !== null;
108 }
109
119 public function getParserOutput(
120 PageIdentity $page,
121 ParserOptions $parserOpts,
122 $revision = null,
123 int $options = 0,
124 bool $lenientRevHandling = false
125 ): Status {
126 [ $page, $revision, $uncacheable ] = $this->resolveRevision( $page, $revision, $lenientRevHandling );
127
128 try {
129 if ( $uncacheable ) {
130 $options |= ParserOutputAccess::OPT_NO_UPDATE_CACHE;
131 }
132
133 $this->adjustParserOptions( $revision, $parserOpts );
134 $status = $this->parserOutputAccess->getParserOutput(
135 $page, $parserOpts, $revision, $options
136 );
137 } catch ( ClientError $e ) {
138 $status = Status::newFatal( 'parsoid-client-error', $e->getMessage() );
139 } catch ( ResourceLimitExceededException $e ) {
140 $status = Status::newFatal( 'parsoid-resource-limit-exceeded', $e->getMessage() );
141 }
142 return $status;
143 }
144
153 public function getCachedParserOutput(
154 PageIdentity $page,
155 ParserOptions $parserOpts,
156 $revision = null,
157 bool $lenientRevHandling = false
158 ): ?ParserOutput {
159 [ $page, $revision, $ignored ] = $this->resolveRevision( $page, $revision, $lenientRevHandling );
160
161 $this->adjustParserOptions( $revision, $parserOpts );
162 return $this->parserOutputAccess->getCachedParserOutput( $page, $parserOpts, $revision );
163 }
164
176 public function parseUncacheable(
177 PageIdentity $page,
178 ParserOptions $parserOpts,
179 $revision,
180 bool $lenientRevHandling = false
181 ): Status {
182 // NOTE: If we have a RevisionRecord already, just use it, there is no need to resolve $page to
183 // a PageRecord (and it may not be possible if the page doesn't exist).
184 if ( !$revision instanceof RevisionRecord ) {
185 [ $page, $revision, $ignored ] = $this->resolveRevision( $page, $revision, $lenientRevHandling );
186 }
187
188 // Enforce caller expectation
189 $revId = $revision->getId();
190 if ( $revId !== 0 && $revId !== null ) {
191 return Status::newFatal( 'parsoid-revision-access',
192 "parseUncacheable should not be called for a real revision" );
193 }
194
195 try {
196 // Since we aren't caching this output, there is no need to
197 // call setUseParsoid() here.
198 $parser = $this->parsoidParserFactory->create();
199 $parserOutput = $this->parsoidParserFactory->create()->parseFakeRevision(
200 $revision, $page, $parserOpts );
201 $parserOutput->updateCacheExpiry( 0 ); // Ensure this isn't accidentally cached
202 $status = Status::newGood( $parserOutput );
203 } catch ( RevisionAccessException $e ) {
204 return Status::newFatal( 'parsoid-revision-access', $e->getMessage() );
205 } catch ( ClientError $e ) {
206 $status = Status::newFatal( 'parsoid-client-error', $e->getMessage() );
207 } catch ( ResourceLimitExceededException $e ) {
208 $status = Status::newFatal( 'parsoid-resource-limit-exceeded', $e->getMessage() );
209 }
210 return $status;
211 }
212
220 private function resolveRevision( PageIdentity $page, $revision, bool $lenientRevHandling = false ): array {
221 $uncacheable = false;
222 if ( !$page instanceof PageRecord ) {
223 $name = "$page";
224 $page = $this->pageLookup->getPageByReference( $page );
225 if ( !$page ) {
226 throw new RevisionAccessException(
227 'Page {name} not found',
228 [ 'name' => $name ]
229 );
230 }
231 }
232
233 if ( $revision === null ) {
234 $revision = $page->getLatest();
235 }
236
237 if ( is_int( $revision ) ) {
238 $revId = $revision;
239 $revision = $this->revisionLookup->getRevisionById( $revId );
240
241 if ( !$revision ) {
242 throw new RevisionAccessException(
243 'Revision {revId} not found',
244 [ 'revId' => $revId ]
245 );
246 }
247 }
248
249 if ( $page->getId() !== $revision->getPageId() ) {
250 if ( $lenientRevHandling ) {
251 $page = $this->pageLookup->getPageById( $revision->getPageId() );
252 if ( !$page ) {
253 // This should ideally never trigger!
254 throw new \RuntimeException(
255 "Unexpected NULL page for pageid " . $revision->getPageId() .
256 " from revision " . $revision->getId()
257 );
258 }
259 // Don't cache this!
260 $uncacheable = true;
261 } else {
262 throw new RevisionAccessException(
263 'Revision {revId} does not belong to page {name}',
264 [ 'name' => $page->getDBkey(), 'revId' => $revision->getId() ]
265 );
266 }
267 }
268
269 return [ $page, $revision, $uncacheable ];
270 }
271
272 private function adjustParserOptions( RevisionRecord $revision, ParserOptions $parserOpts ): void {
273 $mainSlot = $revision->getSlot( SlotRecord::MAIN );
274 $contentModel = $mainSlot->getModel();
275 if ( $this->supportsContentModel( $contentModel ) ) {
276 // Since we know Parsoid supports this content model, explicitly
277 // call ParserOptions::setUseParsoid. This ensures that when
278 // we query the parser-cache, the right cache key is called.
279 // This is an optional transition step to using ParserOutputAccess.
280 $parserOpts->setUseParsoid();
281 }
282 }
283}
const CONTENT_MODEL_WIKITEXT
Definition Defines.php:220
const CONTENT_FORMAT_WIKITEXT
Wikitext.
Definition Defines.php:236
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
Exception thrown when an unregistered content model is requested.
Service for getting rendered output of a given page.
ParserOutput is a rendering of a Content object or a message.
MediaWiki service for getting rendered page content.
getCachedParserOutput(PageIdentity $page, ParserOptions $parserOpts, $revision=null, bool $lenientRevHandling=false)
parseUncacheable(PageIdentity $page, ParserOptions $parserOpts, $revision, bool $lenientRevHandling=false)
This is to be called only for parsing posted wikitext that is actually not part of any real revision.
getParserOutput(PageIdentity $page, ParserOptions $parserOpts, $revision=null, int $options=0, bool $lenientRevHandling=false)
__construct(ParsoidParserFactory $parsoidParserFactory, ParserOutputAccess $parserOutputAccess, PageLookup $pageLookup, RevisionLookup $revisionLookup, SiteConfig $siteConfig, IContentHandlerFactory $contentHandlerFactory)
Exception representing a failure to look up a revision.
Page revision base class.
Value object representing a content slot associated with a page revision.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Set options of the Parser.
setUseParsoid()
Request Parsoid-format HTML output.
Interface for objects (potentially) representing an editable wiki page.
Service for looking up information about wiki pages.
Data record representing a page that is (or used to be, or could be) an editable page on a wiki.
Service for looking up page revisions.
Copyright (C) 2011-2022 Wikimedia Foundation and others.