MediaWiki  master
RevisionRenderer.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki\Revision;
24 
25 use Html;
27 use ParserOptions;
28 use ParserOutput;
31 use Title;
32 use User;
34 
46 
49 
51  private $loadBalancer;
52 
54  private $roleRegistery;
55 
57  private $dbDomain;
58 
64  public function __construct(
65  ILoadBalancer $loadBalancer,
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  User $forUser = 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, $forUser ) ) {
116  // Returning null here is awkward, but consist with the signature of
117  // Revision::getContent() and RevisionRecord::getContent().
118  return null;
119  }
120 
121  if ( !$options ) {
122  $options = ParserOptions::newCanonical( $forUser ?: 'canonical' );
123  }
124 
125  $useMaster = $hints['use-master'] ?? false;
126 
127  $dbIndex = $useMaster
128  ? DB_MASTER // use latest values
129  : DB_REPLICA; // T154554
130 
131  $options->setSpeculativeRevIdCallback( function () use ( $dbIndex ) {
132  return $this->getSpeculativeRevId( $dbIndex );
133  } );
134  $options->setSpeculativePageIdCallback( function () use ( $dbIndex ) {
135  return $this->getSpeculativePageId( $dbIndex );
136  } );
137 
138  if ( !$rev->getId() && $rev->getTimestamp() ) {
139  // This is an unsaved revision with an already determined timestamp.
140  // Make the "current" time used during parsing match that of the revision.
141  // Any REVISION* parser variables will match up if the revision is saved.
142  $options->setTimestamp( $rev->getTimestamp() );
143  }
144 
146 
147  $renderedRevision = new RenderedRevision(
148  $title,
149  $rev,
150  $options,
151  function ( RenderedRevision $rrev, array $hints ) {
152  return $this->combineSlotOutput( $rrev, $hints );
153  },
154  $audience,
155  $forUser
156  );
157 
158  $renderedRevision->setSaveParseLogger( $this->saveParseLogger );
159 
160  if ( isset( $hints['known-revision-output'] ) ) {
161  $renderedRevision->setRevisionParserOutput( $hints['known-revision-output'] );
162  }
163 
164  return $renderedRevision;
165  }
166 
167  private function getSpeculativeRevId( $dbIndex ) {
168  // Use a separate master connection in order to see the latest data, by avoiding
169  // stale data from REPEATABLE-READ snapshots.
170  $flags = ILoadBalancer::CONN_TRX_AUTOCOMMIT;
171 
172  $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->dbDomain, $flags );
173 
174  return 1 + (int)$db->selectField(
175  'revision',
176  'MAX(rev_id)',
177  [],
178  __METHOD__
179  );
180  }
181 
182  private function getSpeculativePageId( $dbIndex ) {
183  // Use a separate master connection in order to see the latest data, by avoiding
184  // stale data from REPEATABLE-READ snapshots.
185  $flags = ILoadBalancer::CONN_TRX_AUTOCOMMIT;
186 
187  $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->dbDomain, $flags );
188 
189  return 1 + (int)$db->selectField(
190  'page',
191  'MAX(page_id)',
192  [],
193  __METHOD__
194  );
195  }
196 
207  private function combineSlotOutput( RenderedRevision $rrev, array $hints = [] ) {
208  $revision = $rrev->getRevision();
209  $slots = $revision->getSlots()->getSlots();
210 
211  $withHtml = $hints['generate-html'] ?? true;
212 
213  // short circuit if there is only the main slot
214  if ( array_keys( $slots ) === [ SlotRecord::MAIN ] ) {
215  return $rrev->getSlotParserOutput( SlotRecord::MAIN );
216  }
217 
218  // move main slot to front
219  if ( isset( $slots[SlotRecord::MAIN] ) ) {
220  $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
221  }
222 
223  $combinedOutput = new ParserOutput( null );
224  $slotOutput = [];
225 
226  $options = $rrev->getOptions();
227  $options->registerWatcher( [ $combinedOutput, 'recordOption' ] );
228 
229  foreach ( $slots as $role => $slot ) {
230  $out = $rrev->getSlotParserOutput( $role, $hints );
231  $slotOutput[$role] = $out;
232 
233  // XXX: should the SlotRoleHandler be able to intervene here?
234  $combinedOutput->mergeInternalMetaDataFrom( $out );
235  $combinedOutput->mergeTrackingMetaDataFrom( $out );
236  }
237 
238  if ( $withHtml ) {
239  $html = '';
240  $first = true;
242  foreach ( $slotOutput as $role => $out ) {
243  $roleHandler = $this->roleRegistery->getRoleHandler( $role );
244 
245  // TODO: put more fancy layout logic here, see T200915.
246  $layout = $roleHandler->getOutputLayoutHints();
247  $display = $layout['display'] ?? 'section';
248 
249  if ( $display === 'none' ) {
250  continue;
251  }
252 
253  if ( $first ) {
254  // skip header for the first slot
255  $first = false;
256  } else {
257  // NOTE: this placeholder is hydrated by ParserOutput::getText().
258  $headText = Html::element( 'mw:slotheader', [], $role );
259  $html .= Html::rawElement( 'h1', [ 'class' => 'mw-slot-header' ], $headText );
260  }
261 
262  // XXX: do we want to put a wrapper div around the output?
263  // Do we want to let $roleHandler do that?
264  $html .= $out->getRawText();
265  $combinedOutput->mergeHtmlMetaDataFrom( $out );
266  }
267 
268  $combinedOutput->setText( $html );
269  }
270 
271  $options->registerWatcher( null );
272  return $combinedOutput;
273  }
274 
275 }
combineSlotOutput(RenderedRevision $rrev, array $hints=[])
This implements the layout for combining the output of multiple slots.
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
RenderedRevision represents the rendered representation of a revision.
The RevisionRenderer service provides access to rendered output for revisions.
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page...
getSlotParserOutput( $role, array $hints=[])
setLogger(LoggerInterface $saveParseLogger)
const DB_MASTER
Definition: defines.php:26
setSaveParseLogger(LoggerInterface $saveParseLogger)
static newCanonical( $context=null, $userLang=null)
Creates a "canonical" ParserOptions object.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
Created by PhpStorm.
LoggerInterface $saveParseLogger
SlotRoleRegistry $roleRegistery
getPageAsLinkTarget()
Returns the title of the page this revision is associated with as a LinkTarget object.
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:269
audienceCan( $field, $audience, User $user=null)
Check that the given audience has access to the given field.
getId()
Get revision ID.
getRenderedRevision(RevisionRecord $rev, ParserOptions $options=null, User $forUser=null, array $hints=[])
getTimestamp()
MCR migration note: this replaces Revision::getTimestamp.
getWikiId()
Get the ID of the wiki this revision belongs to.
Page revision base class.
const DB_REPLICA
Definition: defines.php:25
__construct(ILoadBalancer $loadBalancer, SlotRoleRegistry $roleRegistry, $dbDomain=false)