MediaWiki master
AllMessagesTablePager.php
Go to the documentation of this file.
1<?php
22namespace MediaWiki\Pager;
23
24use Language;
36use stdClass;
41
49
53 protected $foreign;
54
58 protected $prefix;
59
63 protected $suffix;
64
68 public $lang;
69
73 public $custom;
74
75 private LocalisationCache $localisationCache;
76
86 public function __construct(
87 IContextSource $context,
88 Language $contentLanguage,
89 LanguageFactory $languageFactory,
90 LinkRenderer $linkRenderer,
91 IConnectionProvider $dbProvider,
92 LocalisationCache $localisationCache,
93 FormOptions $opts
94 ) {
95 // Set database before parent constructor to avoid setting it there
96 $this->mDb = $dbProvider->getReplicaDatabase();
97 parent::__construct( $context, $linkRenderer );
98 $this->localisationCache = $localisationCache;
99
100 $this->mIndexField = 'am_title';
101 // FIXME: Why does this need to be set to DIR_DESCENDING to produce ascending ordering?
102 $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
103
104 $this->lang = $languageFactory->getRawLanguage( $opts->getValue( 'lang' ) );
105
106 $this->foreign = !$this->lang->equals( $contentLanguage );
107
108 $filter = $opts->getValue( 'filter' );
109 if ( $filter === 'all' ) {
110 $this->custom = null; // So won't match in either case
111 } else {
112 $this->custom = ( $filter === 'unmodified' );
113 }
114
115 $prefix = $this->getLanguage()->ucfirst( $opts->getValue( 'prefix' ) );
116 $prefix = $prefix !== '' ?
117 Title::makeTitleSafe( NS_MEDIAWIKI, $opts->getValue( 'prefix' ) ) :
118 null;
119
120 if ( $prefix !== null ) {
121 $displayPrefix = $prefix->getDBkey();
122 $this->prefix = '/^' . preg_quote( $displayPrefix, '/' ) . '/i';
123 } else {
124 $this->prefix = false;
125 }
126
127 // The suffix that may be needed for message names if we're in a
128 // different language (eg [[MediaWiki:Foo/fr]]: $suffix = '/fr'
129 if ( $this->foreign ) {
130 $this->suffix = '/' . $this->lang->getCode();
131 } else {
132 $this->suffix = '';
133 }
134 }
135
136 private function getAllMessages( $descending ) {
137 $messageNames = $this->localisationCache->getSubitemList( 'en', 'messages' );
138
139 // Normalise message names so they look like page titles and sort correctly - T86139
140 $messageNames = array_map( [ $this->lang, 'ucfirst' ], $messageNames );
141
142 if ( $descending ) {
143 rsort( $messageNames );
144 } else {
145 asort( $messageNames );
146 }
147
148 return $messageNames;
149 }
150
165 public static function getCustomisedStatuses(
166 $messageNames,
167 $langcode = 'en',
168 $foreign = false,
169 IReadableDatabase $dbr = null
170 ) {
171 // FIXME: This function should be moved to Language:: or something.
172 // Fallback to global state, if not provided
173 $dbr ??= MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
174 $res = $dbr->newSelectQueryBuilder()
175 ->select( [ 'page_namespace', 'page_title' ] )
176 ->from( 'page' )
177 ->where( [ 'page_namespace' => [ NS_MEDIAWIKI, NS_MEDIAWIKI_TALK ] ] )
178 ->useIndex( 'page_name_title' )
179 ->caller( __METHOD__ )->fetchResultSet();
180 $xNames = array_fill_keys( $messageNames, true );
181
182 $pageFlags = $talkFlags = [];
183
184 foreach ( $res as $s ) {
185 $exists = false;
186
187 if ( $foreign ) {
188 $titleParts = explode( '/', $s->page_title );
189 if ( count( $titleParts ) === 2 &&
190 $langcode === $titleParts[1] &&
191 isset( $xNames[$titleParts[0]] )
192 ) {
193 $exists = $titleParts[0];
194 }
195 } elseif ( isset( $xNames[$s->page_title] ) ) {
196 $exists = $s->page_title;
197 }
198
199 $title = Title::newFromRow( $s );
200 if ( $exists && $title->inNamespace( NS_MEDIAWIKI ) ) {
201 $pageFlags[$exists] = true;
202 } elseif ( $exists && $title->inNamespace( NS_MEDIAWIKI_TALK ) ) {
203 $talkFlags[$exists] = true;
204 }
205 }
206
207 return [ 'pages' => $pageFlags, 'talks' => $talkFlags ];
208 }
209
218 public function reallyDoQuery( $offset, $limit, $order ) {
219 $asc = ( $order === self::QUERY_ASCENDING );
220
221 $messageNames = $this->getAllMessages( $order );
222 $statuses = self::getCustomisedStatuses(
223 $messageNames,
224 $this->lang->getCode(),
225 $this->foreign,
226 $this->getDatabase()
227 );
228
229 $rows = [];
230 $count = 0;
231 foreach ( $messageNames as $key ) {
232 $customised = isset( $statuses['pages'][$key] );
233 if ( $customised !== $this->custom &&
234 ( ( $asc && ( $key < $offset || !$offset ) ) || ( !$asc && $key > $offset ) ) &&
235 ( ( $this->prefix && preg_match( $this->prefix, $key ) ) || $this->prefix === false )
236 ) {
237 $actual = $this->msg( $key )->inLanguage( $this->lang )->plain();
238 $default = $this->msg( $key )->inLanguage( $this->lang )->useDatabase( false )->plain();
239 $rows[] = [
240 'am_title' => $key,
241 'am_actual' => $actual,
242 'am_default' => $default,
243 'am_customised' => $customised,
244 'am_talk_exists' => isset( $statuses['talks'][$key] )
245 ];
246 $count++;
247 }
248
249 if ( $count === $limit ) {
250 break;
251 }
252 }
253
254 return new FakeResultWrapper( $rows );
255 }
256
257 protected function getStartBody() {
258 return Xml::openElement( 'table', [
259 'class' => $this->getTableClass(),
260 'id' => 'mw-allmessagestable'
261 ] ) .
262 "\n" .
263 "<thead><tr>
264 <th rowspan=\"2\">" .
265 $this->msg( 'allmessagesname' )->escaped() . "
266 </th>
267 <th>" .
268 $this->msg( 'allmessagesdefault' )->escaped() .
269 "</th>
270 </tr>\n
271 <tr>
272 <th>" .
273 $this->msg( 'allmessagescurrent' )->escaped() .
274 "</th>
275 </tr></thead>\n";
276 }
277
278 protected function getEndBody() {
279 return Html::closeElement( 'table' );
280 }
281
287 public function formatValue( $field, $value ) {
288 $linkRenderer = $this->getLinkRenderer();
289 switch ( $field ) {
290 case 'am_title':
291 $title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
292 $talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix );
293 $message = $this->msg( $value )->inLanguage( $this->lang )->useDatabase( false )->plain();
294 $translation = $linkRenderer->makeExternalLink(
295 'https://translatewiki.net/w/i.php?' . wfArrayToCgi( [
296 'title' => 'Special:SearchTranslations',
297 'group' => 'mediawiki',
298 'grouppath' => 'mediawiki',
299 'language' => $this->lang->getCode(),
300 'query' => $value . ' ' . $message
301 ] ),
302 $this->msg( 'allmessages-filter-translate' ),
303 $this->getTitle()
304 );
305 $talkLink = $this->msg( 'talkpagelinktext' )->text();
306
307 if ( $this->mCurrentRow->am_customised ) {
308 $title = $linkRenderer->makeKnownLink( $title, $this->getLanguage()->lcfirst( $value ) );
309 } else {
310 $title = $linkRenderer->makeBrokenLink(
311 $title, $this->getLanguage()->lcfirst( $value )
312 );
313 }
314 if ( $this->mCurrentRow->am_talk_exists ) {
315 $talk = $linkRenderer->makeKnownLink( $talk, $talkLink );
316 } else {
317 $talk = $linkRenderer->makeBrokenLink(
318 $talk,
319 $talkLink
320 );
321 }
322
323 return $title . ' ' .
324 $this->msg( 'parentheses' )->rawParams( $talk )->escaped() .
325 ' ' .
326 $this->msg( 'parentheses' )->rawParams( $translation )->escaped();
327
328 case 'am_default':
329 case 'am_actual':
330 return Sanitizer::escapeHtmlAllowEntities( $value );
331 }
332
333 return '';
334 }
335
340 public function formatRow( $row ) {
341 // Do all the normal stuff
342 $s = parent::formatRow( $row );
343
344 // But if there's a customised message, add that too.
345 if ( $row->am_customised ) {
346 $s .= Html::openElement( 'tr', $this->getRowAttrs( $row ) );
347 $formatted = strval( $this->formatValue( 'am_actual', $row->am_actual ) );
348
349 if ( $formatted === '' ) {
350 $formatted = "\u{00A0}";
351 }
352
353 $s .= Html::rawElement( 'td', $this->getCellAttrs( 'am_actual', $row->am_actual ), $formatted )
354 . Html::closeElement( 'tr' );
355 }
356
357 return Html::rawElement( 'tbody', [], $s );
358 }
359
360 protected function getRowAttrs( $row ) {
361 return [];
362 }
363
369 protected function getCellAttrs( $field, $value ) {
370 $attr = [];
371 if ( $field === 'am_title' ) {
372 if ( $this->mCurrentRow->am_customised ) {
373 $attr += [ 'rowspan' => '2' ];
374 }
375 } else {
376 $attr += [
377 'lang' => $this->lang->getHtmlCode(),
378 'dir' => $this->lang->getDir(),
379 ];
380 if ( $this->mCurrentRow->am_customised ) {
381 // CSS class: am_default, am_actual
382 $attr += [ 'class' => $field ];
383 }
384 }
385 return $attr;
386 }
387
388 // This is not actually used, as getStartBody is overridden above
389 protected function getFieldNames() {
390 return [
391 'am_title' => $this->msg( 'allmessagesname' )->text(),
392 'am_default' => $this->msg( 'allmessagesdefault' )->text()
393 ];
394 }
395
396 public function getTitle() {
397 return SpecialPage::getTitleFor( 'Allmessages', false );
398 }
399
400 protected function isFieldSortable( $x ) {
401 return false;
402 }
403
404 public function getDefaultSort() {
405 return '';
406 }
407
408 public function getQueryInfo() {
409 return [];
410 }
411
412}
413
418class_alias( AllMessagesTablePager::class, 'AllMessagesTablePager' );
const NS_MEDIAWIKI_TALK
Definition Defines.php:74
const NS_MEDIAWIKI
Definition Defines.php:73
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
Base class for language-specific code.
Definition Language.php:65
Caching for the contents of localisation files.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Helper class to keep track of options when mixing links and form elements.
getValue( $name)
Get the value for the given option name.
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition Html.php:240
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
getRawLanguage( $code)
Get a cached or new language object for a given language code without normalization of the language c...
Class that generates HTML for internal links.
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Use TablePager for prettified output.
reallyDoQuery( $offset, $limit, $order)
This function normally does a database query to get the results; we need to make a pretend result usi...
getRowAttrs( $row)
Get attributes to be applied to the given row.
getFieldNames()
An array mapping database field names to a textual description of the field name, for use in the tabl...
__construct(IContextSource $context, Language $contentLanguage, LanguageFactory $languageFactory, LinkRenderer $linkRenderer, IConnectionProvider $dbProvider, LocalisationCache $localisationCache, FormOptions $opts)
getDefaultSort()
The database field name used as a default sort order.
static getCustomisedStatuses( $messageNames, $langcode='en', $foreign=false, IReadableDatabase $dbr=null)
Determine which of the MediaWiki and MediaWiki_talk namespace pages exist.
isFieldSortable( $x)
Return true if the named field should be sortable by the UI, false otherwise.
getQueryInfo()
Provides all parameters needed for the main paged query.
const QUERY_ASCENDING
Backwards-compatible constant for reallyDoQuery() (do not change)
const DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
Table-based display with a user-selectable sort order.
getTableClass()
TablePager relies on mw-datatable for styling, see T214208.
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:46
Parent class for all special pages.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Represents a title within MediaWiki.
Definition Title.php:79
Module of static functions for generating XML.
Definition Xml.php:37
Overloads the relevant methods of the real ResultWrapper so it doesn't go anywhere near an actual dat...
Interface for objects which can provide a MediaWiki context on request.
Provide primary and replica IDatabase connections.
getReplicaDatabase( $domain=false, $group=null)
Get connection to a replica database.
A database connection without write operations.
Result wrapper for grabbing data queried from an IDatabase object.