MediaWiki master
SpecialMediaStatistics.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Specials;
8
9use MediaWiki\Cache\LinkBatchFactory;
17use Wikimedia\Mime\MimeAnalyzer;
21
29
30 public const MAX_LIMIT = 5000;
31
32 protected int $totalCount = 0;
33 protected int $totalBytes = 0;
34
38 protected $totalPerType = 0;
39
43 protected $countPerType = 0;
44
48 protected $totalSize = 0;
49
50 private MimeAnalyzer $mimeAnalyzer;
51 private int $migrationStage;
52
53 public function __construct(
54 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->mimeAnalyzer = $mimeAnalyzer;
64 $this->setDatabaseProvider( $dbProvider );
65 $this->setLinkBatchFactory( $linkBatchFactory );
66 $this->migrationStage = MediaWikiServices::getInstance()->getMainConfig()->get(
68 );
69 }
70
72 public function isExpensive() {
73 return true;
74 }
75
90 public function getQueryInfo() {
91 $dbr = $this->getDatabaseProvider()->getReplicaDatabase();
92 if ( $this->migrationStage & SCHEMA_COMPAT_READ_OLD ) {
93 $fakeTitle = $dbr->buildConcat( [
94 'img_media_type',
95 $dbr->addQuotes( ';' ),
96 'img_major_mime',
97 $dbr->addQuotes( '/' ),
98 'img_minor_mime',
99 $dbr->addQuotes( ';' ),
100 $dbr->buildStringCast( 'COUNT(*)' ),
101 $dbr->addQuotes( ';' ),
102 $dbr->buildStringCast( 'SUM( img_size )' )
103 ] );
104 return [
105 'tables' => [ 'image' ],
106 'fields' => [
107 'title' => $fakeTitle,
108 'namespace' => NS_MEDIA, /* needs to be something */
109 'value' => '1'
110 ],
111 'options' => [
112 'GROUP BY' => [
113 'img_media_type',
114 'img_major_mime',
115 'img_minor_mime',
116 ]
117 ]
118 ];
119 } else {
120 $fakeTitle = $dbr->buildConcat( [
121 'ft_media_type',
122 $dbr->addQuotes( ';' ),
123 'ft_major_mime',
124 $dbr->addQuotes( '/' ),
125 'ft_minor_mime',
126 $dbr->addQuotes( ';' ),
127 $dbr->buildStringCast( 'COUNT(*)' ),
128 $dbr->addQuotes( ';' ),
129 $dbr->buildStringCast( 'SUM( fr_size )' )
130 ] );
131 return [
132 'tables' => [ 'file', 'filetypes', 'filerevision' ],
133 'fields' => [
134 'title' => $fakeTitle,
135 'namespace' => NS_MEDIA, /* needs to be something */
136 'value' => '1'
137 ],
138 'conds' => [
139 'file_deleted' => 0
140 ],
141 'options' => [
142 'GROUP BY' => [
143 'file_type',
144 'ft_media_type',
145 'ft_major_mime',
146 'ft_minor_mime'
147 ]
148 ],
149 'join_conds' => [
150 'filetypes' => [ 'JOIN', 'file_type = ft_id' ],
151 'filerevision' => [ 'JOIN', 'file_latest = fr_id' ]
152 ]
153 ];
154 }
155 }
156
164 protected function getOrderFields() {
165 if ( $this->migrationStage & SCHEMA_COMPAT_READ_OLD ) {
166 return [ 'img_media_type', 'count(*)', 'img_major_mime', 'img_minor_mime' ];
167 } else {
168 return [ 'file_type', 'count(*)', 'ft_media_type', 'ft_major_mime', 'ft_minor_mime' ];
169 }
170 }
171
182 protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
183 $prevMediaType = null;
184 foreach ( $res as $row ) {
185 $mediaStats = $this->splitFakeTitle( $row->title );
186 if ( count( $mediaStats ) < 4 ) {
187 continue;
188 }
189 [ $mediaType, $mime, $totalCount, $totalBytes ] = $mediaStats;
190 if ( $prevMediaType !== $mediaType ) {
191 if ( $prevMediaType !== null ) {
192 // We're not at beginning, so we have to
193 // close the previous table.
194 $this->outputTableEnd();
195 }
196 $this->outputMediaType( $mediaType );
197 $this->totalPerType = 0;
198 $this->countPerType = 0;
199 $this->outputTableStart( $mediaType );
200 $prevMediaType = $mediaType;
201 }
202 $this->outputTableRow( $mime, intval( $totalCount ), intval( $totalBytes ) );
203 }
204 if ( $prevMediaType !== null ) {
205 $this->outputTableEnd();
206 // add total size of all files
207 $this->outputMediaType( 'total' );
208 $this->getOutput()->addWikiTextAsInterface(
209 $this->msg( 'mediastatistics-allbytes' )
210 ->numParams( $this->totalSize )
211 ->sizeParams( $this->totalSize )
212 ->numParams( $this->totalCount )
213 ->text()
214 );
215 }
216 }
217
221 protected function outputTableEnd() {
222 $this->getOutput()->addHTML(
223 Html::closeElement( 'tbody' ) .
224 Html::closeElement( 'table' )
225 );
226 $this->getOutput()->addWikiTextAsInterface(
227 $this->msg( 'mediastatistics-bytespertype' )
228 ->numParams( $this->totalPerType )
229 ->sizeParams( $this->totalPerType )
230 ->numParams( $this->makePercentPretty( $this->totalPerType / $this->totalBytes ) )
231 ->numParams( $this->countPerType )
232 ->numParams( $this->makePercentPretty( $this->countPerType / $this->totalCount ) )
233 ->text()
234 );
235 $this->totalSize += $this->totalPerType;
236 }
237
245 protected function outputTableRow( $mime, $count, $bytes ) {
246 $mimeSearch = SpecialPage::getTitleFor( 'MIMEsearch', $mime );
247 $linkRenderer = $this->getLinkRenderer();
248 $row = Html::rawElement(
249 'td',
250 [],
251 $linkRenderer->makeLink( $mimeSearch, $mime )
252 );
253 $row .= Html::rawElement(
254 'td',
255 [],
256 $this->getExtensionList( $mime )
257 );
258 $row .= Html::rawElement(
259 'td',
260 // Make sure js sorts it in numeric order
261 [ 'data-sort-value' => $count ],
262 $this->msg( 'mediastatistics-nfiles' )
263 ->numParams( $count )
265 ->numParams( $this->makePercentPretty( $count / $this->totalCount ) )
266 ->parse()
267 );
268 $row .= Html::rawElement(
269 'td',
270 // Make sure js sorts it in numeric order
271 [ 'data-sort-value' => $bytes ],
272 $this->msg( 'mediastatistics-nbytes' )
273 ->numParams( $bytes )
274 ->sizeParams( $bytes )
276 ->numParams( $this->makePercentPretty( $bytes / $this->totalBytes ) )
277 ->parse()
278 );
279 $this->totalPerType += $bytes;
280 $this->countPerType += $count;
281 $this->getOutput()->addHTML( Html::rawElement( 'tr', [], $row ) );
282 }
283
288 protected function makePercentPretty( $decimal ) {
289 $decimal *= 100;
290 // Always show three useful digits
291 if ( $decimal == 0 ) {
292 return '0';
293 }
294 if ( $decimal >= 100 ) {
295 return '100';
296 }
297 $percent = sprintf( "%." . max( 0, 2 - floor( log10( $decimal ) ) ) . "f", $decimal );
298 // Then remove any trailing 0's
299 return preg_replace( '/\.?0*$/', '', $percent );
300 }
301
308 private function getExtensionList( $mime ) {
309 $exts = $this->mimeAnalyzer->getExtensionsFromMimeType( $mime );
310 if ( !$exts ) {
311 return '';
312 }
313 foreach ( $exts as &$ext ) {
314 $ext = htmlspecialchars( '.' . $ext );
315 }
316
317 return $this->getLanguage()->commaList( $exts );
318 }
319
326 protected function outputTableStart( $mediaType ) {
327 $out = $this->getOutput();
328 $out->addModuleStyles( 'jquery.tablesorter.styles' );
329 $out->addModules( 'jquery.tablesorter' );
330 $out->addHTML(
331 Html::openElement(
332 'table',
333 [ 'class' => [
334 'mw-mediastats-table',
335 'mw-mediastats-table-' . strtolower( $mediaType ),
336 'sortable',
337 'wikitable'
338 ] ]
339 ) .
340 Html::rawElement( 'thead', [], $this->getTableHeaderRow() ) .
341 Html::openElement( 'tbody' )
342 );
343 }
344
350 protected function getTableHeaderRow() {
351 $headers = [ 'mimetype', 'extensions', 'count', 'totalbytes' ];
352 $ths = '';
353 foreach ( $headers as $header ) {
354 $ths .= Html::rawElement(
355 'th',
356 [],
357 // for grep:
358 // mediastatistics-table-mimetype, mediastatistics-table-extensions
359 // mediastatistics-table-count, mediastatistics-table-totalbytes
360 $this->msg( 'mediastatistics-table-' . $header )->parse()
361 );
362 }
363 return Html::rawElement( 'tr', [], $ths );
364 }
365
371 protected function outputMediaType( $mediaType ) {
372 $this->getOutput()->addHTML(
374 'h2',
375 [ 'class' => [
376 'mw-mediastats-mediatype',
377 'mw-mediastats-mediatype-' . strtolower( $mediaType )
378 ] ],
379 // for grep
380 // mediastatistics-header-unknown, mediastatistics-header-bitmap,
381 // mediastatistics-header-drawing, mediastatistics-header-audio,
382 // mediastatistics-header-video, mediastatistics-header-multimedia,
383 // mediastatistics-header-office, mediastatistics-header-text,
384 // mediastatistics-header-executable, mediastatistics-header-archive,
385 // mediastatistics-header-3d,
386 $this->msg( 'mediastatistics-header-' . strtolower( $mediaType ) )->text()
387 )
388 );
392 }
393
400 private function splitFakeTitle( $fakeTitle ) {
401 return explode( ';', $fakeTitle, 4 );
402 }
403
408 protected function getGroupName() {
409 return 'media';
410 }
411
413 public function formatResult( $skin, $result ) {
414 return false;
415 }
416
423 public function preprocessResults( $dbr, $res ) {
424 $this->executeLBFromResultWrapper( $res );
425 $this->totalCount = $this->totalBytes = 0;
426 foreach ( $res as $row ) {
427 $mediaStats = $this->splitFakeTitle( $row->title );
428 $this->totalCount += $mediaStats[2] ?? 0;
429 $this->totalBytes += $mediaStats[3] ?? 0;
430 }
431 $res->seek( 0 );
432 }
433}
434
439class_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:43
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.
The base class for all skins.
Definition Skin.php:43
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:76
setDatabaseProvider(IConnectionProvider $databaseProvider)
int $offset
The offset and limit in use, as passed to the query() function.
Definition QueryPage.php:81
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.
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.
__construct(MimeAnalyzer $mimeAnalyzer, IConnectionProvider $dbProvider, LinkBatchFactory $linkBatchFactory)
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)