mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Gadgets
synced 2024-12-03 19:36:19 +00:00
dc84f149bc
The column user_properties.up_value is a text based type in all rdbms. postgres does not autoconvert strings to numbers, so SUM( up_value ) gives an error when running on postgres. The use of SUM( up_value ) is also problematic on mariadb/sqlite, as up_value can be set to any number via the Action API's options module. This value goes into the sum and produce a wrong result. Using a non-number value allows to remove the value from the sum and that also produce a wrong result. Change the query to exclude the rows which disables a gadget and just count the "active" ones. Typically there are only rows to disable a gadget, when the gadget is marked as default, but default gadget are displayed with the message 'gadgetusage-default' and does not needs to be counted. In case a gadget was default in the past, there could always such rows, so be safe and exclude them. That means it is safe to only count for "active" gadget option usage. This also allows to remove the up_value condition on the join to querycachetwo as only rows to enable a gadget are now selected and get joined to find the active users. For the join condition use an assoc array, as for database condition the database class is quoting the value of that array, if that is a string. In postgres the " is used to quote columns or tables, but not values, that gives a query error on postgres about the condition. fixed when using the array style and not "raw" sql. Define the condition only once as it should be the same for both queries. Use the new IDatabase::expr function to write that sql piece. Change-Id: I8dfc3fd5adc4c4bdabceaab20c4b37ffd48e6bee
307 lines
8.9 KiB
PHP
307 lines
8.9 KiB
PHP
<?php
|
|
/**
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*
|
|
* @file
|
|
*/
|
|
|
|
namespace MediaWiki\Extension\Gadgets\Special;
|
|
|
|
use MediaWiki\Extension\Gadgets\GadgetRepo;
|
|
use MediaWiki\Html\Html;
|
|
use MediaWiki\Output\OutputPage;
|
|
use MediaWiki\SpecialPage\QueryPage;
|
|
use MediaWiki\Title\TitleValue;
|
|
use Skin;
|
|
use stdClass;
|
|
use Wikimedia\Rdbms\IDatabase;
|
|
use Wikimedia\Rdbms\IExpression;
|
|
use Wikimedia\Rdbms\IResultWrapper;
|
|
use Wikimedia\Rdbms\LikeValue;
|
|
|
|
/**
|
|
* Special:GadgetUsage lists all the gadgets on the wiki along with number of users.
|
|
*
|
|
* @copyright 2015 Niharika Kohli
|
|
*/
|
|
class SpecialGadgetUsage extends QueryPage {
|
|
private GadgetRepo $gadgetRepo;
|
|
|
|
public function __construct( GadgetRepo $gadgetRepo ) {
|
|
parent::__construct( 'GadgetUsage' );
|
|
$this->gadgetRepo = $gadgetRepo;
|
|
$this->limit = 1000; // Show all gadgets
|
|
$this->shownavigation = false;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function execute( $par ) {
|
|
parent::execute( $par );
|
|
$this->addHelpLink( 'Extension:Gadgets' );
|
|
}
|
|
|
|
/**
|
|
* Get value of config variable SpecialGadgetUsageActiveUsers
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function isActiveUsersEnabled() {
|
|
return $this->getConfig()->get( 'SpecialGadgetUsageActiveUsers' );
|
|
}
|
|
|
|
public function isExpensive() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Define the database query that is used to generate the stats table.
|
|
* This uses 1 of 2 possible queries, depending on $wgSpecialGadgetUsageActiveUsers.
|
|
*
|
|
* The simple query is essentially:
|
|
* SELECT up_property, COUNT(*)
|
|
* FROM user_properties
|
|
* WHERE up_property LIKE 'gadget-%' AND up_value NOT IN ('0','')
|
|
* GROUP BY up_property;
|
|
*
|
|
* The more expensive query is:
|
|
* SELECT up_property, COUNT(*), count(qcc_title)
|
|
* FROM user_properties
|
|
* LEFT JOIN user ON up_user = user_id
|
|
* LEFT JOIN querycachetwo ON user_name = qcc_title AND qcc_type = 'activeusers'
|
|
* WHERE up_property LIKE 'gadget-%' AND up_value NOT IN ('0','')
|
|
* GROUP BY up_property;
|
|
* @return array
|
|
*/
|
|
public function getQueryInfo() {
|
|
$dbr = wfGetDB( DB_REPLICA );
|
|
|
|
$conds = [
|
|
$dbr->expr( 'up_property', IExpression::LIKE, new LikeValue( 'gadget-', $dbr->anyString() ) ),
|
|
// Simulate php falsy condition to ignore disabled user preferences
|
|
$dbr->expr( 'up_value', '!=', [ '0', '' ] ),
|
|
];
|
|
|
|
if ( !$this->isActiveUsersEnabled() ) {
|
|
return [
|
|
'tables' => [ 'user_properties' ],
|
|
'fields' => [
|
|
'title' => 'up_property',
|
|
'value' => 'COUNT(*)',
|
|
'namespace' => NS_GADGET
|
|
],
|
|
'conds' => $conds,
|
|
'options' => [
|
|
'GROUP BY' => [ 'up_property' ]
|
|
]
|
|
];
|
|
}
|
|
|
|
return [
|
|
'tables' => [ 'user_properties', 'user', 'querycachetwo' ],
|
|
'fields' => [
|
|
'title' => 'up_property',
|
|
'value' => 'COUNT(*)',
|
|
// Need to pick fields existing in the querycache table so that the results are cachable
|
|
'namespace' => 'COUNT( qcc_title )'
|
|
],
|
|
'conds' => $conds,
|
|
'options' => [
|
|
'GROUP BY' => [ 'up_property' ]
|
|
],
|
|
'join_conds' => [
|
|
'user' => [
|
|
'LEFT JOIN', [
|
|
'up_user = user_id'
|
|
]
|
|
],
|
|
'querycachetwo' => [
|
|
'LEFT JOIN', [
|
|
'user_name = qcc_title',
|
|
'qcc_type' => 'activeusers',
|
|
]
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
public function getOrderFields() {
|
|
return [ 'value' ];
|
|
}
|
|
|
|
/**
|
|
* Output the start of the table
|
|
* Including opening <table>, the thead element with column headers
|
|
* and the opening <tbody>.
|
|
*/
|
|
protected function outputTableStart() {
|
|
$html = Html::openElement( 'table', [ 'class' => [ 'sortable', 'wikitable' ] ] );
|
|
$html .= Html::openElement( 'thead', [] );
|
|
$html .= Html::openElement( 'tr', [] );
|
|
$headers = [ 'gadgetusage-gadget', 'gadgetusage-usercount' ];
|
|
if ( $this->isActiveUsersEnabled() ) {
|
|
$headers[] = 'gadgetusage-activeusers';
|
|
}
|
|
foreach ( $headers as $h ) {
|
|
if ( $h === 'gadgetusage-gadget' ) {
|
|
$html .= Html::element( 'th', [], $this->msg( $h )->text() );
|
|
} else {
|
|
$html .= Html::element( 'th', [ 'data-sort-type' => 'number' ],
|
|
$this->msg( $h )->text() );
|
|
}
|
|
}
|
|
$html .= Html::closeElement( 'tr' );
|
|
$html .= Html::closeElement( 'thead' );
|
|
$html .= Html::openElement( 'tbody', [] );
|
|
$this->getOutput()->addHTML( $html );
|
|
$this->getOutput()->addModuleStyles( 'jquery.tablesorter.styles' );
|
|
$this->getOutput()->addModules( 'jquery.tablesorter' );
|
|
}
|
|
|
|
/**
|
|
* Output the end of the table
|
|
* </tbody></table>
|
|
*/
|
|
protected function outputTableEnd() {
|
|
$this->getOutput()->addHTML(
|
|
Html::closeElement( 'tbody' ) .
|
|
Html::closeElement( 'table' )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param Skin $skin
|
|
* @param stdClass $result Result row
|
|
* @return string|bool String of HTML
|
|
*/
|
|
public function formatResult( $skin, $result ) {
|
|
$gadgetTitle = substr( $result->title, 7 );
|
|
$gadgetUserCount = $this->getLanguage()->formatNum( $result->value );
|
|
if ( $gadgetTitle ) {
|
|
$html = Html::openElement( 'tr', [] );
|
|
// "Gadget" column
|
|
$link = $this->getLinkRenderer()->makeLink(
|
|
new TitleValue( NS_SPECIAL, 'Gadgets', 'gadget-' . $gadgetTitle ),
|
|
$gadgetTitle
|
|
);
|
|
$html .= Html::rawElement( 'td', [], $link );
|
|
// "Number of users" column
|
|
$html .= Html::element( 'td', [], $gadgetUserCount );
|
|
// "Active users" column
|
|
if ( $this->getConfig()->get( 'SpecialGadgetUsageActiveUsers' ) ) {
|
|
$activeUserCount = $this->getLanguage()->formatNum( $result->namespace );
|
|
$html .= Html::element( 'td', [], $activeUserCount );
|
|
}
|
|
$html .= Html::closeElement( 'tr' );
|
|
return $html;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get a list of default gadgets
|
|
* @param array $gadgetIds list of gagdet ids registered in the wiki
|
|
* @return array
|
|
*/
|
|
protected function getDefaultGadgets( $gadgetIds ) {
|
|
$gadgetsList = [];
|
|
foreach ( $gadgetIds as $g ) {
|
|
$gadget = $this->gadgetRepo->getGadget( $g );
|
|
if ( $gadget->isOnByDefault() ) {
|
|
$gadgetsList[] = $gadget->getName();
|
|
}
|
|
}
|
|
asort( $gadgetsList, SORT_STRING | SORT_FLAG_CASE );
|
|
return $gadgetsList;
|
|
}
|
|
|
|
/**
|
|
* Format and output report results using the given information plus
|
|
* OutputPage
|
|
*
|
|
* @param OutputPage $out OutputPage to print to
|
|
* @param Skin $skin User skin to use
|
|
* @param IDatabase $dbr Database (read) connection to use
|
|
* @param IResultWrapper $res Result pointer
|
|
* @param int $num Number of available result rows
|
|
* @param int $offset Paging offset
|
|
*/
|
|
protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
|
|
$gadgetIds = $this->gadgetRepo->getGadgetIds();
|
|
$defaultGadgets = $this->getDefaultGadgets( $gadgetIds );
|
|
if ( $this->isActiveUsersEnabled() ) {
|
|
$out->addHtml(
|
|
$this->msg( 'gadgetusage-intro' )
|
|
->numParams( $this->getConfig()->get( 'ActiveUserDays' ) )->parseAsBlock()
|
|
);
|
|
} else {
|
|
$out->addHtml(
|
|
$this->msg( 'gadgetusage-intro-noactive' )->parseAsBlock()
|
|
);
|
|
}
|
|
if ( $num > 0 ) {
|
|
$this->outputTableStart();
|
|
// Append default gadgets to the table with 'default' in the total and active user fields
|
|
foreach ( $defaultGadgets as $default ) {
|
|
$html = Html::openElement( 'tr', [] );
|
|
// "Gadget" column
|
|
$link = $this->getLinkRenderer()->makeLink(
|
|
new TitleValue( NS_SPECIAL, 'Gadgets', 'gadget-' . $default ),
|
|
$default
|
|
);
|
|
$html .= Html::rawElement( 'td', [], $link );
|
|
// "Number of users" column
|
|
$html .= Html::element( 'td', [ 'data-sort-value' => 'Infinity' ],
|
|
$this->msg( 'gadgetusage-default' )->text() );
|
|
// "Active users" column
|
|
if ( $this->isActiveUsersEnabled() ) {
|
|
$html .= Html::element( 'td', [ 'data-sort-value' => 'Infinity' ],
|
|
$this->msg( 'gadgetusage-default' )->text() );
|
|
}
|
|
$html .= Html::closeElement( 'tr' );
|
|
$out->addHTML( $html );
|
|
}
|
|
foreach ( $res as $row ) {
|
|
// Remove the 'gadget-' part of the result string and compare if it's present
|
|
// in $defaultGadgets, if not we format it and add it to the output
|
|
$name = substr( $row->title, 7 );
|
|
|
|
// Only pick gadgets which are in the list $gadgetIds to make sure they exist
|
|
if ( !in_array( $name, $defaultGadgets, true ) && in_array( $name, $gadgetIds, true ) ) {
|
|
$line = $this->formatResult( $skin, $row );
|
|
if ( $line ) {
|
|
$out->addHTML( $line );
|
|
}
|
|
}
|
|
}
|
|
// Close table element
|
|
$this->outputTableEnd();
|
|
} else {
|
|
$out->addHtml(
|
|
$this->msg( 'gadgetusage-noresults' )->parseAsBlock()
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
protected function getGroupName() {
|
|
return 'wiki';
|
|
}
|
|
}
|