MediaWiki master
AllMessagesTablePager.php
Go to the documentation of this file.
1<?php
22namespace MediaWiki\Pager;
23
24use Language;
36use stdClass;
41use Xml;
42
50
54 protected $foreign;
55
59 protected $prefix;
60
64 protected $suffix;
65
69 public $lang;
70
74 public $custom;
75
76 private LocalisationCache $localisationCache;
77
87 public function __construct(
88 IContextSource $context,
89 Language $contentLanguage,
90 LanguageFactory $languageFactory,
91 LinkRenderer $linkRenderer,
92 IConnectionProvider $dbProvider,
93 LocalisationCache $localisationCache,
94 FormOptions $opts
95 ) {
96 // Set database before parent constructor to avoid setting it there
97 $this->mDb = $dbProvider->getReplicaDatabase();
98 parent::__construct( $context, $linkRenderer );
99 $this->localisationCache = $localisationCache;
100
101 $this->mIndexField = 'am_title';
102 // FIXME: Why does this need to be set to DIR_DESCENDING to produce ascending ordering?
103 $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
104
105 $this->lang = $languageFactory->getRawLanguage( $opts->getValue( 'lang' ) );
106
107 $this->foreign = !$this->lang->equals( $contentLanguage );
108
109 $filter = $opts->getValue( 'filter' );
110 if ( $filter === 'all' ) {
111 $this->custom = null; // So won't match in either case
112 } else {
113 $this->custom = ( $filter === 'unmodified' );
114 }
115
116 $prefix = $this->getLanguage()->ucfirst( $opts->getValue( 'prefix' ) );
117 $prefix = $prefix !== '' ?
118 Title::makeTitleSafe( NS_MEDIAWIKI, $opts->getValue( 'prefix' ) ) :
119 null;
120
121 if ( $prefix !== null ) {
122 $displayPrefix = $prefix->getDBkey();
123 $this->prefix = '/^' . preg_quote( $displayPrefix, '/' ) . '/i';
124 } else {
125 $this->prefix = false;
126 }
127
128 // The suffix that may be needed for message names if we're in a
129 // different language (eg [[MediaWiki:Foo/fr]]: $suffix = '/fr'
130 if ( $this->foreign ) {
131 $this->suffix = '/' . $this->lang->getCode();
132 } else {
133 $this->suffix = '';
134 }
135 }
136
137 private function getAllMessages( $descending ) {
138 $messageNames = $this->localisationCache->getSubitemList( 'en', 'messages' );
139
140 // Normalise message names so they look like page titles and sort correctly - T86139
141 $messageNames = array_map( [ $this->lang, 'ucfirst' ], $messageNames );
142
143 if ( $descending ) {
144 rsort( $messageNames );
145 } else {
146 asort( $messageNames );
147 }
148
149 return $messageNames;
150 }
151
166 public static function getCustomisedStatuses(
167 $messageNames,
168 $langcode = 'en',
169 $foreign = false,
170 IReadableDatabase $dbr = null
171 ) {
172 // FIXME: This function should be moved to Language:: or something.
173 // Fallback to global state, if not provided
174 $dbr ??= MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
175 $res = $dbr->newSelectQueryBuilder()
176 ->select( [ 'page_namespace', 'page_title' ] )
177 ->from( 'page' )
178 ->where( [ 'page_namespace' => [ NS_MEDIAWIKI, NS_MEDIAWIKI_TALK ] ] )
179 ->useIndex( 'page_name_title' )
180 ->caller( __METHOD__ )->fetchResultSet();
181 $xNames = array_fill_keys( $messageNames, true );
182
183 $pageFlags = $talkFlags = [];
184
185 foreach ( $res as $s ) {
186 $exists = false;
187
188 if ( $foreign ) {
189 $titleParts = explode( '/', $s->page_title );
190 if ( count( $titleParts ) === 2 &&
191 $langcode === $titleParts[1] &&
192 isset( $xNames[$titleParts[0]] )
193 ) {
194 $exists = $titleParts[0];
195 }
196 } elseif ( isset( $xNames[$s->page_title] ) ) {
197 $exists = $s->page_title;
198 }
199
200 $title = Title::newFromRow( $s );
201 if ( $exists && $title->inNamespace( NS_MEDIAWIKI ) ) {
202 $pageFlags[$exists] = true;
203 } elseif ( $exists && $title->inNamespace( NS_MEDIAWIKI_TALK ) ) {
204 $talkFlags[$exists] = true;
205 }
206 }
207
208 return [ 'pages' => $pageFlags, 'talks' => $talkFlags ];
209 }
210
219 public function reallyDoQuery( $offset, $limit, $order ) {
220 $asc = ( $order === self::QUERY_ASCENDING );
221
222 $messageNames = $this->getAllMessages( $order );
223 $statuses = self::getCustomisedStatuses(
224 $messageNames,
225 $this->lang->getCode(),
226 $this->foreign,
227 $this->getDatabase()
228 );
229
230 $rows = [];
231 $count = 0;
232 foreach ( $messageNames as $key ) {
233 $customised = isset( $statuses['pages'][$key] );
234 if ( $customised !== $this->custom &&
235 ( ( $asc && ( $key < $offset || !$offset ) ) || ( !$asc && $key > $offset ) ) &&
236 ( ( $this->prefix && preg_match( $this->prefix, $key ) ) || $this->prefix === false )
237 ) {
238 $actual = $this->msg( $key )->inLanguage( $this->lang )->plain();
239 $default = $this->msg( $key )->inLanguage( $this->lang )->useDatabase( false )->plain();
240 $rows[] = [
241 'am_title' => $key,
242 'am_actual' => $actual,
243 'am_default' => $default,
244 'am_customised' => $customised,
245 'am_talk_exists' => isset( $statuses['talks'][$key] )
246 ];
247 $count++;
248 }
249
250 if ( $count === $limit ) {
251 break;
252 }
253 }
254
255 return new FakeResultWrapper( $rows );
256 }
257
258 protected function getStartBody() {
259 return Xml::openElement( 'table', [
260 'class' => $this->getTableClass(),
261 'id' => 'mw-allmessagestable'
262 ] ) .
263 "\n" .
264 "<thead><tr>
265 <th rowspan=\"2\">" .
266 $this->msg( 'allmessagesname' )->escaped() . "
267 </th>
268 <th>" .
269 $this->msg( 'allmessagesdefault' )->escaped() .
270 "</th>
271 </tr>\n
272 <tr>
273 <th>" .
274 $this->msg( 'allmessagescurrent' )->escaped() .
275 "</th>
276 </tr></thead>\n";
277 }
278
279 protected function getEndBody() {
280 return Html::closeElement( 'table' );
281 }
282
288 public function formatValue( $field, $value ) {
289 $linkRenderer = $this->getLinkRenderer();
290 switch ( $field ) {
291 case 'am_title':
292 $title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
293 $talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix );
294 $message = $this->msg( $value )->inLanguage( $this->lang )->useDatabase( false )->plain();
295 $translation = Linker::makeExternalLink(
296 'https://translatewiki.net/w/i.php?' . wfArrayToCgi( [
297 'title' => 'Special:SearchTranslations',
298 'group' => 'mediawiki',
299 'grouppath' => 'mediawiki',
300 'language' => $this->lang->getCode(),
301 'query' => $value . ' ' . $message
302 ] ),
303 $this->msg( 'allmessages-filter-translate' )->text()
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:73
const NS_MEDIAWIKI
Definition Defines.php:72
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:63
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
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.
Some internal bits split of from Skin.php.
Definition Linker.php:65
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:78
Overloads the relevant methods of the real ResultWrapper so it doesn't go anywhere near an actual dat...
Module of static functions for generating XML.
Definition Xml.php:33
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.