MediaWiki master
SpecialMediaStatistics.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Specials;
22
31use Wikimedia\Mime\MimeAnalyzer;
36
44
45 public const MAX_LIMIT = 5000;
46
47 protected int $totalCount = 0;
48 protected int $totalBytes = 0;
49
53 protected $totalPerType = 0;
54
58 protected $countPerType = 0;
59
63 protected $totalSize = 0;
64
65 private MimeAnalyzer $mimeAnalyzer;
66 private int $migrationStage;
67
68 public function __construct(
69 MimeAnalyzer $mimeAnalyzer,
70 IConnectionProvider $dbProvider,
71 LinkBatchFactory $linkBatchFactory
72 ) {
73 parent::__construct( 'MediaStatistics' );
74 // Generally speaking there is only a small number of file types,
75 // so just show all of them.
76 $this->limit = self::MAX_LIMIT;
77 $this->shownavigation = false;
78 $this->mimeAnalyzer = $mimeAnalyzer;
79 $this->setDatabaseProvider( $dbProvider );
80 $this->setLinkBatchFactory( $linkBatchFactory );
81 $this->migrationStage = MediaWikiServices::getInstance()->getMainConfig()->get(
83 );
84 }
85
86 public function isExpensive() {
87 return true;
88 }
89
104 public function getQueryInfo() {
105 $dbr = $this->getDatabaseProvider()->getReplicaDatabase();
106 if ( $this->migrationStage & SCHEMA_COMPAT_READ_OLD ) {
107 $fakeTitle = $dbr->buildConcat( [
108 'img_media_type',
109 $dbr->addQuotes( ';' ),
110 'img_major_mime',
111 $dbr->addQuotes( '/' ),
112 'img_minor_mime',
113 $dbr->addQuotes( ';' ),
114 $dbr->buildStringCast( 'COUNT(*)' ),
115 $dbr->addQuotes( ';' ),
116 $dbr->buildStringCast( 'SUM( img_size )' )
117 ] );
118 return [
119 'tables' => [ 'image' ],
120 'fields' => [
121 'title' => $fakeTitle,
122 'namespace' => NS_MEDIA, /* needs to be something */
123 'value' => '1'
124 ],
125 'options' => [
126 'GROUP BY' => [
127 'img_media_type',
128 'img_major_mime',
129 'img_minor_mime',
130 ]
131 ]
132 ];
133 } else {
134 $fakeTitle = $dbr->buildConcat( [
135 'ft_media_type',
136 $dbr->addQuotes( ';' ),
137 'ft_major_mime',
138 $dbr->addQuotes( '/' ),
139 'ft_minor_mime',
140 $dbr->addQuotes( ';' ),
141 $dbr->buildStringCast( 'COUNT(*)' ),
142 $dbr->addQuotes( ';' ),
143 $dbr->buildStringCast( 'SUM( fr_size )' )
144 ] );
145 return [
146 'tables' => [ 'file', 'filetypes', 'filerevision' ],
147 'fields' => [
148 'title' => $fakeTitle,
149 'namespace' => NS_MEDIA, /* needs to be something */
150 'value' => '1'
151 ],
152 'conds' => [
153 'file_deleted' => 0
154 ],
155 'options' => [
156 'GROUP BY' => [
157 'file_type',
158 'ft_media_type',
159 'ft_major_mime',
160 'ft_minor_mime'
161 ]
162 ],
163 'join_conds' => [
164 'filetypes' => [ 'JOIN', 'file_type = ft_id' ],
165 'filerevision' => [ 'JOIN', 'file_latest = fr_id' ]
166 ]
167 ];
168 }
169 }
170
178 protected function getOrderFields() {
179 if ( $this->migrationStage & SCHEMA_COMPAT_READ_OLD ) {
180 return [ 'img_media_type', 'count(*)', 'img_major_mime', 'img_minor_mime' ];
181 } else {
182 return [ 'file_type', 'count(*)', 'ft_media_type', 'ft_major_mime', 'ft_minor_mime' ];
183 }
184 }
185
196 protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
197 $prevMediaType = null;
198 foreach ( $res as $row ) {
199 $mediaStats = $this->splitFakeTitle( $row->title );
200 if ( count( $mediaStats ) < 4 ) {
201 continue;
202 }
203 [ $mediaType, $mime, $totalCount, $totalBytes ] = $mediaStats;
204 if ( $prevMediaType !== $mediaType ) {
205 if ( $prevMediaType !== null ) {
206 // We're not at beginning, so we have to
207 // close the previous table.
208 $this->outputTableEnd();
209 }
210 $this->outputMediaType( $mediaType );
211 $this->totalPerType = 0;
212 $this->countPerType = 0;
213 $this->outputTableStart( $mediaType );
214 $prevMediaType = $mediaType;
215 }
216 $this->outputTableRow( $mime, intval( $totalCount ), intval( $totalBytes ) );
217 }
218 if ( $prevMediaType !== null ) {
219 $this->outputTableEnd();
220 // add total size of all files
221 $this->outputMediaType( 'total' );
222 $this->getOutput()->addWikiTextAsInterface(
223 $this->msg( 'mediastatistics-allbytes' )
224 ->numParams( $this->totalSize )
225 ->sizeParams( $this->totalSize )
226 ->numParams( $this->totalCount )
227 ->text()
228 );
229 }
230 }
231
235 protected function outputTableEnd() {
236 $this->getOutput()->addHTML(
237 Html::closeElement( 'tbody' ) .
238 Html::closeElement( 'table' )
239 );
240 $this->getOutput()->addWikiTextAsInterface(
241 $this->msg( 'mediastatistics-bytespertype' )
242 ->numParams( $this->totalPerType )
243 ->sizeParams( $this->totalPerType )
244 ->numParams( $this->makePercentPretty( $this->totalPerType / $this->totalBytes ) )
245 ->numParams( $this->countPerType )
246 ->numParams( $this->makePercentPretty( $this->countPerType / $this->totalCount ) )
247 ->text()
248 );
249 $this->totalSize += $this->totalPerType;
250 }
251
259 protected function outputTableRow( $mime, $count, $bytes ) {
260 $mimeSearch = SpecialPage::getTitleFor( 'MIMEsearch', $mime );
261 $linkRenderer = $this->getLinkRenderer();
262 $row = Html::rawElement(
263 'td',
264 [],
265 $linkRenderer->makeLink( $mimeSearch, $mime )
266 );
267 $row .= Html::rawElement(
268 'td',
269 [],
270 $this->getExtensionList( $mime )
271 );
272 $row .= Html::rawElement(
273 'td',
274 // Make sure js sorts it in numeric order
275 [ 'data-sort-value' => $count ],
276 $this->msg( 'mediastatistics-nfiles' )
277 ->numParams( $count )
279 ->numParams( $this->makePercentPretty( $count / $this->totalCount ) )
280 ->parse()
281 );
282 $row .= Html::rawElement(
283 'td',
284 // Make sure js sorts it in numeric order
285 [ 'data-sort-value' => $bytes ],
286 $this->msg( 'mediastatistics-nbytes' )
287 ->numParams( $bytes )
288 ->sizeParams( $bytes )
290 ->numParams( $this->makePercentPretty( $bytes / $this->totalBytes ) )
291 ->parse()
292 );
293 $this->totalPerType += $bytes;
294 $this->countPerType += $count;
295 $this->getOutput()->addHTML( Html::rawElement( 'tr', [], $row ) );
296 }
297
302 protected function makePercentPretty( $decimal ) {
303 $decimal *= 100;
304 // Always show three useful digits
305 if ( $decimal == 0 ) {
306 return '0';
307 }
308 if ( $decimal >= 100 ) {
309 return '100';
310 }
311 $percent = sprintf( "%." . max( 0, 2 - floor( log10( $decimal ) ) ) . "f", $decimal );
312 // Then remove any trailing 0's
313 return preg_replace( '/\.?0*$/', '', $percent );
314 }
315
322 private function getExtensionList( $mime ) {
323 $exts = $this->mimeAnalyzer->getExtensionsFromMimeType( $mime );
324 if ( !$exts ) {
325 return '';
326 }
327 foreach ( $exts as &$ext ) {
328 $ext = htmlspecialchars( '.' . $ext );
329 }
330
331 return $this->getLanguage()->commaList( $exts );
332 }
333
340 protected function outputTableStart( $mediaType ) {
341 $out = $this->getOutput();
342 $out->addModuleStyles( 'jquery.tablesorter.styles' );
343 $out->addModules( 'jquery.tablesorter' );
344 $out->addHTML(
345 Html::openElement(
346 'table',
347 [ 'class' => [
348 'mw-mediastats-table',
349 'mw-mediastats-table-' . strtolower( $mediaType ),
350 'sortable',
351 'wikitable'
352 ] ]
353 ) .
354 Html::rawElement( 'thead', [], $this->getTableHeaderRow() ) .
355 Html::openElement( 'tbody' )
356 );
357 }
358
364 protected function getTableHeaderRow() {
365 $headers = [ 'mimetype', 'extensions', 'count', 'totalbytes' ];
366 $ths = '';
367 foreach ( $headers as $header ) {
368 $ths .= Html::rawElement(
369 'th',
370 [],
371 // for grep:
372 // mediastatistics-table-mimetype, mediastatistics-table-extensions
373 // mediastatistics-table-count, mediastatistics-table-totalbytes
374 $this->msg( 'mediastatistics-table-' . $header )->parse()
375 );
376 }
377 return Html::rawElement( 'tr', [], $ths );
378 }
379
385 protected function outputMediaType( $mediaType ) {
386 $this->getOutput()->addHTML(
388 'h2',
389 [ 'class' => [
390 'mw-mediastats-mediatype',
391 'mw-mediastats-mediatype-' . strtolower( $mediaType )
392 ] ],
393 // for grep
394 // mediastatistics-header-unknown, mediastatistics-header-bitmap,
395 // mediastatistics-header-drawing, mediastatistics-header-audio,
396 // mediastatistics-header-video, mediastatistics-header-multimedia,
397 // mediastatistics-header-office, mediastatistics-header-text,
398 // mediastatistics-header-executable, mediastatistics-header-archive,
399 // mediastatistics-header-3d,
400 $this->msg( 'mediastatistics-header-' . strtolower( $mediaType ) )->text()
401 )
402 );
406 }
407
414 private function splitFakeTitle( $fakeTitle ) {
415 return explode( ';', $fakeTitle, 4 );
416 }
417
422 protected function getGroupName() {
423 return 'media';
424 }
425
427 public function formatResult( $skin, $result ) {
428 return false;
429 }
430
437 public function preprocessResults( $dbr, $res ) {
438 $this->executeLBFromResultWrapper( $res );
439 $this->totalCount = $this->totalBytes = 0;
440 foreach ( $res as $row ) {
441 $mediaStats = $this->splitFakeTitle( $row->title );
442 $this->totalCount += $mediaStats[2] ?? 0;
443 $this->totalBytes += $mediaStats[3] ?? 0;
444 }
445 $res->seek( 0 );
446 }
447}
448
453class_alias( SpecialMediaStatistics::class, 'SpecialMediaStatistics' );
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:304
const NS_MEDIA
Definition Defines.php:53
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
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:58
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:87
setDatabaseProvider(IConnectionProvider $databaseProvider)
int $offset
The offset and limit in use, as passed to the query() function.
Definition QueryPage.php:92
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?
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.
Interface to a relational database.
Definition IDatabase.php:45
A database connection without write operations.
Result wrapper for grabbing data queried from an IDatabase object.
element(SerializerNode $parent, SerializerNode $node, $contents)