Completed
Branch master (939199)
by
unknown
39:35
created

includes/specials/SpecialStatistics.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Implements Special:Statistics
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 * @ingroup SpecialPage
22
 */
23
24
/**
25
 * Special page lists various statistics, including the contents of
26
 * `site_stats`, plus page view details if enabled
27
 *
28
 * @ingroup SpecialPage
29
 */
30
class SpecialStatistics extends SpecialPage {
31
	private $edits, $good, $images, $total, $users,
32
		$activeUsers = 0;
33
34
	public function __construct() {
35
		parent::__construct( 'Statistics' );
36
	}
37
38
	public function execute( $par ) {
39
		$this->setHeaders();
40
		$this->getOutput()->addModuleStyles( 'mediawiki.special' );
41
42
		$this->edits = SiteStats::edits();
43
		$this->good = SiteStats::articles();
44
		$this->images = SiteStats::images();
45
		$this->total = SiteStats::pages();
46
		$this->users = SiteStats::users();
47
		$this->activeUsers = SiteStats::activeUsers();
48
		$this->hook = '';
0 ignored issues
show
The property hook does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
49
50
		$text = Xml::openElement( 'table', [ 'class' => 'wikitable mw-statistics-table' ] );
51
52
		# Statistic - pages
53
		$text .= $this->getPageStats();
54
55
		# Statistic - edits
56
		$text .= $this->getEditStats();
57
58
		# Statistic - users
59
		$text .= $this->getUserStats();
60
61
		# Statistic - usergroups
62
		$text .= $this->getGroupStats();
63
64
		# Statistic - other
65
		$extraStats = [];
66
		if ( Hooks::run( 'SpecialStatsAddExtra', [ &$extraStats, $this->getContext() ] ) ) {
67
			$text .= $this->getOtherStats( $extraStats );
68
		}
69
70
		$text .= Xml::closeElement( 'table' );
71
72
		# Customizable footer
73
		$footer = $this->msg( 'statistics-footer' );
74
		if ( !$footer->isBlank() ) {
75
			$text .= "\n" . $footer->parse();
76
		}
77
78
		$this->getOutput()->addHTML( $text );
79
	}
80
81
	/**
82
	 * Format a row
83
	 * @param string $text Description of the row
84
	 * @param float $number A statistical number
85
	 * @param array $trExtraParams Params to table row, see Html::elememt
86
	 * @param string $descMsg Message key
87
	 * @param array|string $descMsgParam Message parameters
88
	 * @return string Table row in HTML format
89
	 */
90
	private function formatRow( $text, $number, $trExtraParams = [],
91
		$descMsg = '', $descMsgParam = ''
92
	) {
93
		if ( $descMsg ) {
94
			$msg = $this->msg( $descMsg, $descMsgParam );
95
			if ( !$msg->isDisabled() ) {
96
				$descriptionHtml = $this->msg( 'parentheses' )->rawParams( $msg->parse() )
97
					->escaped();
98
				$text .= "<br />" . Html::rawElement( 'small', [ 'class' => 'mw-statistic-desc' ],
99
					" $descriptionHtml" );
100
			}
101
		}
102
103
		return Html::rawElement( 'tr', $trExtraParams,
104
			Html::rawElement( 'td', [], $text ) .
105
			Html::rawElement( 'td', [ 'class' => 'mw-statistics-numbers' ], $number )
106
		);
107
	}
108
109
	/**
110
	 * Each of these methods is pretty self-explanatory, get a particular
111
	 * row for the table of statistics
112
	 * @return string
113
	 */
114
	private function getPageStats() {
115
		$specialAllPagesTitle = SpecialPage::getTitleFor( 'Allpages' );
116
		$pageStatsHtml = Xml::openElement( 'tr' ) .
117
			Xml::tags( 'th', [ 'colspan' => '2' ], $this->msg( 'statistics-header-pages' )
118
				->parse() ) .
119
			Xml::closeElement( 'tr' ) .
120
				$this->formatRow( Linker::linkKnown( $specialAllPagesTitle,
121
					$this->msg( 'statistics-articles' )->parse(), [], [ 'hideredirects' => 1 ] ),
122
					$this->getLanguage()->formatNum( $this->good ),
123
					[ 'class' => 'mw-statistics-articles' ],
124
					'statistics-articles-desc' ) .
125
				$this->formatRow( Linker::linkKnown( $specialAllPagesTitle,
126
					$this->msg( 'statistics-pages' )->parse() ),
127
					$this->getLanguage()->formatNum( $this->total ),
128
					[ 'class' => 'mw-statistics-pages' ],
129
					'statistics-pages-desc' );
130
131
		// Show the image row only, when there are files or upload is possible
132
		if ( $this->images !== 0 || $this->getConfig()->get( 'EnableUploads' ) ) {
133
			$pageStatsHtml .= $this->formatRow(
134
				Linker::linkKnown( SpecialPage::getTitleFor( 'MediaStatistics' ),
135
				$this->msg( 'statistics-files' )->parse() ),
136
				$this->getLanguage()->formatNum( $this->images ),
137
				[ 'class' => 'mw-statistics-files' ] );
138
		}
139
140
		return $pageStatsHtml;
141
	}
142
143
	private function getEditStats() {
144
		return Xml::openElement( 'tr' ) .
145
			Xml::tags( 'th', [ 'colspan' => '2' ],
146
				$this->msg( 'statistics-header-edits' )->parse() ) .
147
			Xml::closeElement( 'tr' ) .
148
			$this->formatRow( $this->msg( 'statistics-edits' )->parse(),
149
				$this->getLanguage()->formatNum( $this->edits ),
150
				[ 'class' => 'mw-statistics-edits' ]
151
			) .
152
			$this->formatRow( $this->msg( 'statistics-edits-average' )->parse(),
153
				$this->getLanguage()
154
					->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ),
155
				[ 'class' => 'mw-statistics-edits-average' ]
156
			);
157
	}
158
159
	private function getUserStats() {
160
		return Xml::openElement( 'tr' ) .
161
			Xml::tags( 'th', [ 'colspan' => '2' ],
162
				$this->msg( 'statistics-header-users' )->parse() ) .
163
			Xml::closeElement( 'tr' ) .
164
			$this->formatRow( $this->msg( 'statistics-users' )->parse(),
165
				$this->getLanguage()->formatNum( $this->users ),
166
				[ 'class' => 'mw-statistics-users' ]
167
			) .
168
			$this->formatRow( $this->msg( 'statistics-users-active' )->parse() . ' ' .
169
				Linker::linkKnown(
170
					SpecialPage::getTitleFor( 'Activeusers' ),
171
					$this->msg( 'listgrouprights-members' )->escaped()
172
				),
173
				$this->getLanguage()->formatNum( $this->activeUsers ),
174
				[ 'class' => 'mw-statistics-users-active' ],
175
				'statistics-users-active-desc',
176
				$this->getLanguage()->formatNum( $this->getConfig()->get( 'ActiveUserDays' ) )
177
			);
178
	}
179
180
	private function getGroupStats() {
181
		$text = '';
182
		foreach ( $this->getConfig()->get( 'GroupPermissions' ) as $group => $permissions ) {
183
			# Skip generic * and implicit groups
184
			if ( in_array( $group, $this->getConfig()->get( 'ImplicitGroups' ) ) || $group == '*' ) {
185
				continue;
186
			}
187
			$groupname = htmlspecialchars( $group );
188
			$msg = $this->msg( 'group-' . $groupname );
189
			if ( $msg->isBlank() ) {
190
				$groupnameLocalized = $groupname;
191
			} else {
192
				$groupnameLocalized = $msg->text();
193
			}
194
			$msg = $this->msg( 'grouppage-' . $groupname )->inContentLanguage();
195
			if ( $msg->isBlank() ) {
196
				$grouppageLocalized = MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname;
197
			} else {
198
				$grouppageLocalized = $msg->text();
199
			}
200
			$linkTarget = Title::newFromText( $grouppageLocalized );
201
202
			if ( $linkTarget ) {
203
				$grouppage = Linker::link(
204
					$linkTarget,
205
					htmlspecialchars( $groupnameLocalized )
206
				);
207
			} else {
208
				$grouppage = htmlspecialchars( $groupnameLocalized );
209
			}
210
211
			$grouplink = Linker::linkKnown(
212
				SpecialPage::getTitleFor( 'Listusers' ),
213
				$this->msg( 'listgrouprights-members' )->escaped(),
214
				[],
215
				[ 'group' => $group ]
216
			);
217
			# Add a class when a usergroup contains no members to allow hiding these rows
218
			$classZero = '';
219
			$countUsers = SiteStats::numberingroup( $groupname );
220
			if ( $countUsers == 0 ) {
221
				$classZero = ' statistics-group-zero';
222
			}
223
			$text .= $this->formatRow( $grouppage . ' ' . $grouplink,
224
				$this->getLanguage()->formatNum( $countUsers ),
225
				[ 'class' => 'statistics-group-' . Sanitizer::escapeClass( $group ) .
226
					$classZero ] );
227
		}
228
229
		return $text;
230
	}
231
232
	/**
233
	 * Conversion of external statistics into an internal representation
234
	 * Following a ([<header-message>][<item-message>] = number) pattern
235
	 *
236
	 * @param array $stats
237
	 * @return string
238
	 */
239
	private function getOtherStats( array $stats ) {
240
		$return = '';
241
242
		foreach ( $stats as $header => $items ) {
243
			// Identify the structure used
244
			if ( is_array( $items ) ) {
245
246
				// Ignore headers that are recursively set as legacy header
247
				if ( $header !== 'statistics-header-hooks' ) {
248
					$return .= $this->formatRowHeader( $header );
249
				}
250
251
				// Collect all items that belong to the same header
252
				foreach ( $items as $key => $value ) {
253
					if ( is_array( $value ) ) {
254
						$name = $value['name'];
255
						$number = $value['number'];
256
					} else {
257
						$name = $this->msg( $key )->parse();
258
						$number = $value;
259
					}
260
261
					$return .= $this->formatRow(
262
						$name,
263
						$this->getLanguage()->formatNum( htmlspecialchars( $number ) ),
264
						[ 'class' => 'mw-statistics-hook', 'id' => 'mw-' . $key ]
265
					);
266
				}
267
			} else {
268
				// Create the legacy header only once
269
				if ( $return === '' ) {
270
					$return .= $this->formatRowHeader( 'statistics-header-hooks' );
271
				}
272
273
				// Recursively remap the legacy structure
274
				$return .= $this->getOtherStats( [ 'statistics-header-hooks' =>
275
					[ $header => $items ] ] );
276
			}
277
		}
278
279
		return $return;
280
	}
281
282
	/**
283
	 * Format row header
284
	 *
285
	 * @param string $header
286
	 * @return string
287
	 */
288
	private function formatRowHeader( $header ) {
289
		return Xml::openElement( 'tr' ) .
290
			Xml::tags( 'th', [ 'colspan' => '2' ], $this->msg( $header )->parse() ) .
291
			Xml::closeElement( 'tr' );
292
	}
293
294
	protected function getGroupName() {
295
		return 'wiki';
296
	}
297
}
298