MediaWiki REL1_39
HistoryPager.php
Go to the documentation of this file.
1<?php
31
36#[\AllowDynamicProperties]
38
39 public $mGroupByDate = true;
40
41 public $historyPage, $buttons, $conds;
42
43 protected $oldIdChecked;
44
45 protected $preventClickjacking = false;
49 protected $parentLens;
50
52 protected $showTagEditUI;
53
55 private $tagFilter;
56
58 private $revisionStore;
59
61 private $watchlistManager;
62
64 private $linkBatchFactory;
65
67 private $commentFormatter;
68
72 private $revisions = [];
73
77 private $formattedComments = [];
78
90 public function __construct(
91 HistoryAction $historyPage,
92 $year = 0,
93 $month = 0,
94 $tagFilter = '',
95 array $conds = [],
96 $day = 0,
97 LinkBatchFactory $linkBatchFactory = null,
98 WatchlistManager $watchlistManager = null,
99 CommentFormatter $commentFormatter = null
100 ) {
101 parent::__construct( $historyPage->getContext() );
102 $this->historyPage = $historyPage;
103 $this->tagFilter = $tagFilter;
104 $this->getDateCond( $year, $month, $day );
105 $this->conds = $conds;
106 $this->showTagEditUI = ChangeTags::showTagEditingUI( $this->getAuthority() );
107 $services = MediaWikiServices::getInstance();
108 $this->revisionStore = $services->getRevisionStore();
109 $this->linkBatchFactory = $linkBatchFactory ?? $services->getLinkBatchFactory();
110 $this->watchlistManager = $watchlistManager
111 ?? $services->getWatchlistManager();
112 $this->commentFormatter = $commentFormatter ?? $services->getCommentFormatter();
113 }
114
115 // For hook compatibility...
116 public function getArticle() {
117 return $this->historyPage->getArticle();
118 }
119
120 protected function getSqlComment() {
121 if ( $this->conds ) {
122 return 'history page filtered'; // potentially slow, see CR r58153
123 } else {
124 return 'history page unfiltered';
125 }
126 }
127
128 public function getQueryInfo() {
129 $revQuery = $this->revisionStore->getQueryInfo( [ 'user' ] );
130
131 $queryInfo = [
132 'tables' => $revQuery['tables'],
133 'fields' => $revQuery['fields'],
134 'conds' => array_merge(
135 [ 'rev_page' => $this->getWikiPage()->getId() ],
136 $this->conds ),
137 'options' => [ 'USE INDEX' => [ 'revision' => 'rev_page_timestamp' ] ],
138 'join_conds' => $revQuery['joins'],
139 ];
141 $queryInfo['tables'],
142 $queryInfo['fields'],
143 $queryInfo['conds'],
144 $queryInfo['join_conds'],
145 $queryInfo['options'],
146 $this->tagFilter
147 );
148
149 $this->getHookRunner()->onPageHistoryPager__getQueryInfo( $this, $queryInfo );
150
151 return $queryInfo;
152 }
153
154 public function getIndexField() {
155 return [ [ 'rev_timestamp', 'rev_id' ] ];
156 }
157
162 public function formatRow( $row ) {
163 $notifTimestamp = $this->getConfig()->get( MainConfigNames::ShowUpdatedMarker )
164 ? $this->watchlistManager
165 ->getTitleNotificationTimestamp( $this->getUser(), $this->getTitle() )
166 : false;
167
168 return $this->historyLine( $row, $notifTimestamp, $this->getResultOffset() );
169 }
170
171 protected function doBatchLookups() {
172 if ( !$this->getHookRunner()->onPageHistoryPager__doBatchLookups( $this, $this->mResult ) ) {
173 return;
174 }
175
176 # Do a link batch query
177 $batch = $this->linkBatchFactory->newLinkBatch();
178 $revIds = [];
179 $title = $this->getTitle();
180 foreach ( $this->mResult as $row ) {
181 if ( $row->rev_parent_id ) {
182 $revIds[] = (int)$row->rev_parent_id;
183 }
184 if ( $row->user_name !== null ) {
185 $batch->add( NS_USER, $row->user_name );
186 $batch->add( NS_USER_TALK, $row->user_name );
187 } else { # for anons or usernames of imported revisions
188 $batch->add( NS_USER, $row->rev_user_text );
189 $batch->add( NS_USER_TALK, $row->rev_user_text );
190 }
191 $this->revisions[] = $this->revisionStore->newRevisionFromRow(
192 $row,
193 RevisionStore::READ_NORMAL,
194 $title
195 );
196 }
197 $this->parentLens = $this->revisionStore->getRevisionSizes( $revIds );
198 $batch->execute();
199
200 # The keys of $this->formattedComments will be the same as the keys of $this->revisions
201 $this->formattedComments = $this->commentFormatter->createRevisionBatch()
202 ->revisions( $this->revisions )
203 ->authority( $this->getAuthority() )
204 ->samePage( false )
205 ->hideIfDeleted( true )
206 ->useParentheses( false )
207 ->execute();
208
209 $this->mResult->seek( 0 );
210 }
211
216 protected function getEmptyBody() {
217 return $this->msg( 'history-empty' )->escaped();
218 }
219
225 protected function getStartBody() {
226 $this->oldIdChecked = 0;
227 $s = '';
228 // Button container stored in $this->buttons for re-use in getEndBody()
229 $this->buttons = '';
230 if ( $this->getNumRows() > 0 ) {
231 $this->getOutput()->wrapWikiMsg( "<div class='mw-history-legend'>\n$1\n</div>", 'histlegend' );
232 $s = Html::openElement( 'form', [
233 'action' => wfScript(),
234 'id' => 'mw-history-compare'
235 ] ) . "\n";
236 $s .= Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n";
237 $s .= Html::hidden( 'action', 'historysubmit' ) . "\n";
238 $s .= Html::hidden( 'type', 'revision' ) . "\n";
239
240 $this->buttons .= Html::openElement(
241 'div', [ 'class' => 'mw-history-compareselectedversions' ] );
242 $className = 'historysubmit mw-history-compareselectedversions-button mw-ui-button';
243 $attrs = [ 'class' => $className ]
244 + Linker::tooltipAndAccesskeyAttribs( 'compareselectedversions' );
245 $this->buttons .= $this->submitButton( $this->msg( 'compareselectedversions' )->text(),
246 $attrs
247 ) . "\n";
248
249 $actionButtons = '';
250 if ( $this->getAuthority()->isAllowed( 'deleterevision' ) ) {
251 $actionButtons .= $this->getRevisionButton(
252 'revisiondelete', 'showhideselectedversions' );
253 }
254 if ( $this->showTagEditUI ) {
255 $actionButtons .= $this->getRevisionButton(
256 'editchangetags', 'history-edit-tags' );
257 }
258 if ( $actionButtons ) {
259 $this->buttons .= Xml::tags( 'div', [ 'class' =>
260 'mw-history-revisionactions' ], $actionButtons );
261 }
262
263 if ( $this->getAuthority()->isAllowed( 'deleterevision' ) || $this->showTagEditUI ) {
264 $this->buttons .= ( new ListToggle( $this->getOutput() ) )->getHTML();
265 }
266
267 $this->buttons .= '</div>';
268
269 $s .= $this->buttons;
270 }
271
272 $s .= '<section id="pagehistory" class="mw-pager-body">';
273
274 return $s;
275 }
276
277 private function getRevisionButton( $name, $msg ) {
278 $this->setPreventClickjacking( true );
279 $element = Html::element(
280 'button',
281 [
282 'type' => 'submit',
283 'name' => $name,
284 'value' => '1',
285 'class' => "historysubmit mw-history-$name-button mw-ui-button",
286 ],
287 $this->msg( $msg )->text()
288 ) . "\n";
289 return $element;
290 }
291
292 protected function getEndBody() {
293 if ( $this->getNumRows() == 0 ) {
294 return '';
295 }
296 $s = '';
297 # Add second buttons only if there is more than one rev
298 if ( $this->getNumRows() > 2 ) {
299 $s .= $this->buttons;
300 }
301 $s .= '</section>'; // closes section#pagehistory
302 $s .= '</form>';
303 return $s;
304 }
305
313 private function submitButton( $message, $attributes = [] ) {
314 # Disable submit button if history has 1 revision only
315 if ( $this->getNumRows() > 1 ) {
316 return Html::submitButton( $message, $attributes );
317 } else {
318 return '';
319 }
320 }
321
332 private function historyLine( $row, $notificationtimestamp, $resultOffset ) {
333 $numRows = min( $this->mResult->numRows(), $this->mLimit );
334
335 $firstInList = $resultOffset === ( $this->mIsBackwards ? $numRows - 1 : 0 );
336 // Next in the list, previous in chronological order.
337 $nextResultOffset = $resultOffset + ( $this->mIsBackwards ? -1 : 1 );
338
339 $revRecord = $this->revisions[$resultOffset];
340 // This may only be null if the current line is the last one in the list.
341 $previousRevRecord = $this->revisions[$nextResultOffset] ?? null;
342
343 $latest = $revRecord->getId() === $this->getWikiPage()->getLatest();
344 $curlink = $this->curLink( $revRecord );
345 if ( $previousRevRecord ) {
346 // Display a link to compare to the previous revision
347 $lastlink = $this->lastLink( $revRecord, $previousRevRecord );
348 } elseif ( $this->mIsBackwards && $this->mOffset !== '' ) {
349 // When paging "backwards", we don't have the extra result for the next revision that would
350 // appear in the list, and we don't know whether this is the oldest revision or not.
351 // However, if an offset has been specified, then the user probably reached this page by
352 // navigating from the "next" page, therefore the next revision probably exists.
353 // Display a link using &oldid=prev (this skips some checks but that's fine).
354 $lastlink = $this->lastLink( $revRecord, null );
355 } else {
356 // Do not display a link, because this is the oldest revision of the page
357 $lastlink = $this->historyPage->message['last'];
358 }
359 $curLastlinks = Html::rawElement( 'span', [], $curlink ) .
360 Html::rawElement( 'span', [], $lastlink );
361 $histLinks = Html::rawElement(
362 'span',
363 [ 'class' => 'mw-history-histlinks mw-changeslist-links' ],
364 $curLastlinks
365 );
366
367 $diffButtons = $this->diffButtons( $revRecord, $firstInList );
368 $s = $histLinks . $diffButtons;
369
370 $link = $this->revLink( $revRecord );
371 $classes = [];
372
373 $del = '';
374 $user = $this->getUser();
375 $canRevDelete = $this->getAuthority()->isAllowed( 'deleterevision' );
376 // Show checkboxes for each revision, to allow for revision deletion and
377 // change tags
378 if ( $canRevDelete || $this->showTagEditUI ) {
379 $this->setPreventClickjacking( true );
380 // If revision was hidden from sysops and we don't need the checkbox
381 // for anything else, disable it
382 if ( !$this->showTagEditUI
383 && !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $this->getAuthority() )
384 ) {
385 $del = Xml::check( 'deleterevisions', false, [ 'disabled' => 'disabled' ] );
386 // Otherwise, enable the checkbox...
387 } else {
388 $del = Xml::check( 'showhiderevisions', false,
389 [ 'name' => 'ids[' . $revRecord->getId() . ']' ] );
390 }
391 // User can only view deleted revisions...
392 } elseif ( $revRecord->getVisibility() && $this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
393 // If revision was hidden from sysops, disable the link
394 if ( !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $this->getAuthority() ) ) {
395 $del = Linker::revDeleteLinkDisabled( false );
396 // Otherwise, show the link...
397 } else {
398 $query = [
399 'type' => 'revision',
400 'target' => $this->getTitle()->getPrefixedDBkey(),
401 'ids' => $revRecord->getId()
402 ];
403 $del .= Linker::revDeleteLink(
404 $query,
405 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
406 false
407 );
408 }
409 }
410 if ( $del ) {
411 $s .= " $del ";
412 }
413
414 $lang = $this->getLanguage();
415 $dirmark = $lang->getDirMark();
416
417 $s .= " $link";
418 $s .= $dirmark;
419 $s .= " <span class='history-user'>" .
420 Linker::revUserTools( $revRecord, true, false ) . "</span>";
421 $s .= $dirmark;
422
423 if ( $revRecord->isMinor() ) {
424 $s .= ' ' . ChangesList::flag( 'minor', $this->getContext() );
425 }
426
427 # Sometimes rev_len isn't populated
428 if ( $revRecord->getSize() !== null ) {
429 # Size is always public data
430 $prevSize = $this->parentLens[$row->rev_parent_id] ?? 0;
431 $sDiff = ChangesList::showCharacterDifference( $prevSize, $revRecord->getSize() );
432 $fSize = Linker::formatRevisionSize( $revRecord->getSize() );
433 $s .= ' <span class="mw-changeslist-separator"></span> ' . "$fSize $sDiff";
434 }
435
436 # Text following the character difference is added just before running hooks
437 $s2 = $this->formattedComments[$resultOffset];
438
439 if ( $s2 === '' ) {
440 $defaultComment = $this->msg( 'changeslist-nocomment' )->escaped();
441 $s2 = "<span class=\"comment mw-comment-none\">$defaultComment</span>";
442 }
443
444 if ( $notificationtimestamp && ( $row->rev_timestamp >= $notificationtimestamp ) ) {
445 $s2 .= ' <span class="updatedmarker">' . $this->msg( 'updatedmarker' )->escaped() . '</span>';
446 $classes[] = 'mw-history-line-updated';
447 }
448
449 $tools = [];
450
451 # Rollback and undo links
452
453 if ( $previousRevRecord && $this->getAuthority()->probablyCan( 'edit', $this->getTitle() ) ) {
454 if ( $latest && $this->getAuthority()->probablyCan( 'rollback', $this->getTitle() )
455 ) {
456 // Get a rollback link without the brackets
457 $rollbackLink = Linker::generateRollback(
458 $revRecord,
459 $this->getContext(),
460 [ 'verify', 'noBrackets' ]
461 );
462 if ( $rollbackLink ) {
463 $this->setPreventClickjacking( true );
464 $tools['mw-rollback'] = $rollbackLink;
465 }
466 }
467
468 if ( !$revRecord->isDeleted( RevisionRecord::DELETED_TEXT )
469 && !$previousRevRecord->isDeleted( RevisionRecord::DELETED_TEXT )
470 ) {
471 # Create undo tooltip for the first (=latest) line only
472 $undoTooltip = $latest
473 ? [ 'title' => $this->msg( 'tooltip-undo' )->text() ]
474 : [];
475 $undolink = $this->getLinkRenderer()->makeKnownLink(
476 $this->getTitle(),
477 $this->msg( 'editundo' )->text(),
478 $undoTooltip,
479 [
480 'action' => 'edit',
481 'undoafter' => $previousRevRecord->getId(),
482 'undo' => $revRecord->getId()
483 ]
484 );
485 $tools['mw-undo'] = "<span class=\"mw-history-undo\">{$undolink}</span>";
486 }
487 }
488 // Allow extension to add their own links here
489 $this->getHookRunner()->onHistoryTools(
490 $revRecord,
491 $tools,
492 $previousRevRecord,
493 $user
494 );
495
496 if ( $tools ) {
497 $s2 .= ' ' . Html::openElement( 'span', [ 'class' => 'mw-changeslist-links' ] );
498 foreach ( $tools as $tool ) {
499 $s2 .= Html::rawElement( 'span', [], $tool );
500 }
501 $s2 .= Html::closeElement( 'span' );
502 }
503
504 # Tags
505 list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow(
506 $row->ts_tags,
507 'history',
508 $this->getContext()
509 );
510 $classes = array_merge( $classes, $newClasses );
511 if ( $tagSummary !== '' ) {
512 $s2 .= " $tagSummary";
513 }
514
515 # Include separator between character difference and following text
516 $s .= ' <span class="mw-changeslist-separator"></span> ' . $s2;
517
518 $attribs = [ 'data-mw-revid' => $revRecord->getId() ];
519
520 $this->getHookRunner()->onPageHistoryLineEnding( $this, $row, $s, $classes, $attribs );
521 $attribs = array_filter( $attribs,
522 [ Sanitizer::class, 'isReservedDataAttribute' ],
523 ARRAY_FILTER_USE_KEY
524 );
525
526 if ( $classes ) {
527 $attribs['class'] = implode( ' ', $classes );
528 }
529
530 return Xml::tags( 'li', $attribs, $s ) . "\n";
531 }
532
539 private function revLink( RevisionRecord $rev ) {
540 return ChangesList::revDateLink( $rev, $this->getAuthority(), $this->getLanguage(),
541 $this->getTitle() );
542 }
543
550 private function curLink( RevisionRecord $rev ) {
551 $cur = $this->historyPage->message['cur'];
552 $latest = $this->getWikiPage()->getLatest();
553 if ( $latest === $rev->getId()
554 || !$rev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() )
555 ) {
556 return $cur;
557 } else {
558 return $this->getLinkRenderer()->makeKnownLink(
559 $this->getTitle(),
560 new HtmlArmor( $cur ),
561 [
562 'title' => $this->historyPage->message['tooltip-cur']
563 ],
564 [
565 'diff' => $latest,
566 'oldid' => $rev->getId()
567 ]
568 );
569 }
570 }
571
580 private function lastLink( RevisionRecord $prevRev, ?RevisionRecord $nextRev ) {
581 $last = $this->historyPage->message['last'];
582
583 if ( !$prevRev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ||
584 ( $nextRev && !$nextRev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) )
585 ) {
586 return $last;
587 }
588
589 return $this->getLinkRenderer()->makeKnownLink(
590 $this->getTitle(),
591 new HtmlArmor( $last ),
592 [
593 'title' => $this->historyPage->message['tooltip-last']
594 ],
595 [
596 'diff' => $prevRev->getId(),
597 'oldid' => $nextRev ? $nextRev->getId() : 'prev'
598 ]
599 );
600 }
601
610 private function diffButtons( RevisionRecord $rev, $firstInList ) {
611 if ( $this->getNumRows() > 1 ) {
612 $id = $rev->getId();
613 $radio = [ 'type' => 'radio', 'value' => $id ];
615 if ( $firstInList ) {
616 $first = Xml::element( 'input',
617 array_merge( $radio, [
618 // Disable the hidden radio because it can still
619 // be selected with arrow keys on Firefox
620 'disabled' => '',
621 'name' => 'oldid',
622 'id' => 'mw-oldid-null' ] )
623 );
624 $checkmark = [ 'checked' => 'checked' ];
625 } else {
626 # Check visibility of old revisions
627 if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
628 $radio['disabled'] = 'disabled';
629 $checkmark = []; // We will check the next possible one
630 } elseif ( !$this->oldIdChecked ) {
631 $checkmark = [ 'checked' => 'checked' ];
632 $this->oldIdChecked = $id;
633 } else {
634 $checkmark = [];
635 }
636 $first = Xml::element( 'input',
637 array_merge( $radio, $checkmark, [
638 'name' => 'oldid',
639 'id' => "mw-oldid-$id" ] ) );
640 $checkmark = [];
641 }
642 $second = Xml::element( 'input',
643 array_merge( $radio, $checkmark, [
644 'name' => 'diff',
645 'id' => "mw-diff-$id" ] ) );
646
647 return $first . $second;
648 } else {
649 return '';
650 }
651 }
652
658 protected function isNavigationBarShown() {
659 if ( $this->getNumRows() == 0 ) {
660 return false;
661 }
662 return parent::isNavigationBarShown();
663 }
664
668 public function getDefaultQuery() {
669 parent::getDefaultQuery();
670 unset( $this->mDefaultQuery['date-range-to'] );
671 return $this->mDefaultQuery;
672 }
673
679 private function setPreventClickjacking( bool $flag ) {
680 $this->preventClickjacking = $flag;
681 }
682
687 public function getPreventClickjacking() {
688 return $this->preventClickjacking;
689 }
690
691}
getUser()
getAuthority()
const NS_USER
Definition Defines.php:66
const NS_USER_TALK
Definition Defines.php:67
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
getContext()
getContext()
Get the IContextSource in use here.
Definition Action.php:136
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='', bool $exclude=false)
Applies all tags-related changes to a query.
static showTagEditingUI(Authority $performer)
Indicate whether change tag editing UI is relevant.
static formatSummaryRow( $tags, $page, MessageLocalizer $localizer=null)
Creates HTML for the given tags.
static revDateLink(RevisionRecord $rev, Authority $performer, Language $lang, $title=null)
Render the date and time of a revision in the current user language based on whether the user is able...
static showCharacterDifference( $old, $new, IContextSource $context=null)
Show formatted char difference.
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getWikiPage()
Get the WikiPage object.
This class handles printing the history page for an article.
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called.
isNavigationBarShown()
Returns whether to show the "navigation bar".
getEndBody()
Hook into getBody() for the end of the list.
getDefaultQuery()
Get an array of query parameters that should be put into self-links.By default, all parameters passed...
__construct(HistoryAction $historyPage, $year=0, $month=0, $tagFilter='', array $conds=[], $day=0, LinkBatchFactory $linkBatchFactory=null, WatchlistManager $watchlistManager=null, CommentFormatter $commentFormatter=null)
bool $showTagEditUI
Whether to show the tag editing UI.
getSqlComment()
Get some text to go in brackets in the "function name" part of the SQL comment.
getPreventClickjacking()
Get the "prevent clickjacking" flag.
getIndexField()
Returns the name of the index field.
getQueryInfo()
Provides all parameters needed for the main paged query.
getStartBody()
Creates begin of history list with a submit button.
getEmptyBody()
Returns message when query returns no revisions.
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition Html.php:214
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition Html.php:256
static closeElement( $element)
Returns "</$element>".
Definition Html.php:320
getNumRows()
Get the number of rows in the result set.
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2229
static generateRollback(RevisionRecord $revRecord, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
Definition Linker.php:1834
static formatRevisionSize( $size)
Definition Linker.php:1623
static revUserTools(RevisionRecord $revRecord, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition Linker.php:1371
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2205
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null, $localizer=null, $user=null, $config=null, $relevantTitle=null)
Returns the attributes for the tooltip and access key.
Definition Linker.php:2299
Class for generating clickable toggle links for a list of checkboxes.
This is the main service interface for converting single-line comments from various DB comment fields...
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Page revision base class.
userCan( $field, Authority $performer)
Determine if the give authority is allowed to view a particular field of this revision,...
getId( $wikiId=self::LOCAL)
Get revision ID.
Service for looking up page revisions.
Efficient paging for SQL queries.
getDateCond( $year, $month, $day=-1)
Set and return the mOffset timestamp such that we can get all revisions with a timestamp up to the sp...
static check( $name, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox.
Definition Xml.php:332
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition Xml.php:134
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition Xml.php:43
foreach( $mmfl['setupFiles'] as $fileName) if($queue) if(empty( $mmfl['quiet'])) $s
if(!isset( $args[0])) $lang