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