MediaWiki REL1_39
RevisionRenderer.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Revision;
24
25use Html;
26use InvalidArgumentException;
30use ParserOutput;
31use Psr\Log\LoggerInterface;
32use Psr\Log\NullLogger;
34
46
48 private $saveParseLogger;
49
51 private $loadBalancer;
52
54 private $roleRegistery;
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->roleRegistery = $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
108 public function getRenderedRevision(
109 RevisionRecord $rev,
110 ParserOptions $options = null,
111 Authority $forPerformer = null,
112 array $hints = []
113 ) {
114 if ( $rev->getWikiId() !== $this->dbDomain ) {
115 throw new InvalidArgumentException( 'Mismatching wiki ID ' . $rev->getWikiId() );
116 }
117
118 $audience = $hints['audience']
120
121 if ( !$rev->audienceCan( RevisionRecord::DELETED_TEXT, $audience, $forPerformer ) ) {
122 // Returning null here is awkward, but consistent with the signature of
123 // RevisionRecord::getContent().
124 return null;
125 }
126
127 if ( !$options ) {
128 $options = ParserOptions::newCanonical(
129 $forPerformer ? $forPerformer->getUser() : 'canonical'
130 );
131 }
132
133 $usePrimary = $hints['use-master'] ?? false;
134
135 $dbIndex = $usePrimary
136 ? DB_PRIMARY // use latest values
137 : DB_REPLICA; // T154554
138
139 $options->setSpeculativeRevIdCallback( function () use ( $dbIndex ) {
140 return $this->getSpeculativeRevId( $dbIndex );
141 } );
142 $options->setSpeculativePageIdCallback( function () use ( $dbIndex ) {
143 return $this->getSpeculativePageId( $dbIndex );
144 } );
145
146 if ( !$rev->getId() && $rev->getTimestamp() ) {
147 // This is an unsaved revision with an already determined timestamp.
148 // Make the "current" time used during parsing match that of the revision.
149 // Any REVISION* parser variables will match up if the revision is saved.
150 $options->setTimestamp( $rev->getTimestamp() );
151 }
152
153 $renderedRevision = new RenderedRevision(
154 $rev,
155 $options,
156 $this->contentRenderer,
157 function ( RenderedRevision $rrev, array $hints ) {
158 return $this->combineSlotOutput( $rrev, $hints );
159 },
160 $audience,
161 $forPerformer
162 );
163
164 $renderedRevision->setSaveParseLogger( $this->saveParseLogger );
165
166 if ( isset( $hints['known-revision-output'] ) ) {
167 $renderedRevision->setRevisionParserOutput( $hints['known-revision-output'] );
168 }
169
170 return $renderedRevision;
171 }
172
173 private function getSpeculativeRevId( $dbIndex ) {
174 // Use a separate primary DB connection in order to see the latest data, by avoiding
175 // stale data from REPEATABLE-READ snapshots.
176 $flags = ILoadBalancer::CONN_TRX_AUTOCOMMIT;
177
178 $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->dbDomain, $flags );
179
180 return 1 + (int)$db->selectField(
181 'revision',
182 'MAX(rev_id)',
183 [],
184 __METHOD__
185 );
186 }
187
188 private function getSpeculativePageId( $dbIndex ) {
189 // Use a separate primary DB connection in order to see the latest data, by avoiding
190 // stale data from REPEATABLE-READ snapshots.
191 $flags = ILoadBalancer::CONN_TRX_AUTOCOMMIT;
192
193 $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->dbDomain, $flags );
194
195 return 1 + (int)$db->selectField(
196 'page',
197 'MAX(page_id)',
198 [],
199 __METHOD__
200 );
201 }
202
213 private function combineSlotOutput( RenderedRevision $rrev, array $hints = [] ) {
214 $revision = $rrev->getRevision();
215 $slots = $revision->getSlots()->getSlots();
216
217 $withHtml = $hints['generate-html'] ?? true;
218
219 // short circuit if there is only the main slot
220 if ( array_keys( $slots ) === [ SlotRecord::MAIN ] ) {
221 return $rrev->getSlotParserOutput( SlotRecord::MAIN, $hints );
222 }
223
224 // move main slot to front
225 if ( isset( $slots[SlotRecord::MAIN] ) ) {
226 $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
227 }
228
229 $combinedOutput = new ParserOutput( null );
230 $slotOutput = [];
231
232 $options = $rrev->getOptions();
233 $options->registerWatcher( [ $combinedOutput, 'recordOption' ] );
234
235 foreach ( $slots as $role => $slot ) {
236 $out = $rrev->getSlotParserOutput( $role, $hints );
237 $slotOutput[$role] = $out;
238
239 // XXX: should the SlotRoleHandler be able to intervene here?
240 $combinedOutput->mergeInternalMetaDataFrom( $out );
241 $combinedOutput->mergeTrackingMetaDataFrom( $out );
242 }
243
244 if ( $withHtml ) {
245 $html = '';
246 $first = true;
248 foreach ( $slotOutput as $role => $out ) {
249 $roleHandler = $this->roleRegistery->getRoleHandler( $role );
250
251 // TODO: put more fancy layout logic here, see T200915.
252 $layout = $roleHandler->getOutputLayoutHints();
253 $display = $layout['display'] ?? 'section';
254
255 if ( $display === 'none' ) {
256 continue;
257 }
258
259 if ( $first ) {
260 // skip header for the first slot
261 $first = false;
262 } else {
263 // NOTE: this placeholder is hydrated by ParserOutput::getText().
264 $headText = Html::element( 'mw:slotheader', [], $role );
265 $html .= Html::rawElement( 'h1', [ 'class' => 'mw-slot-header' ], $headText );
266 }
267
268 // XXX: do we want to put a wrapper div around the output?
269 // Do we want to let $roleHandler do that?
270 $html .= $out->getRawText();
271 $combinedOutput->mergeHtmlMetaDataFrom( $out );
272 }
273
274 $combinedOutput->setText( $html );
275 }
276
277 $options->registerWatcher( null );
278 return $combinedOutput;
279 }
280
281}
This class is a collection of static functions that serve two purposes:
Definition Html.php:51
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition Html.php:236
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition Html.php:214
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.
This interface represents the authority associated the current execution context, such as a web reque...
Definition Authority.php:37
Create and track the database connections and transactions for a given database cluster.
const DB_REPLICA
Definition defines.php:26
const DB_PRIMARY
Definition defines.php:28