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
|
|||
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 |
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: