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 'ft_media_type',
143 'ft_major_mime',
144 'ft_minor_mime'
145 ]
146 ],
147 'join_conds' => [
148 'filetypes' => [ 'JOIN', 'file_type = ft_id' ],
149 'filerevision' => [ 'JOIN', 'file_latest = fr_id' ]
150 ]
151 ];
152 }
153 }
154
162 protected function getOrderFields() {
163 if ( $this->migrationStage & SCHEMA_COMPAT_READ_OLD ) {
164 return [ 'img_media_type', 'count(*)', 'img_major_mime', 'img_minor_mime' ];
165 } else {
166 return [ 'ft_media_type', 'count(*)', 'ft_major_mime', 'ft_minor_mime' ];
167 }
168 }
169
180 protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
181 $prevMediaType = null;
182 foreach ( $res as $row ) {
183 $mediaStats = $this->splitFakeTitle( $row->title );
184 if ( count( $mediaStats ) < 4 ) {
185 continue;
186 }
187 [ $mediaType, $mime, $totalCount, $totalBytes ] = $mediaStats;
188 if ( $prevMediaType !== $mediaType ) {
189 if ( $prevMediaType !== null ) {
190 // We're not at beginning, so we have to
191 // close the previous table.
192 $this->outputTableEnd();
193 }
194 $this->outputMediaType( $mediaType );
195 $this->totalPerType = 0;
196 $this->countPerType = 0;
197 $this->outputTableStart( $mediaType );
198 $prevMediaType = $mediaType;
199 }
200 $this->outputTableRow( $mime, intval( $totalCount ), intval( $totalBytes ) );
201 }
202 if ( $prevMediaType !== null ) {
203 $this->outputTableEnd();
204 // add total size of all files
205 $this->outputMediaType( 'total' );
206 $this->getOutput()->addWikiTextAsInterface(
207 $this->msg( 'mediastatistics-allbytes' )
208 ->numParams( $this->totalSize )
209 ->sizeParams( $this->totalSize )
210 ->numParams( $this->totalCount )
211 ->text()
212 );
213 }
214 }
215
219 protected function outputTableEnd() {
220 $this->getOutput()->addHTML(
221 Html::closeElement( 'tbody' ) .
222 Html::closeElement( 'table' )
223 );
224 $this->getOutput()->addWikiTextAsInterface(
225 $this->msg( 'mediastatistics-bytespertype' )
226 ->numParams( $this->totalPerType )
227 ->sizeParams( $this->totalPerType )
228 ->numParams( $this->makePercentPretty( $this->totalPerType / $this->totalBytes ) )
229 ->numParams( $this->countPerType )
230 ->numParams( $this->makePercentPretty( $this->countPerType / $this->totalCount ) )
231 ->text()
232 );
233 $this->totalSize += $this->totalPerType;
234 }
235
243 protected function outputTableRow( $mime, $count, $bytes ) {
244 $mimeSearch = SpecialPage::getTitleFor( 'MIMEsearch', $mime );
245 $linkRenderer = $this->getLinkRenderer();
246 $row = Html::rawElement(
247 'td',
248 [],
249 $linkRenderer->makeLink( $mimeSearch, $mime )
250 );
251 $row .= Html::rawElement(
252 'td',
253 [],
254 $this->getExtensionList( $mime )
255 );
256 $row .= Html::rawElement(
257 'td',
258 // Make sure js sorts it in numeric order
259 [ 'data-sort-value' => $count ],
260 $this->msg( 'mediastatistics-nfiles' )
261 ->numParams( $count )
263 ->numParams( $this->makePercentPretty( $count / $this->totalCount ) )
264 ->parse()
265 );
266 $row .= Html::rawElement(
267 'td',
268 // Make sure js sorts it in numeric order
269 [ 'data-sort-value' => $bytes ],
270 $this->msg( 'mediastatistics-nbytes' )
271 ->numParams( $bytes )
272 ->sizeParams( $bytes )
274 ->numParams( $this->makePercentPretty( $bytes / $this->totalBytes ) )
275 ->parse()
276 );
277 $this->totalPerType += $bytes;
278 $this->countPerType += $count;
279 $this->getOutput()->addHTML( Html::rawElement( 'tr', [], $row ) );
280 }
281
286 protected function makePercentPretty( $decimal ) {
287 $decimal *= 100;
288 // Always show three useful digits
289 if ( $decimal == 0 ) {
290 return '0';
291 }
292 if ( $decimal >= 100 ) {
293 return '100';
294 }
295 $percent = sprintf( "%." . max( 0, 2 - floor( log10( $decimal ) ) ) . "f", $decimal );
296 // Then remove any trailing 0's
297 return preg_replace( '/\.?0*$/', '', $percent );
298 }
299
306 private function getExtensionList( $mime ) {
307 $exts = $this->mimeAnalyzer->getExtensionsFromMimeType( $mime );
308 if ( !$exts ) {
309 return '';
310 }
311 foreach ( $exts as &$ext ) {
312 $ext = htmlspecialchars( '.' . $ext );
313 }
314
315 return $this->getLanguage()->commaList( $exts );
316 }
317
324 protected function outputTableStart( $mediaType ) {
325 $out = $this->getOutput();
326 $out->addModuleStyles( 'jquery.tablesorter.styles' );
327 $out->addModules( 'jquery.tablesorter' );
328 $out->addHTML(
329 Html::openElement(
330 'table',
331 [ 'class' => [
332 'mw-mediastats-table',
333 'mw-mediastats-table-' . strtolower( $mediaType ),
334 'sortable',
335 'wikitable'
336 ] ]
337 ) .
338 Html::rawElement( 'thead', [], $this->getTableHeaderRow() ) .
339 Html::openElement( 'tbody' )
340 );
341 }
342
348 protected function getTableHeaderRow() {
349 $headers = [ 'mimetype', 'extensions', 'count', 'totalbytes' ];
350 $ths = '';
351 foreach ( $headers as $header ) {
352 $ths .= Html::rawElement(
353 'th',
354 [],
355 // for grep:
356 // mediastatistics-table-mimetype, mediastatistics-table-extensions
357 // mediastatistics-table-count, mediastatistics-table-totalbytes
358 $this->msg( 'mediastatistics-table-' . $header )->parse()
359 );
360 }
361 return Html::rawElement( 'tr', [], $ths );
362 }
363
369 protected function outputMediaType( $mediaType ) {
370 $this->getOutput()->addHTML(
372 'h2',
373 [
374 'id' => Sanitizer::escapeIdForAttribute(
375 'mw-mediastats-mediatype-' . strtolower( $mediaType )
376 ),
377 'class' => [
378 'mw-mediastats-mediatype',
379 'mw-mediastats-mediatype-' . strtolower( $mediaType )
380 ]
381 ],
382 // for grep
383 // mediastatistics-header-unknown, mediastatistics-header-bitmap,
384 // mediastatistics-header-drawing, mediastatistics-header-audio,
385 // mediastatistics-header-video, mediastatistics-header-multimedia,
386 // mediastatistics-header-office, mediastatistics-header-text,
387 // mediastatistics-header-executable, mediastatistics-header-archive,
388 // mediastatistics-header-3d,
389 $this->msg( 'mediastatistics-header-' . strtolower( $mediaType ) )->text()
390 )
391 );
395 }
396
403 private function splitFakeTitle( $fakeTitle ) {
404 return explode( ';', $fakeTitle, 4 );
405 }
406
411 protected function getGroupName() {
412 return 'media';
413 }
414
416 public function formatResult( $skin, $result ) {
417 return false;
418 }
419
426 public function preprocessResults( $dbr, $res ) {
427 $this->executeLBFromResultWrapper( $res );
428 $this->totalCount = $this->totalBytes = 0;
429 foreach ( $res as $row ) {
430 $mediaStats = $this->splitFakeTitle( $row->title );
431 $this->totalCount += $mediaStats[2] ?? 0;
432 $this->totalBytes += $mediaStats[3] ?? 0;
433 }
434 $res->seek( 0 );
435 }
436}
437
438// @codeCoverageIgnoreStart
443class_alias( SpecialMediaStatistics::class, 'SpecialMediaStatistics' );
444// @codeCoverageIgnoreEnd
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)