MediaWiki master
RevisionRenderer.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Revision;
24
25use InvalidArgumentException;
31use Psr\Log\LoggerInterface;
32use Psr\Log\NullLogger;
34
46
48 private $saveParseLogger;
49
51 private $loadBalancer;
52
54 private $roleRegistry;
55
57 private $contentRenderer;
58
60 private $dbDomain;
61
68 public function __construct(
69 ILoadBalancer $loadBalancer,
70 SlotRoleRegistry $roleRegistry,
71 ContentRenderer $contentRenderer,
72 $dbDomain = false
73 ) {
74 $this->loadBalancer = $loadBalancer;
75 $this->roleRegistry = $roleRegistry;
76 $this->contentRenderer = $contentRenderer;
77 $this->dbDomain = $dbDomain;
78 $this->saveParseLogger = new NullLogger();
79 }
80
84 public function setLogger( LoggerInterface $saveParseLogger ) {
85 $this->saveParseLogger = $saveParseLogger;
86 }
87
110 public function getRenderedRevision(
111 RevisionRecord $rev,
112 ParserOptions $options = null,
113 Authority $forPerformer = null,
114 array $hints = []
115 ) {
116 if ( $rev->getWikiId() !== $this->dbDomain ) {
117 throw new InvalidArgumentException( 'Mismatching wiki ID ' . $rev->getWikiId() );
118 }
119
120 $audience = $hints['audience']
122
123 if ( !$rev->audienceCan( RevisionRecord::DELETED_TEXT, $audience, $forPerformer ) ) {
124 // Returning null here is awkward, but consistent with the signature of
125 // RevisionRecord::getContent().
126 return null;
127 }
128
129 if ( !$options ) {
130 $options = $forPerformer ?
131 ParserOptions::newFromUser( $forPerformer->getUser() ) :
132 ParserOptions::newFromAnon();
133 }
134
135 if ( isset( $hints['causeAction'] ) ) {
136 $options->setRenderReason( $hints['causeAction'] );
137 }
138
139 $usePrimary = $hints['use-master'] ?? false;
140
141 $dbIndex = $usePrimary
142 ? DB_PRIMARY // use latest values
143 : DB_REPLICA; // T154554
144
145 $options->setSpeculativeRevIdCallback( function () use ( $dbIndex ) {
146 return $this->getSpeculativeRevId( $dbIndex );
147 } );
148 $options->setSpeculativePageIdCallback( function () use ( $dbIndex ) {
149 return $this->getSpeculativePageId( $dbIndex );
150 } );
151
152 if ( !$rev->getId() && $rev->getTimestamp() ) {
153 // This is an unsaved revision with an already determined timestamp.
154 // Make the "current" time used during parsing match that of the revision.
155 // Any REVISION* parser variables will match up if the revision is saved.
156 $options->setTimestamp( $rev->getTimestamp() );
157 }
158
159 $renderedRevision = new RenderedRevision(
160 $rev,
161 $options,
162 $this->contentRenderer,
163 function ( RenderedRevision $rrev, array $hints ) use ( $options ) {
164 return $this->combineSlotOutput( $rrev, $options, $hints );
165 },
166 $audience,
167 $forPerformer
168 );
169
170 $renderedRevision->setSaveParseLogger( $this->saveParseLogger );
171
172 if ( isset( $hints['known-revision-output'] ) ) {
173 $renderedRevision->setRevisionParserOutput( $hints['known-revision-output'] );
174 }
175
176 return $renderedRevision;
177 }
178
179 private function getSpeculativeRevId( $dbIndex ) {
180 // Use a separate primary DB connection in order to see the latest data, by avoiding
181 // stale data from REPEATABLE-READ snapshots.
182 $flags = ILoadBalancer::CONN_TRX_AUTOCOMMIT;
183
184 $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->dbDomain, $flags );
185
186 return 1 + (int)$db->newSelectQueryBuilder()
187 ->select( 'MAX(rev_id)' )
188 ->from( 'revision' )
189 ->caller( __METHOD__ )->fetchField();
190 }
191
192 private function getSpeculativePageId( $dbIndex ) {
193 // Use a separate primary DB connection in order to see the latest data, by avoiding
194 // stale data from REPEATABLE-READ snapshots.
195 $flags = ILoadBalancer::CONN_TRX_AUTOCOMMIT;
196
197 $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->dbDomain, $flags );
198
199 return 1 + (int)$db->newSelectQueryBuilder()
200 ->select( 'MAX(page_id)' )
201 ->from( 'page' )
202 ->caller( __METHOD__ )->fetchField();
203 }
204
216 private function combineSlotOutput( RenderedRevision $rrev, ParserOptions $options, array $hints = [] ) {
217 $revision = $rrev->getRevision();
218 $slots = $revision->getSlots()->getSlots();
219
220 $withHtml = $hints['generate-html'] ?? true;
221
222 // short circuit if there is only the main slot
223 // T351026 hack: if use-parsoid is set, only return main slot output for now
224 // T351113 will remove this hack.
225 if ( array_keys( $slots ) === [ SlotRecord::MAIN ] || $options->getUseParsoid() ) {
226 return $rrev->getSlotParserOutput( SlotRecord::MAIN, $hints );
227 }
228
229 // move main slot to front
230 if ( isset( $slots[SlotRecord::MAIN] ) ) {
231 $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
232 }
233
234 $combinedOutput = new ParserOutput( null );
235 $slotOutput = [];
236
237 $options = $rrev->getOptions();
238 $options->registerWatcher( [ $combinedOutput, 'recordOption' ] );
239
240 foreach ( $slots as $role => $slot ) {
241 $out = $rrev->getSlotParserOutput( $role, $hints );
242 $slotOutput[$role] = $out;
243
244 // XXX: should the SlotRoleHandler be able to intervene here?
245 $combinedOutput->mergeInternalMetaDataFrom( $out );
246 $combinedOutput->mergeTrackingMetaDataFrom( $out );
247 }
248
249 if ( $withHtml ) {
250 $html = '';
251 $first = true;
253 foreach ( $slotOutput as $role => $out ) {
254 $roleHandler = $this->roleRegistry->getRoleHandler( $role );
255
256 // TODO: put more fancy layout logic here, see T200915.
257 $layout = $roleHandler->getOutputLayoutHints();
258 $display = $layout['display'] ?? 'section';
259
260 if ( $display === 'none' ) {
261 continue;
262 }
263
264 if ( $first ) {
265 // skip header for the first slot
266 $first = false;
267 } else {
268 // NOTE: this placeholder is hydrated by ParserOutput::getText().
269 $headText = Html::element( 'mw:slotheader', [], $role );
270 $html .= Html::rawElement( 'h1', [ 'class' => 'mw-slot-header' ], $headText );
271 }
272
273 // XXX: do we want to put a wrapper div around the output?
274 // Do we want to let $roleHandler do that?
275 $html .= $out->getRawText();
276 $combinedOutput->mergeHtmlMetaDataFrom( $out );
277 }
278
279 $combinedOutput->setRawText( $html );
280 }
281
282 $options->registerWatcher( null );
283 return $combinedOutput;
284 }
285
286}
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
ParserOutput is a rendering of a Content object or a message.
RenderedRevision represents the rendered representation of a revision.
setSaveParseLogger(LoggerInterface $saveParseLogger)
Page revision base class.
audienceCan( $field, $audience, Authority $performer=null)
Check that the given audience has access to the given field.
getWikiId()
Get the ID of the wiki this revision belongs to.
getTimestamp()
MCR migration note: this replaced Revision::getTimestamp.
getId( $wikiId=self::LOCAL)
Get revision ID.
The RevisionRenderer service provides access to rendered output for revisions.
setLogger(LoggerInterface $saveParseLogger)
__construct(ILoadBalancer $loadBalancer, SlotRoleRegistry $roleRegistry, ContentRenderer $contentRenderer, $dbDomain=false)
getRenderedRevision(RevisionRecord $rev, ParserOptions $options=null, Authority $forPerformer=null, array $hints=[])
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page.
Set options of the Parser.
setRenderReason(string $renderReason)
Sets reason for rendering the content.
setTimestamp( $x)
Timestamp used for {{CURRENTDAY}} etc.
getUseParsoid()
Parsoid-format HTML output, or legacy wikitext parser HTML?
registerWatcher( $callback)
Registers a callback for tracking which ParserOptions which are used.
setSpeculativeRevIdCallback( $x)
Callback to generate a guess for {{REVISIONID}}.
setSpeculativePageIdCallback( $x)
Callback to generate a guess for {{PAGEID}}.
This interface represents the authority associated with the current execution context,...
Definition Authority.php:37
This class is a delegate to ILBFactory for a given database cluster.
element(SerializerNode $parent, SerializerNode $node, $contents)
const DB_REPLICA
Definition defines.php:26
const DB_PRIMARY
Definition defines.php:28