MediaWiki master
AllMessagesTablePager.php
Go to the documentation of this file.
1<?php
9
15use MediaWiki\Languages\LanguageFactory;
23use stdClass;
28
36
40 protected $foreign;
41
45 protected $prefix;
46
50 protected $suffix;
51
55 public $lang;
56
60 public $custom;
61
62 private LocalisationCache $localisationCache;
63
64 public function __construct(
65 IContextSource $context,
66 Language $contentLanguage,
67 LanguageFactory $languageFactory,
68 LinkRenderer $linkRenderer,
69 IConnectionProvider $dbProvider,
70 LocalisationCache $localisationCache,
71 FormOptions $opts
72 ) {
73 // Set database before parent constructor to avoid setting it there
74 $this->mDb = $dbProvider->getReplicaDatabase();
75 parent::__construct( $context, $linkRenderer );
76 $this->localisationCache = $localisationCache;
77
78 $this->mIndexField = 'am_title';
79 // FIXME: Why does this need to be set to DIR_DESCENDING to produce ascending ordering?
80 $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
81
82 $this->lang = $languageFactory->getRawLanguage( $opts->getValue( 'lang' ) );
83
84 $this->foreign = !$this->lang->equals( $contentLanguage );
85
86 $filter = $opts->getValue( 'filter' );
87 if ( $filter === 'all' ) {
88 $this->custom = null; // So won't match in either case
89 } else {
90 $this->custom = ( $filter === 'unmodified' );
91 }
92
93 $prefix = $this->getLanguage()->ucfirst( $opts->getValue( 'prefix' ) );
94 $prefix = $prefix !== '' ?
95 Title::makeTitleSafe( NS_MEDIAWIKI, $opts->getValue( 'prefix' ) ) :
96 null;
97
98 if ( $prefix !== null ) {
99 $displayPrefix = $prefix->getDBkey();
100 $this->prefix = '/^' . preg_quote( $displayPrefix, '/' ) . '/i';
101 } else {
102 $this->prefix = false;
103 }
104
105 // The suffix that may be needed for message names if we're in a
106 // different language (eg [[MediaWiki:Foo/fr]]: $suffix = '/fr'
107 if ( $this->foreign ) {
108 $this->suffix = '/' . $this->lang->getCode();
109 } else {
110 $this->suffix = '';
111 }
112 }
113
114 private function getAllMessages( bool $descending ): array {
115 $messageNames = $this->localisationCache->getSubitemList( 'en', 'messages' );
116
117 // Normalise message names so they look like page titles and sort correctly - T86139
118 $messageNames = array_map( $this->lang->ucfirst( ... ), $messageNames );
119
120 if ( $descending ) {
121 rsort( $messageNames );
122 } else {
123 asort( $messageNames );
124 }
125
126 return $messageNames;
127 }
128
143 public static function getCustomisedStatuses(
144 $messageNames,
145 $langcode = 'en',
146 $foreign = false,
147 ?IReadableDatabase $dbr = null
148 ) {
149 // FIXME: This function should be moved to Language:: or something.
150 // Fallback to global state, if not provided
151 $dbr ??= MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
152 $res = $dbr->newSelectQueryBuilder()
153 ->select( [ 'page_namespace', 'page_title' ] )
154 ->from( 'page' )
155 ->where( [ 'page_namespace' => [ NS_MEDIAWIKI, NS_MEDIAWIKI_TALK ] ] )
156 ->useIndex( 'page_name_title' )
157 ->caller( __METHOD__ )->fetchResultSet();
158 $xNames = array_fill_keys( $messageNames, true );
159
160 $pageFlags = $talkFlags = [];
161
162 foreach ( $res as $s ) {
163 $exists = false;
164
165 if ( $foreign ) {
166 $titleParts = explode( '/', $s->page_title );
167 if ( count( $titleParts ) === 2 &&
168 $langcode === $titleParts[1] &&
169 isset( $xNames[$titleParts[0]] )
170 ) {
171 $exists = $titleParts[0];
172 }
173 } elseif ( isset( $xNames[$s->page_title] ) ) {
174 $exists = $s->page_title;
175 }
176
177 $title = Title::newFromRow( $s );
178 if ( $exists && $title->inNamespace( NS_MEDIAWIKI ) ) {
179 $pageFlags[$exists] = true;
180 } elseif ( $exists && $title->inNamespace( NS_MEDIAWIKI_TALK ) ) {
181 $talkFlags[$exists] = true;
182 }
183 }
184
185 return [ 'pages' => $pageFlags, 'talks' => $talkFlags ];
186 }
187
196 public function reallyDoQuery( $offset, $limit, $order ) {
197 $asc = ( $order === self::QUERY_ASCENDING );
198
199 $messageNames = $this->getAllMessages( $order );
200 $statuses = self::getCustomisedStatuses(
201 $messageNames,
202 $this->lang->getCode(),
203 $this->foreign,
204 $this->getDatabase()
205 );
206
207 $rows = [];
208 $count = 0;
209 foreach ( $messageNames as $key ) {
210 $customised = isset( $statuses['pages'][$key] );
211 if ( $customised !== $this->custom &&
212 ( ( $asc && ( $key < $offset || !$offset ) ) || ( !$asc && $key > $offset ) ) &&
213 ( ( $this->prefix && preg_match( $this->prefix, $key ) ) || $this->prefix === false )
214 ) {
215 $actual = $this->msg( $key )->inLanguage( $this->lang )->plain();
216 $default = $this->msg( $key )->inLanguage( $this->lang )->useDatabase( false )->plain();
217 $rows[] = [
218 'am_title' => $key,
219 'am_actual' => $actual,
220 'am_default' => $default,
221 'am_customised' => $customised,
222 'am_talk_exists' => isset( $statuses['talks'][$key] )
223 ];
224 $count++;
225 }
226
227 if ( $count === $limit ) {
228 break;
229 }
230 }
231
232 return new FakeResultWrapper( $rows );
233 }
234
236 protected function getStartBody() {
237 return Html::openElement( 'table', [
238 'class' => $this->getTableClass(),
239 'id' => 'mw-allmessagestable'
240 ] ) .
241 "\n" .
242 "<thead><tr>
243 <th rowspan=\"2\">" .
244 $this->msg( 'allmessagesname' )->escaped() . "
245 </th>
246 <th>" .
247 $this->msg( 'allmessagesdefault' )->escaped() .
248 "</th>
249 </tr>\n
250 <tr>
251 <th>" .
252 $this->msg( 'allmessagescurrent' )->escaped() .
253 "</th>
254 </tr></thead>\n";
255 }
256
258 protected function getEndBody() {
259 return Html::closeElement( 'table' );
260 }
261
267 public function formatValue( $field, $value ) {
268 $linkRenderer = $this->getLinkRenderer();
269 switch ( $field ) {
270 case 'am_title':
271 $title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
272 $talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix );
273 $message = $this->msg( $value )->inLanguage( $this->lang )->useDatabase( false )->plain();
274 $translation = $linkRenderer->makeExternalLink(
275 'https://translatewiki.net/w/i.php?' . wfArrayToCgi( [
276 'title' => 'Special:SearchTranslations',
277 'group' => 'mediawiki',
278 'grouppath' => 'mediawiki',
279 'language' => $this->lang->getCode(),
280 'query' => $value . ' ' . $message
281 ] ),
282 $this->msg( 'allmessages-filter-translate' ),
283 $this->getTitle()
284 );
285 $talkLink = $this->msg( 'talkpagelinktext' )->text();
286
287 if ( $this->mCurrentRow->am_customised ) {
288 $title = $linkRenderer->makeKnownLink( $title, $this->getLanguage()->lcfirst( $value ) );
289 } else {
290 $title = $linkRenderer->makeBrokenLink(
291 $title, $this->getLanguage()->lcfirst( $value )
292 );
293 }
294 if ( $this->mCurrentRow->am_talk_exists ) {
295 $talk = $linkRenderer->makeKnownLink( $talk, $talkLink );
296 } else {
297 $talk = $linkRenderer->makeBrokenLink(
298 $talk,
299 $talkLink
300 );
301 }
302
303 return $title . ' ' .
304 $this->msg( 'parentheses' )->rawParams( $talk )->escaped() .
305 ' ' .
306 $this->msg( 'parentheses' )->rawParams( $translation )->escaped();
307
308 case 'am_default':
309 case 'am_actual':
310 return Sanitizer::escapeHtmlAllowEntities( $value );
311 }
312
313 return '';
314 }
315
320 public function formatRow( $row ) {
321 // Do all the normal stuff
322 $s = parent::formatRow( $row );
323
324 // But if there's a customised message, add that too.
325 if ( $row->am_customised ) {
326 $s .= Html::openElement( 'tr', $this->getRowAttrs( $row ) );
327 $formatted = strval( $this->formatValue( 'am_actual', $row->am_actual ) );
328
329 if ( $formatted === '' ) {
330 $formatted = "\u{00A0}";
331 }
332
333 $s .= Html::rawElement( 'td', $this->getCellAttrs( 'am_actual', $row->am_actual ), $formatted )
334 . Html::closeElement( 'tr' );
335 }
336
337 return Html::rawElement( 'tbody', [], $s );
338 }
339
341 protected function getRowAttrs( $row ) {
342 return [];
343 }
344
350 protected function getCellAttrs( $field, $value ) {
351 $attr = [];
352 if ( $field === 'am_title' ) {
353 if ( $this->mCurrentRow->am_customised ) {
354 $attr += [ 'rowspan' => '2' ];
355 }
356 } else {
357 $attr += [
358 'lang' => $this->lang->getHtmlCode(),
359 'dir' => $this->lang->getDir(),
360 ];
361 if ( $this->mCurrentRow->am_customised ) {
362 // CSS class: am_default, am_actual
363 $attr += [ 'class' => $field ];
364 }
365 }
366 return $attr;
367 }
368
370 protected function getFieldNames() {
371 // This is not actually used, as getStartBody is overridden above
372 return [
373 'am_title' => $this->msg( 'allmessagesname' )->text(),
374 'am_default' => $this->msg( 'allmessagesdefault' )->text()
375 ];
376 }
377
379 public function getTitle() {
380 return SpecialPage::getTitleFor( 'Allmessages', false );
381 }
382
384 protected function isFieldSortable( $x ) {
385 return false;
386 }
387
389 public function getDefaultSort() {
390 return '';
391 }
392
394 public function getQueryInfo() {
395 return [];
396 }
397
398}
399
404class_alias( AllMessagesTablePager::class, 'AllMessagesTablePager' );
405
407class_alias( AllMessagesTablePager::class, 'MediaWiki\\Pager\\AllMessagesTablePager' );
const NS_MEDIAWIKI_TALK
Definition Defines.php:60
const NS_MEDIAWIKI
Definition Defines.php:59
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.
makeTitle( $linkId)
Convert a link ID to a Title.to override Title
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:43
Base class for language-specific code.
Definition Language.php:69
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.
Efficient paging for SQL queries that use a (roughly unique) index.
Table-based display with a user-selectable sort order.
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:32
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,...
getRowAttrs( $row)
Get attributes to be applied to the given row.to overridearray Array of attribute => value
getQueryInfo()
Provides all parameters needed for the main paged query.It returns an associative array with the foll...
getDefaultSort()
The database field name used as a default sort order.Note that this field will only be sorted on if i...
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.bool
reallyDoQuery( $offset, $limit, $order)
This function normally does a database query to get the results; we need to make a pretend result usi...
__construct(IContextSource $context, Language $contentLanguage, LanguageFactory $languageFactory, LinkRenderer $linkRenderer, IConnectionProvider $dbProvider, LocalisationCache $localisationCache, FormOptions $opts)
getFieldNames()
An array mapping database field names to a textual description of the field name, for use in the tabl...
Represents a title within MediaWiki.
Definition Title.php:69
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(string|false $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.