Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 131 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
SpecialGadgetUsage | |
0.00% |
0 / 131 |
|
0.00% |
0 / 12 |
812 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
isActiveUsersEnabled | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isExpensive | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getQueryInfo | |
0.00% |
0 / 43 |
|
0.00% |
0 / 1 |
6 | |||
getOrderFields | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
outputTableStart | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
20 | |||
outputTableEnd | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
formatResult | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
12 | |||
getDefaultGadgets | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
outputResults | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
90 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\Extension\Gadgets\Special; |
22 | |
23 | use MediaWiki\Extension\Gadgets\GadgetRepo; |
24 | use MediaWiki\Html\Html; |
25 | use MediaWiki\Output\OutputPage; |
26 | use MediaWiki\SpecialPage\QueryPage; |
27 | use MediaWiki\Title\TitleValue; |
28 | use Skin; |
29 | use stdClass; |
30 | use Wikimedia\Rdbms\IConnectionProvider; |
31 | use Wikimedia\Rdbms\IExpression; |
32 | use Wikimedia\Rdbms\IReadableDatabase; |
33 | use Wikimedia\Rdbms\IResultWrapper; |
34 | use Wikimedia\Rdbms\LikeValue; |
35 | |
36 | /** |
37 | * Special:GadgetUsage lists all the gadgets on the wiki along with number of users. |
38 | * |
39 | * @copyright 2015 Niharika Kohli |
40 | */ |
41 | class SpecialGadgetUsage extends QueryPage { |
42 | private GadgetRepo $gadgetRepo; |
43 | private IConnectionProvider $dbProvider; |
44 | |
45 | public function __construct( GadgetRepo $gadgetRepo, IConnectionProvider $dbProvider ) { |
46 | parent::__construct( 'GadgetUsage' ); |
47 | $this->gadgetRepo = $gadgetRepo; |
48 | $this->dbProvider = $dbProvider; |
49 | $this->limit = 1000; // Show all gadgets |
50 | $this->shownavigation = false; |
51 | } |
52 | |
53 | /** |
54 | * @inheritDoc |
55 | */ |
56 | public function execute( $par ) { |
57 | parent::execute( $par ); |
58 | $this->addHelpLink( 'Extension:Gadgets' ); |
59 | } |
60 | |
61 | /** |
62 | * Get value of config variable SpecialGadgetUsageActiveUsers |
63 | * |
64 | * @return bool |
65 | */ |
66 | private function isActiveUsersEnabled() { |
67 | return $this->getConfig()->get( 'SpecialGadgetUsageActiveUsers' ); |
68 | } |
69 | |
70 | public function isExpensive() { |
71 | return true; |
72 | } |
73 | |
74 | /** |
75 | * Define the database query that is used to generate the stats table. |
76 | * This uses 1 of 2 possible queries, depending on $wgSpecialGadgetUsageActiveUsers. |
77 | * |
78 | * The simple query is essentially: |
79 | * SELECT up_property, COUNT(*) |
80 | * FROM user_properties |
81 | * WHERE up_property LIKE 'gadget-%' AND up_value NOT IN ('0','') |
82 | * GROUP BY up_property; |
83 | * |
84 | * The more expensive query is: |
85 | * SELECT up_property, COUNT(*), count(qcc_title) |
86 | * FROM user_properties |
87 | * LEFT JOIN user ON up_user = user_id |
88 | * LEFT JOIN querycachetwo ON user_name = qcc_title AND qcc_type = 'activeusers' |
89 | * WHERE up_property LIKE 'gadget-%' AND up_value NOT IN ('0','') |
90 | * GROUP BY up_property; |
91 | * @return array |
92 | */ |
93 | public function getQueryInfo() { |
94 | $dbr = $this->dbProvider->getReplicaDatabase(); |
95 | |
96 | $conds = [ |
97 | $dbr->expr( 'up_property', IExpression::LIKE, new LikeValue( 'gadget-', $dbr->anyString() ) ), |
98 | // Simulate php falsy condition to ignore disabled user preferences |
99 | $dbr->expr( 'up_value', '!=', [ '0', '' ] ), |
100 | ]; |
101 | |
102 | if ( !$this->isActiveUsersEnabled() ) { |
103 | return [ |
104 | 'tables' => [ 'user_properties' ], |
105 | 'fields' => [ |
106 | 'title' => 'up_property', |
107 | 'value' => 'COUNT(*)', |
108 | // Required field, but unused |
109 | 'namespace' => NS_MEDIAWIKI |
110 | ], |
111 | 'conds' => $conds, |
112 | 'options' => [ |
113 | 'GROUP BY' => [ 'up_property' ] |
114 | ] |
115 | ]; |
116 | } |
117 | |
118 | return [ |
119 | 'tables' => [ 'user_properties', 'user', 'querycachetwo' ], |
120 | 'fields' => [ |
121 | 'title' => 'up_property', |
122 | 'value' => 'COUNT(*)', |
123 | // Need to pick fields existing in the querycache table so that the results are cachable |
124 | 'namespace' => 'COUNT( qcc_title )' |
125 | ], |
126 | 'conds' => $conds, |
127 | 'options' => [ |
128 | 'GROUP BY' => [ 'up_property' ] |
129 | ], |
130 | 'join_conds' => [ |
131 | 'user' => [ |
132 | 'LEFT JOIN', [ |
133 | 'up_user = user_id' |
134 | ] |
135 | ], |
136 | 'querycachetwo' => [ |
137 | 'LEFT JOIN', [ |
138 | 'user_name = qcc_title', |
139 | 'qcc_type' => 'activeusers', |
140 | ] |
141 | ] |
142 | ] |
143 | ]; |
144 | } |
145 | |
146 | public function getOrderFields() { |
147 | return [ 'value' ]; |
148 | } |
149 | |
150 | /** |
151 | * Output the start of the table |
152 | * Including opening <table>, the thead element with column headers |
153 | * and the opening <tbody>. |
154 | */ |
155 | protected function outputTableStart() { |
156 | $html = ''; |
157 | $headers = [ 'gadgetusage-gadget', 'gadgetusage-usercount' ]; |
158 | if ( $this->isActiveUsersEnabled() ) { |
159 | $headers[] = 'gadgetusage-activeusers'; |
160 | } |
161 | foreach ( $headers as $h ) { |
162 | if ( $h === 'gadgetusage-gadget' ) { |
163 | $html .= Html::element( 'th', [], $this->msg( $h )->text() ); |
164 | } else { |
165 | $html .= Html::element( 'th', [ 'data-sort-type' => 'number' ], |
166 | $this->msg( $h )->text() ); |
167 | } |
168 | } |
169 | |
170 | $this->getOutput()->addHTML( |
171 | Html::openElement( 'table', [ 'class' => [ 'sortable', 'wikitable' ] ] ) . |
172 | Html::rawElement( 'thead', [], Html::rawElement( 'tr', [], $html ) ) . |
173 | Html::openElement( 'tbody', [] ) |
174 | ); |
175 | $this->getOutput()->addModuleStyles( 'jquery.tablesorter.styles' ); |
176 | $this->getOutput()->addModules( 'jquery.tablesorter' ); |
177 | } |
178 | |
179 | /** |
180 | * Output the end of the table |
181 | * </tbody></table> |
182 | */ |
183 | protected function outputTableEnd() { |
184 | $this->getOutput()->addHTML( |
185 | Html::closeElement( 'tbody' ) . |
186 | Html::closeElement( 'table' ) |
187 | ); |
188 | } |
189 | |
190 | /** |
191 | * @param Skin $skin |
192 | * @param stdClass $result Result row |
193 | * @return string|bool String of HTML |
194 | */ |
195 | public function formatResult( $skin, $result ) { |
196 | $gadgetTitle = substr( $result->title, 7 ); |
197 | $gadgetUserCount = $this->getLanguage()->formatNum( $result->value ); |
198 | if ( $gadgetTitle ) { |
199 | $html = ''; |
200 | // "Gadget" column |
201 | $link = $this->getLinkRenderer()->makeLink( |
202 | new TitleValue( NS_SPECIAL, 'Gadgets', 'gadget-' . $gadgetTitle ), |
203 | $gadgetTitle |
204 | ); |
205 | $html .= Html::rawElement( 'td', [], $link ); |
206 | // "Number of users" column |
207 | $html .= Html::element( 'td', [], $gadgetUserCount ); |
208 | // "Active users" column |
209 | if ( $this->getConfig()->get( 'SpecialGadgetUsageActiveUsers' ) ) { |
210 | $activeUserCount = $this->getLanguage()->formatNum( $result->namespace ); |
211 | $html .= Html::element( 'td', [], $activeUserCount ); |
212 | } |
213 | return Html::rawElement( 'tr', [], $html ); |
214 | } |
215 | return false; |
216 | } |
217 | |
218 | /** |
219 | * Get a list of default gadgets |
220 | * @param array $gadgetIds list of gagdet ids registered in the wiki |
221 | * @return array |
222 | */ |
223 | protected function getDefaultGadgets( $gadgetIds ) { |
224 | $gadgetsList = []; |
225 | foreach ( $gadgetIds as $g ) { |
226 | $gadget = $this->gadgetRepo->getGadget( $g ); |
227 | if ( $gadget->isOnByDefault() ) { |
228 | $gadgetsList[] = $gadget->getName(); |
229 | } |
230 | } |
231 | asort( $gadgetsList, SORT_STRING | SORT_FLAG_CASE ); |
232 | return $gadgetsList; |
233 | } |
234 | |
235 | /** |
236 | * Format and output report results using the given information plus |
237 | * OutputPage |
238 | * |
239 | * @param OutputPage $out OutputPage to print to |
240 | * @param Skin $skin User skin to use |
241 | * @param IReadableDatabase $dbr Database (read) connection to use |
242 | * @param IResultWrapper $res Result pointer |
243 | * @param int $num Number of available result rows |
244 | * @param int $offset Paging offset |
245 | */ |
246 | protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) { |
247 | $gadgetIds = $this->gadgetRepo->getGadgetIds(); |
248 | $defaultGadgets = $this->getDefaultGadgets( $gadgetIds ); |
249 | if ( $this->isActiveUsersEnabled() ) { |
250 | $out->addHtml( |
251 | $this->msg( 'gadgetusage-intro' ) |
252 | ->numParams( $this->getConfig()->get( 'ActiveUserDays' ) )->parseAsBlock() |
253 | ); |
254 | } else { |
255 | $out->addHtml( |
256 | $this->msg( 'gadgetusage-intro-noactive' )->parseAsBlock() |
257 | ); |
258 | } |
259 | if ( $num > 0 ) { |
260 | $this->outputTableStart(); |
261 | // Append default gadgets to the table with 'default' in the total and active user fields |
262 | foreach ( $defaultGadgets as $default ) { |
263 | $html = ''; |
264 | // "Gadget" column |
265 | $link = $this->getLinkRenderer()->makeLink( |
266 | new TitleValue( NS_SPECIAL, 'Gadgets', 'gadget-' . $default ), |
267 | $default |
268 | ); |
269 | $html .= Html::rawElement( 'td', [], $link ); |
270 | // "Number of users" column |
271 | $html .= Html::element( 'td', [ 'data-sort-value' => 'Infinity' ], |
272 | $this->msg( 'gadgetusage-default' )->text() ); |
273 | // "Active users" column |
274 | if ( $this->isActiveUsersEnabled() ) { |
275 | $html .= Html::element( 'td', [ 'data-sort-value' => 'Infinity' ], |
276 | $this->msg( 'gadgetusage-default' )->text() ); |
277 | } |
278 | $out->addHTML( Html::rawElement( 'tr', [], $html ) ); |
279 | } |
280 | foreach ( $res as $row ) { |
281 | // Remove the 'gadget-' part of the result string and compare if it's present |
282 | // in $defaultGadgets, if not we format it and add it to the output |
283 | $name = substr( $row->title, 7 ); |
284 | |
285 | // Only pick gadgets which are in the list $gadgetIds to make sure they exist |
286 | if ( !in_array( $name, $defaultGadgets, true ) && in_array( $name, $gadgetIds, true ) ) { |
287 | $line = $this->formatResult( $skin, $row ); |
288 | if ( $line ) { |
289 | $out->addHTML( $line ); |
290 | } |
291 | } |
292 | } |
293 | // Close table element |
294 | $this->outputTableEnd(); |
295 | } else { |
296 | $out->addHtml( |
297 | $this->msg( 'gadgetusage-noresults' )->parseAsBlock() |
298 | ); |
299 | } |
300 | } |
301 | |
302 | /** |
303 | * @inheritDoc |
304 | */ |
305 | protected function getGroupName() { |
306 | return 'wiki'; |
307 | } |
308 | } |