MediaWiki master
SpecialMediaStatistics.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Specials;
8
18use Wikimedia\Mime\MimeAnalyzer;
22
30
31 public const MAX_LIMIT = 5000;
32
33 protected int $totalCount = 0;
34 protected int $totalBytes = 0;
35
39 protected $totalPerType = 0;
40
44 protected $countPerType = 0;
45
49 protected $totalSize = 0;
50
51 private readonly int $migrationStage;
52
53 public function __construct(
54 private readonly MimeAnalyzer $mimeAnalyzer,
55 IConnectionProvider $dbProvider,
56 LinkBatchFactory $linkBatchFactory
57 ) {
58 parent::__construct( 'MediaStatistics' );
59 // Generally speaking there is only a small number of file types,
60 // so just show all of them.
61 $this->limit = self::MAX_LIMIT;
62 $this->shownavigation = false;
63 $this->setDatabaseProvider( $dbProvider );
64 $this->setLinkBatchFactory( $linkBatchFactory );
65 $this->migrationStage = MediaWikiServices::getInstance()->getMainConfig()->get(
67 );
68 }
69
71 public function isExpensive() {
72 return true;
73 }
74
89 public function getQueryInfo() {
90 $dbr = $this->getDatabaseProvider()->getReplicaDatabase();
91 if ( $this->migrationStage & SCHEMA_COMPAT_READ_OLD ) {
92 $fakeTitle = $dbr->buildConcat( [
93 'img_media_type',
94 $dbr->addQuotes( ';' ),
95 'img_major_mime',
96 $dbr->addQuotes( '/' ),
97 'img_minor_mime',
98 $dbr->addQuotes( ';' ),
99 $dbr->buildStringCast( 'COUNT(*)' ),
100 $dbr->addQuotes( ';' ),
101 $dbr->buildStringCast( 'SUM( img_size )' )
102 ] );
103 return [
104 'tables' => [ 'image' ],
105 'fields' => [
106 'title' => $fakeTitle,
107 'namespace' => NS_MEDIA, /* needs to be something */
108 'value' => '1'
109 ],
110 'options' => [
111 'GROUP BY' => [
112 'img_media_type',
113 'img_major_mime',
114 'img_minor_mime',
115 ]
116 ]
117 ];
118 } else {
119 $fakeTitle = $dbr->buildConcat( [
120 'ft_media_type',
121 $dbr->addQuotes( ';' ),
122 'ft_major_mime',
123 $dbr->addQuotes( '/' ),
124 'ft_minor_mime',
125 $dbr->addQuotes( ';' ),
126 $dbr->buildStringCast( 'COUNT(*)' ),
127 $dbr->addQuotes( ';' ),
128 $dbr->buildStringCast( 'SUM( fr_size )' )
129 ] );
130 return [
131 'tables' => [ 'file', 'filetypes', 'filerevision' ],
132 'fields' => [
133 'title' => $fakeTitle,
134 'namespace' => NS_MEDIA, /* needs to be something */
135 'value' => '1'
136 ],
137 'conds' => [
138 'file_deleted' => 0
139 ],
140 'options' => [
141 'GROUP BY' => [
142 'file_type',
143 'ft_media_type',
144 'ft_major_mime',
145 'ft_minor_mime'
146 ]
147 ],
148 'join_conds' => [
149 'filetypes' => [ 'JOIN', 'file_type = ft_id' ],
150 'filerevision' => [ 'JOIN', 'file_latest = fr_id' ]
151 ]
152 ];
153 }
154 }
155
163 protected function getOrderFields() {
164 if ( $this->migrationStage & SCHEMA_COMPAT_READ_OLD ) {
165 return [ 'img_media_type', 'count(*)', 'img_major_mime', 'img_minor_mime' ];
166 } else {
167 return [ 'file_type', 'count(*)', 'ft_media_type', 'ft_major_mime', 'ft_minor_mime' ];
168 }
169 }
170
181 protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
182 $prevMediaType = null;
183 foreach ( $res as $row ) {
184 $mediaStats = $this->splitFakeTitle( $row->title );
185 if ( count( $mediaStats ) < 4 ) {
186 continue;
187 }
188 [ $mediaType, $mime, $totalCount, $totalBytes ] = $mediaStats;
189 if ( $prevMediaType !== $mediaType ) {
190 if ( $prevMediaType !== null ) {
191 // We're not at beginning, so we have to
192 // close the previous table.
193 $this->outputTableEnd();
194 }
195 $this->outputMediaType( $mediaType );
196 $this->totalPerType = 0;
197 $this->countPerType = 0;
198 $this->outputTableStart( $mediaType );
199 $prevMediaType = $mediaType;
200 }
201 $this->outputTableRow( $mime, intval( $totalCount ), intval( $totalBytes ) );
202 }
203 if ( $prevMediaType !== null ) {
204 $this->outputTableEnd();
205 // add total size of all files
206 $this->outputMediaType( 'total' );
207 $this->getOutput()->addWikiTextAsInterface(
208 $this->msg( 'mediastatistics-allbytes' )
209 ->numParams( $this->totalSize )
210 ->sizeParams( $this->totalSize )
211 ->numParams( $this->totalCount )
212 ->text()
213 );
214 }
215 }
216
220 protected function outputTableEnd() {
221 $this->getOutput()->addHTML(
222 Html::closeElement( 'tbody' ) .
223 Html::closeElement( 'table' )
224 );
225 $this->getOutput()->addWikiTextAsInterface(
226 $this->msg( 'mediastatistics-bytespertype' )
227 ->numParams( $this->totalPerType )
228 ->sizeParams( $this->totalPerType )
229 ->numParams( $this->makePercentPretty( $this->totalPerType / $this->totalBytes ) )
230 ->numParams( $this->countPerType )
231 ->numParams( $this->makePercentPretty( $this->countPerType / $this->totalCount ) )
232 ->text()
233 );
234 $this->totalSize += $this->totalPerType;
235 }
236
244 protected function outputTableRow( $mime, $count, $bytes ) {
245 $mimeSearch = SpecialPage::getTitleFor( 'MIMEsearch', $mime );
246 $linkRenderer = $this->getLinkRenderer();
247 $row = Html::rawElement(
248 'td',
249 [],
250 $linkRenderer->makeLink( $mimeSearch, $mime )
251 );
252 $row .= Html::rawElement(
253 'td',
254 [],
255 $this->getExtensionList( $mime )
256 );
257 $row .= Html::rawElement(
258 'td',
259 // Make sure js sorts it in numeric order
260 [ 'data-sort-value' => $count ],
261 $this->msg( 'mediastatistics-nfiles' )
262 ->numParams( $count )
264 ->numParams( $this->makePercentPretty( $count / $this->totalCount ) )
265 ->parse()
266 );
267 $row .= Html::rawElement(
268 'td',
269 // Make sure js sorts it in numeric order
270 [ 'data-sort-value' => $bytes ],
271 $this->msg( 'mediastatistics-nbytes' )
272 ->numParams( $bytes )
273 ->sizeParams( $bytes )
275 ->numParams( $this->makePercentPretty( $bytes / $this->totalBytes ) )
276 ->parse()
277 );
278 $this->totalPerType += $bytes;
279 $this->countPerType += $count;
280 $this->getOutput()->addHTML( Html::rawElement( 'tr', [], $row ) );
281 }
282
287 protected function makePercentPretty( $decimal ) {
288 $decimal *= 100;
289 // Always show three useful digits
290 if ( $decimal == 0 ) {
291 return '0';
292 }
293 if ( $decimal >= 100 ) {
294 return '100';
295 }
296 $percent = sprintf( "%." . max( 0, 2 - floor( log10( $decimal ) ) ) . "f", $decimal );
297 // Then remove any trailing 0's
298 return preg_replace( '/\.?0*$/', '', $percent );
299 }
300
307 private function getExtensionList( $mime ) {
308 $exts = $this->mimeAnalyzer->getExtensionsFromMimeType( $mime );
309 if ( !$exts ) {
310 return '';
311 }
312 foreach ( $exts as &$ext ) {
313 $ext = htmlspecialchars( '.' . $ext );
314 }
315
316 return $this->getLanguage()->commaList( $exts );
317 }
318
325 protected function outputTableStart( $mediaType ) {
326 $out = $this->getOutput();
327 $out->addModuleStyles( 'jquery.tablesorter.styles' );
328 $out->addModules( 'jquery.tablesorter' );
329 $out->addHTML(
330 Html::openElement(
331 'table',
332 [ 'class' => [
333 'mw-mediastats-table',
334 'mw-mediastats-table-' . strtolower( $mediaType ),
335 'sortable',
336 'wikitable'
337 ] ]
338 ) .
339 Html::rawElement( 'thead', [], $this->getTableHeaderRow() ) .
340 Html::openElement( 'tbody' )
341 );
342 }
343
349 protected function getTableHeaderRow() {
350 $headers = [ 'mimetype', 'extensions', 'count', 'totalbytes' ];
351 $ths = '';
352 foreach ( $headers as $header ) {
353 $ths .= Html::rawElement(
354 'th',
355 [],
356 // for grep:
357 // mediastatistics-table-mimetype, mediastatistics-table-extensions
358 // mediastatistics-table-count, mediastatistics-table-totalbytes
359 $this->msg( 'mediastatistics-table-' . $header )->parse()
360 );
361 }
362 return Html::rawElement( 'tr', [], $ths );
363 }
364
370 protected function outputMediaType( $mediaType ) {
371 $this->getOutput()->addHTML(
373 'h2',
374 [
375 'id' => Sanitizer::escapeIdForAttribute(
376 'mw-mediastats-mediatype-' . strtolower( $mediaType )
377 ),
378 'class' => [
379 'mw-mediastats-mediatype',
380 'mw-mediastats-mediatype-' . strtolower( $mediaType )
381 ]
382 ],
383 // for grep
384 // mediastatistics-header-unknown, mediastatistics-header-bitmap,
385 // mediastatistics-header-drawing, mediastatistics-header-audio,
386 // mediastatistics-header-video, mediastatistics-header-multimedia,
387 // mediastatistics-header-office, mediastatistics-header-text,
388 // mediastatistics-header-executable, mediastatistics-header-archive,
389 // mediastatistics-header-3d,
390 $this->msg( 'mediastatistics-header-' . strtolower( $mediaType ) )->text()
391 )
392 );
396 }
397
404 private function splitFakeTitle( $fakeTitle ) {
405 return explode( ';', $fakeTitle, 4 );
406 }
407
412 protected function getGroupName() {
413 return 'media';
414 }
415
417 public function formatResult( $skin, $result ) {
418 return false;
419 }
420
427 public function preprocessResults( $dbr, $res ) {
428 $this->executeLBFromResultWrapper( $res );
429 $this->totalCount = $this->totalBytes = 0;
430 foreach ( $res as $row ) {
431 $mediaStats = $this->splitFakeTitle( $row->title );
432 $this->totalCount += $mediaStats[2] ?? 0;
433 $this->totalBytes += $mediaStats[3] ?? 0;
434 }
435 $res->seek( 0 );
436 }
437}
438
443class_alias( SpecialMediaStatistics::class, 'SpecialMediaStatistics' );
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:294
const NS_MEDIA
Definition Defines.php:39
This class is a collection of static functions that serve two purposes:
Definition Html.php:44
A class containing constants representing the names of configuration variables.
const FileSchemaMigrationStage
Name constant for the FileSchemaMigrationStage setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
This is one of the Core classes and should be read at least once by any new developers.
Factory for LinkBatch objects to batch query page metadata.
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:34
The base class for all skins.
Definition Skin.php:54
This is a class for doing query pages; since they're almost all the same, we factor out some of the f...
Definition QueryPage.php:77
setDatabaseProvider(IConnectionProvider $databaseProvider)
int $offset
The offset and limit in use, as passed to the query() function.
Definition QueryPage.php:82
executeLBFromResultWrapper(IResultWrapper $res, $ns=null)
Creates a new LinkBatch object, adds all pages from the passed result wrapper (MUST include title and...
setLinkBatchFactory(LinkBatchFactory $linkBatchFactory)
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,...
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
getLanguage()
Shortcut to get user's language.
formatResult( $skin, $result)
Formats the results of the query for display.The skin is the current skin; you can use it for making ...
int $totalPerType
Combined file size of all files in a section.
__construct(private readonly MimeAnalyzer $mimeAnalyzer, IConnectionProvider $dbProvider, LinkBatchFactory $linkBatchFactory)
isExpensive()
Should this query page only be updated offline on large wikis?If the query for this page is considere...
int $countPerType
Combined file count of all files in a section.
outputTableStart( $mediaType)
Output the start of the table.
getTableHeaderRow()
Get (not output) the header row for the table.
outputMediaType( $mediaType)
Output a header for a new media type section.
outputTableRow( $mime, $count, $bytes)
Output a row of the stats table.
preprocessResults( $dbr, $res)
Initialize total values so we can figure out percentages later.
outputResults( $out, $skin, $dbr, $res, $num, $offset)
Output the results of the query.
int $totalSize
Combined file size of all files.
Provide primary and replica IDatabase connections.
A database connection without write operations.
Result wrapper for grabbing data queried from an IDatabase object.
element(SerializerNode $parent, SerializerNode $node, $contents)