MediaWiki master
AllMessagesTablePager.php
Go to the documentation of this file.
1<?php
22namespace MediaWiki\Pager;
23
35use stdClass;
40
48
52 protected $foreign;
53
57 protected $prefix;
58
62 protected $suffix;
63
67 public $lang;
68
72 public $custom;
73
74 private LocalisationCache $localisationCache;
75
76 public function __construct(
77 IContextSource $context,
78 Language $contentLanguage,
79 LanguageFactory $languageFactory,
80 LinkRenderer $linkRenderer,
81 IConnectionProvider $dbProvider,
82 LocalisationCache $localisationCache,
83 FormOptions $opts
84 ) {
85 // Set database before parent constructor to avoid setting it there
86 $this->mDb = $dbProvider->getReplicaDatabase();
87 parent::__construct( $context, $linkRenderer );
88 $this->localisationCache = $localisationCache;
89
90 $this->mIndexField = 'am_title';
91 // FIXME: Why does this need to be set to DIR_DESCENDING to produce ascending ordering?
92 $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
93
94 $this->lang = $languageFactory->getRawLanguage( $opts->getValue( 'lang' ) );
95
96 $this->foreign = !$this->lang->equals( $contentLanguage );
97
98 $filter = $opts->getValue( 'filter' );
99 if ( $filter === 'all' ) {
100 $this->custom = null; // So won't match in either case
101 } else {
102 $this->custom = ( $filter === 'unmodified' );
103 }
104
105 $prefix = $this->getLanguage()->ucfirst( $opts->getValue( 'prefix' ) );
106 $prefix = $prefix !== '' ?
107 Title::makeTitleSafe( NS_MEDIAWIKI, $opts->getValue( 'prefix' ) ) :
108 null;
109
110 if ( $prefix !== null ) {
111 $displayPrefix = $prefix->getDBkey();
112 $this->prefix = '/^' . preg_quote( $displayPrefix, '/' ) . '/i';
113 } else {
114 $this->prefix = false;
115 }
116
117 // The suffix that may be needed for message names if we're in a
118 // different language (eg [[MediaWiki:Foo/fr]]: $suffix = '/fr'
119 if ( $this->foreign ) {
120 $this->suffix = '/' . $this->lang->getCode();
121 } else {
122 $this->suffix = '';
123 }
124 }
125
126 private function getAllMessages( bool $descending ): array {
127 $messageNames = $this->localisationCache->getSubitemList( 'en', 'messages' );
128
129 // Normalise message names so they look like page titles and sort correctly - T86139
130 $messageNames = array_map( [ $this->lang, 'ucfirst' ], $messageNames );
131
132 if ( $descending ) {
133 rsort( $messageNames );
134 } else {
135 asort( $messageNames );
136 }
137
138 return $messageNames;
139 }
140
155 public static function getCustomisedStatuses(
156 $messageNames,
157 $langcode = 'en',
158 $foreign = false,
159 ?IReadableDatabase $dbr = null
160 ) {
161 // FIXME: This function should be moved to Language:: or something.
162 // Fallback to global state, if not provided
163 $dbr ??= MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
164 $res = $dbr->newSelectQueryBuilder()
165 ->select( [ 'page_namespace', 'page_title' ] )
166 ->from( 'page' )
167 ->where( [ 'page_namespace' => [ NS_MEDIAWIKI, NS_MEDIAWIKI_TALK ] ] )
168 ->useIndex( 'page_name_title' )
169 ->caller( __METHOD__ )->fetchResultSet();
170 $xNames = array_fill_keys( $messageNames, true );
171
172 $pageFlags = $talkFlags = [];
173
174 foreach ( $res as $s ) {
175 $exists = false;
176
177 if ( $foreign ) {
178 $titleParts = explode( '/', $s->page_title );
179 if ( count( $titleParts ) === 2 &&
180 $langcode === $titleParts[1] &&
181 isset( $xNames[$titleParts[0]] )
182 ) {
183 $exists = $titleParts[0];
184 }
185 } elseif ( isset( $xNames[$s->page_title] ) ) {
186 $exists = $s->page_title;
187 }
188
189 $title = Title::newFromRow( $s );
190 if ( $exists && $title->inNamespace( NS_MEDIAWIKI ) ) {
191 $pageFlags[$exists] = true;
192 } elseif ( $exists && $title->inNamespace( NS_MEDIAWIKI_TALK ) ) {
193 $talkFlags[$exists] = true;
194 }
195 }
196
197 return [ 'pages' => $pageFlags, 'talks' => $talkFlags ];
198 }
199
208 public function reallyDoQuery( $offset, $limit, $order ) {
209 $asc = ( $order === self::QUERY_ASCENDING );
210
211 $messageNames = $this->getAllMessages( $order );
212 $statuses = self::getCustomisedStatuses(
213 $messageNames,
214 $this->lang->getCode(),
215 $this->foreign,
216 $this->getDatabase()
217 );
218
219 $rows = [];
220 $count = 0;
221 foreach ( $messageNames as $key ) {
222 $customised = isset( $statuses['pages'][$key] );
223 if ( $customised !== $this->custom &&
224 ( ( $asc && ( $key < $offset || !$offset ) ) || ( !$asc && $key > $offset ) ) &&
225 ( ( $this->prefix && preg_match( $this->prefix, $key ) ) || $this->prefix === false )
226 ) {
227 $actual = $this->msg( $key )->inLanguage( $this->lang )->plain();
228 $default = $this->msg( $key )->inLanguage( $this->lang )->useDatabase( false )->plain();
229 $rows[] = [
230 'am_title' => $key,
231 'am_actual' => $actual,
232 'am_default' => $default,
233 'am_customised' => $customised,
234 'am_talk_exists' => isset( $statuses['talks'][$key] )
235 ];
236 $count++;
237 }
238
239 if ( $count === $limit ) {
240 break;
241 }
242 }
243
244 return new FakeResultWrapper( $rows );
245 }
246
247 protected function getStartBody() {
248 return Html::openElement( 'table', [
249 'class' => $this->getTableClass(),
250 'id' => 'mw-allmessagestable'
251 ] ) .
252 "\n" .
253 "<thead><tr>
254 <th rowspan=\"2\">" .
255 $this->msg( 'allmessagesname' )->escaped() . "
256 </th>
257 <th>" .
258 $this->msg( 'allmessagesdefault' )->escaped() .
259 "</th>
260 </tr>\n
261 <tr>
262 <th>" .
263 $this->msg( 'allmessagescurrent' )->escaped() .
264 "</th>
265 </tr></thead>\n";
266 }
267
268 protected function getEndBody() {
269 return Html::closeElement( 'table' );
270 }
271
277 public function formatValue( $field, $value ) {
278 $linkRenderer = $this->getLinkRenderer();
279 switch ( $field ) {
280 case 'am_title':
281 $title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
282 $talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix );
283 $message = $this->msg( $value )->inLanguage( $this->lang )->useDatabase( false )->plain();
284 $translation = $linkRenderer->makeExternalLink(
285 'https://translatewiki.net/w/i.php?' . wfArrayToCgi( [
286 'title' => 'Special:SearchTranslations',
287 'group' => 'mediawiki',
288 'grouppath' => 'mediawiki',
289 'language' => $this->lang->getCode(),
290 'query' => $value . ' ' . $message
291 ] ),
292 $this->msg( 'allmessages-filter-translate' ),
293 $this->getTitle()
294 );
295 $talkLink = $this->msg( 'talkpagelinktext' )->text();
296
297 if ( $this->mCurrentRow->am_customised ) {
298 $title = $linkRenderer->makeKnownLink( $title, $this->getLanguage()->lcfirst( $value ) );
299 } else {
300 $title = $linkRenderer->makeBrokenLink(
301 $title, $this->getLanguage()->lcfirst( $value )
302 );
303 }
304 if ( $this->mCurrentRow->am_talk_exists ) {
305 $talk = $linkRenderer->makeKnownLink( $talk, $talkLink );
306 } else {
307 $talk = $linkRenderer->makeBrokenLink(
308 $talk,
309 $talkLink
310 );
311 }
312
313 return $title . ' ' .
314 $this->msg( 'parentheses' )->rawParams( $talk )->escaped() .
315 ' ' .
316 $this->msg( 'parentheses' )->rawParams( $translation )->escaped();
317
318 case 'am_default':
319 case 'am_actual':
320 return Sanitizer::escapeHtmlAllowEntities( $value );
321 }
322
323 return '';
324 }
325
330 public function formatRow( $row ) {
331 // Do all the normal stuff
332 $s = parent::formatRow( $row );
333
334 // But if there's a customised message, add that too.
335 if ( $row->am_customised ) {
336 $s .= Html::openElement( 'tr', $this->getRowAttrs( $row ) );
337 $formatted = strval( $this->formatValue( 'am_actual', $row->am_actual ) );
338
339 if ( $formatted === '' ) {
340 $formatted = "\u{00A0}";
341 }
342
343 $s .= Html::rawElement( 'td', $this->getCellAttrs( 'am_actual', $row->am_actual ), $formatted )
344 . Html::closeElement( 'tr' );
345 }
346
347 return Html::rawElement( 'tbody', [], $s );
348 }
349
350 protected function getRowAttrs( $row ) {
351 return [];
352 }
353
359 protected function getCellAttrs( $field, $value ) {
360 $attr = [];
361 if ( $field === 'am_title' ) {
362 if ( $this->mCurrentRow->am_customised ) {
363 $attr += [ 'rowspan' => '2' ];
364 }
365 } else {
366 $attr += [
367 'lang' => $this->lang->getHtmlCode(),
368 'dir' => $this->lang->getDir(),
369 ];
370 if ( $this->mCurrentRow->am_customised ) {
371 // CSS class: am_default, am_actual
372 $attr += [ 'class' => $field ];
373 }
374 }
375 return $attr;
376 }
377
378 // This is not actually used, as getStartBody is overridden above
379 protected function getFieldNames() {
380 return [
381 'am_title' => $this->msg( 'allmessagesname' )->text(),
382 'am_default' => $this->msg( 'allmessagesdefault' )->text()
383 ];
384 }
385
386 public function getTitle() {
387 return SpecialPage::getTitleFor( 'Allmessages', false );
388 }
389
390 protected function isFieldSortable( $x ) {
391 return false;
392 }
393
394 public function getDefaultSort() {
395 return '';
396 }
397
398 public function getQueryInfo() {
399 return [];
400 }
401
402}
403
408class_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....
Caching for the contents of localisation files.
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:57
Base class for language-specific code.
Definition Language.php:81
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)
static getCustomisedStatuses( $messageNames, $langcode='en', $foreign=false, ?IReadableDatabase $dbr=null)
Determine which of the MediaWiki and MediaWiki_talk namespace pages exist.
getDefaultSort()
The database field name used as a default sort order.
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 DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
Table-based display with a user-selectable sort order.
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:78
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.