Completed
Branch master (1b526b)
by
unknown
29:06
created

ActiveUsersPager   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 165
Duplicated Lines 3.64 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 6
loc 165
rs 10
wmc 21
lcom 1
cbo 11

5 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 6 21 5
A getIndexField() 0 3 1
B getQueryInfo() 0 37 4
B doBatchLookups() 0 24 3
C formatRow() 0 48 8

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * Copyright © 2008 Aaron Schulz
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 Pager
22
 */
23
24
/**
25
 * This class is used to get a list of active users. The ones with specials
26
 * rights (sysop, bureaucrat, developer) will have them displayed
27
 * next to their names.
28
 *
29
 * @ingroup Pager
30
 */
31
class ActiveUsersPager extends UsersPager {
32
33
	/**
34
	 * @var FormOptions
35
	 */
36
	protected $opts;
37
38
	/**
39
	 * @var array
40
	 */
41
	protected $hideGroups = [];
42
43
	/**
44
	 * @var array
45
	 */
46
	protected $hideRights = [];
47
48
	/**
49
	 * @var array
50
	 */
51
	private $blockStatusByUid;
52
53
	/**
54
	 * @param IContextSource $context
55
	 * @param FormOptions $opts
56
	 */
57
	function __construct( IContextSource $context = null, FormOptions $opts ) {
58
		parent::__construct( $context );
59
60
		$this->RCMaxAge = $this->getConfig()->get( 'ActiveUserDays' );
0 ignored issues
show
Bug introduced by
The property RCMaxAge 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...
61
		$this->requestedUser = '';
0 ignored issues
show
Bug introduced by
The property requestedUser 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...
62
63
		$un = $opts->getValue( 'username' );
64 View Code Duplication
		if ( $un != '' ) {
65
			$username = Title::makeTitleSafe( NS_USER, $un );
66
			if ( !is_null( $username ) ) {
67
				$this->requestedUser = $username->getText();
68
			}
69
		}
70
71
		if ( $opts->getValue( 'hidebots' ) == 1 ) {
72
			$this->hideRights[] = 'bot';
73
		}
74
		if ( $opts->getValue( 'hidesysops' ) == 1 ) {
75
			$this->hideGroups[] = 'sysop';
76
		}
77
	}
78
79
	function getIndexField() {
80
		return 'qcc_title';
81
	}
82
83
	function getQueryInfo() {
84
		$dbr = $this->getDatabase();
85
86
		$activeUserSeconds = $this->getConfig()->get( 'ActiveUserDays' ) * 86400;
87
		$timestamp = $dbr->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
88
		$conds = [
89
			'qcc_type' => 'activeusers',
90
			'qcc_namespace' => NS_USER,
91
			'user_name = qcc_title',
92
			'rc_user_text = qcc_title',
93
			'rc_type != ' . $dbr->addQuotes( RC_EXTERNAL ), // Don't count wikidata.
94
			'rc_type != ' . $dbr->addQuotes( RC_CATEGORIZE ), // Don't count categorization changes.
95
			'rc_log_type IS NULL OR rc_log_type != ' . $dbr->addQuotes( 'newusers' ),
96
			'rc_timestamp >= ' . $dbr->addQuotes( $timestamp ),
0 ignored issues
show
Security Bug introduced by
It seems like $timestamp defined by $dbr->timestamp(wfTimest...) - $activeUserSeconds) on line 87 can also be of type false; however, DatabaseBase::addQuotes() does only seem to accept string|object<Blob>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
97
		];
98
		if ( $this->requestedUser != '' ) {
99
			$conds[] = 'qcc_title >= ' . $dbr->addQuotes( $this->requestedUser );
100
		}
101
		if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
102
			$conds[] = 'NOT EXISTS (' . $dbr->selectSQLText(
103
					'ipblocks', '1', [ 'ipb_user=user_id', 'ipb_deleted' => 1 ]
104
				) . ')';
105
		}
106
107
		if ( $dbr->implicitGroupby() ) {
108
			$options = [ 'GROUP BY' => [ 'qcc_title' ] ];
109
		} else {
110
			$options = [ 'GROUP BY' => [ 'user_name', 'user_id', 'qcc_title' ] ];
111
		}
112
113
		return [
114
			'tables' => [ 'querycachetwo', 'user', 'recentchanges' ],
115
			'fields' => [ 'user_name', 'user_id', 'recentedits' => 'COUNT(*)', 'qcc_title' ],
116
			'options' => $options,
117
			'conds' => $conds
118
		];
119
	}
120
121
	function doBatchLookups() {
122
		parent::doBatchLookups();
123
124
		$uids = [];
125
		foreach ( $this->mResult as $row ) {
126
			$uids[] = $row->user_id;
127
		}
128
		// Fetch the block status of the user for showing "(blocked)" text and for
129
		// striking out names of suppressed users when privileged user views the list.
130
		// Although the first query already hits the block table for un-privileged, this
131
		// is done in two queries to avoid huge quicksorts and to make COUNT(*) correct.
132
		$dbr = $this->getDatabase();
133
		$res = $dbr->select( 'ipblocks',
134
			[ 'ipb_user', 'MAX(ipb_deleted) AS block_status' ],
135
			[ 'ipb_user' => $uids ],
136
			__METHOD__,
137
			[ 'GROUP BY' => [ 'ipb_user' ] ]
138
		);
139
		$this->blockStatusByUid = [];
140
		foreach ( $res as $row ) {
0 ignored issues
show
Bug introduced by
The expression $res of type object<ResultWrapper>|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
141
			$this->blockStatusByUid[$row->ipb_user] = $row->block_status; // 0 or 1
142
		}
143
		$this->mResult->seek( 0 );
144
	}
145
146
	function formatRow( $row ) {
147
		$userName = $row->user_name;
148
149
		$ulinks = Linker::userLink( $row->user_id, $userName );
150
		$ulinks .= Linker::userToolLinks( $row->user_id, $userName );
151
152
		$lang = $this->getLanguage();
153
154
		$list = [];
155
		$user = User::newFromId( $row->user_id );
156
157
		// User right filter
158
		foreach ( $this->hideRights as $right ) {
159
			// Calling User::getRights() within the loop so that
160
			// if the hideRights() filter is empty, we don't have to
161
			// trigger the lazy-init of the big userrights array in the
162
			// User object
163
			if ( in_array( $right, $user->getRights() ) ) {
164
				return '';
165
			}
166
		}
167
168
		// User group filter
169
		// Note: This is a different loop than for user rights,
170
		// because we're reusing it to build the group links
171
		// at the same time
172
		$groups_list = self::getGroups( intval( $row->user_id ), $this->userGroupCache );
173
		foreach ( $groups_list as $group ) {
174
			if ( in_array( $group, $this->hideGroups ) ) {
175
				return '';
176
			}
177
			$list[] = self::buildGroupLink( $group, $userName );
178
		}
179
180
		$groups = $lang->commaList( $list );
181
182
		$item = $lang->specialList( $ulinks, $groups );
183
184
		$isBlocked = isset( $this->blockStatusByUid[$row->user_id] );
185
		if ( $isBlocked && $this->blockStatusByUid[$row->user_id] == 1 ) {
186
			$item = "<span class=\"deleted\">$item</span>";
187
		}
188
		$count = $this->msg( 'activeusers-count' )->numParams( $row->recentedits )
189
			->params( $userName )->numParams( $this->RCMaxAge )->escaped();
190
		$blocked = $isBlocked ? ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() : '';
191
192
		return Html::rawElement( 'li', [], "{$item} [{$count}]{$blocked}" );
193
	}
194
195
}
196