Test Failed
Push — ip-ranges ( af8775 )
by MusikAnimal
07:44
created

User   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 301
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 69
c 2
b 0
f 0
dl 0
loc 301
rs 9.44
wmc 37

23 Methods

Rating   Name   Duplication   Size   Complexity  
A isIPv6() 0 3 1
A getActorId() 0 5 1
A getPrettyUsername() 0 6 2
A getCacheKey() 0 3 1
A getEditCount() 0 13 2
A getGlobalUserRights() 0 3 1
A isBlocked() 0 3 1
A hasTooManyEdits() 0 4 2
A isQueryableRange() 0 10 3
A getUserRights() 0 3 1
A countEdits() 0 3 1
A getBlockExpiry() 0 13 3
A getRegistrationDate() 0 10 2
A isAdmin() 0 3 1
A isCurrentlyLoggedIn() 0 8 3
A maxEdits() 0 3 1
A __construct() 0 10 3
A existsOnProject() 0 3 1
A isIpRange() 0 3 1
A isAnon() 0 3 1
A getUsernameIdent() 0 6 2
A getUsername() 0 3 1
A getId() 0 8 2
1
<?php
2
/**
3
 * This file contains only the User class.
4
 */
5
6
declare(strict_types = 1);
7
8
namespace AppBundle\Model;
9
10
use DateTime;
11
use Exception;
12
use Wikimedia\IPUtils;
13
14
/**
15
 * A User is a wiki user who has the same username across all projects in an XTools installation.
16
 */
17
class User extends Model
18
{
19
    /** @var int Maximum queryable range for IPv4. */
20
    public const MAX_IPV4_CIDR = 16;
21
22
    /** @var int Maximum queryable range for IPv6. */
23
    public const MAX_IPV6_CIDR = 32;
24
25
    /** @var string The user's username. */
26
    protected $username;
27
28
    /** @var int Quick cache of edit counts, keyed by project domain. */
29
    protected $editCounts = [];
30
31
    /**
32
     * Create a new User given a username.
33
     * @param string $username
34
     */
35
    public function __construct(string $username)
36
    {
37
        if ('ipr-' === substr($username, 0, 4)) {
38
            $username = substr($username, 4);
39
        }
40
        $this->username = ucfirst(str_replace('_', ' ', trim($username)));
41
42
        // IPv6 address are stored as uppercase in the database.
43
        if ($this->isAnon()) {
44
            $this->username = IPUtils::sanitizeIP($this->username);
45
        }
46
    }
47
48
    /**
49
     * Unique identifier for this User, to be used in cache keys. Use of md5 ensures the cache key does not contain
50
     * reserved characters. You could also use the ID, but that may require an unnecessary DB query.
51
     * @see Repository::getCacheKey()
52
     * @return string
53
     */
54
    public function getCacheKey(): string
55
    {
56
        return md5($this->username);
57
    }
58
59
    /**
60
     * Get the username.
61
     * @return string
62
     */
63
    public function getUsername(): string
64
    {
65
        return $this->username;
66
    }
67
68
    /**
69
     * Get a prettified username for IP addresses. For accounts, just the username is returned.
70
     * @return string
71
     */
72
    public function getPrettyUsername(): string
73
    {
74
        if (!$this->isAnon()) {
75
            return $this->username;
76
        }
77
        return IPUtils::prettifyIP($this->username);
78
    }
79
80
    /**
81
     * Get the username identifier that should be used in routing. This only matters for IP ranges,
82
     * which get prefixed with 'ipr-' to ensure they don't conflict with other routing params (such as namespace ID).
83
     * Always use this method when linking to or generating internal routes, and use it nowhere else.
84
     * @return string
85
     */
86
    public function getUsernameIdent(): string
87
    {
88
        if ($this->isIpRange()) {
89
            return 'ipr-'.$this->username;
90
        }
91
        return $this->username;
92
    }
93
94
    /**
95
     * Is this an IP range?
96
     * @return bool
97
     */
98
    public function isIpRange(): bool
99
    {
100
        return IPUtils::isValidRange($this->username);
101
    }
102
103
    /**
104
     * Is this an IPv6 address or range?
105
     * @return bool
106
     */
107
    public function isIPv6(): bool
108
    {
109
        return IPUtils::isIPv6($this->username);
110
    }
111
112
    /**
113
     * Is this IP range outside the queryable limits?
114
     * @return bool
115
     */
116
    public function isQueryableRange(): bool
117
    {
118
        if (!$this->isIpRange()) {
119
            return true;
120
        }
121
122
        [, $bits] = IPUtils::parseCIDR($this->username);
123
        $limit = $this->isIPv6() ? self::MAX_IPV6_CIDR : self::MAX_IPV4_CIDR;
124
125
        return $limit < (int)$bits;
126
    }
127
128
    /**
129
     * Get the user's ID on the given project.
130
     * @param Project $project
131
     * @return int|null
132
     */
133
    public function getId(Project $project): ?int
134
    {
135
        $ret = $this->getRepository()->getIdAndRegistration(
0 ignored issues
show
Bug introduced by
The method getIdAndRegistration() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\UserRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

135
        $ret = $this->getRepository()->/** @scrutinizer ignore-call */ getIdAndRegistration(
Loading history...
136
            $project->getDatabaseName(),
137
            $this->getUsername()
138
        );
139
140
        return $ret ? (int)$ret['userId'] : null;
141
    }
142
143
    /**
144
     * Get the user's actor ID on the given project.
145
     * @param Project $project
146
     * @return int
147
     */
148
    public function getActorId(Project $project): int
149
    {
150
        return (int)$this->getRepository()->getActorId(
0 ignored issues
show
Bug introduced by
The method getActorId() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\UserRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

150
        return (int)$this->getRepository()->/** @scrutinizer ignore-call */ getActorId(
Loading history...
151
            $project->getDatabaseName(),
152
            $this->getUsername()
153
        );
154
    }
155
156
    /**
157
     * Get the user's registration date on the given project.
158
     * @param Project $project
159
     * @return DateTime|null null if no registration date was found.
160
     */
161
    public function getRegistrationDate(Project $project): ?DateTime
162
    {
163
        $ret = $this->getRepository()->getIdAndRegistration(
164
            $project->getDatabaseName(),
165
            $this->getUsername()
166
        );
167
168
        return null !== $ret['regDate']
169
            ? DateTime::createFromFormat('YmdHis', $ret['regDate'])
170
            : null;
171
    }
172
173
    /**
174
     * Get a user's local user rights on the given Project.
175
     * @param Project $project
176
     * @return string[]
177
     */
178
    public function getUserRights(Project $project): array
179
    {
180
        return $this->getRepository()->getUserRights($project, $this);
0 ignored issues
show
Bug introduced by
The method getUserRights() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\UserRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

180
        return $this->getRepository()->/** @scrutinizer ignore-call */ getUserRights($project, $this);
Loading history...
181
    }
182
183
    /**
184
     * Get a list of this user's global rights.
185
     * @param Project|null $project A project to query; if not provided, the default will be used.
186
     * @return string[]
187
     */
188
    public function getGlobalUserRights(?Project $project = null): array
189
    {
190
        return $this->getRepository()->getGlobalUserRights($this->getUsername(), $project);
0 ignored issues
show
Bug introduced by
The method getGlobalUserRights() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\UserRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

190
        return $this->getRepository()->/** @scrutinizer ignore-call */ getGlobalUserRights($this->getUsername(), $project);
Loading history...
191
    }
192
193
    /**
194
     * Get the user's (system) edit count.
195
     * @param Project $project
196
     * @return int
197
     */
198
    public function getEditCount(Project $project): int
199
    {
200
        $domain = $project->getDomain();
201
        if (isset($this->editCounts[$domain])) {
202
            return $this->editCounts[$domain];
203
        }
204
205
        $this->editCounts[$domain] = (int)$this->getRepository()->getEditCount(
0 ignored issues
show
Bug introduced by
The method getEditCount() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\UserRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

205
        $this->editCounts[$domain] = (int)$this->getRepository()->/** @scrutinizer ignore-call */ getEditCount(
Loading history...
206
            $project->getDatabaseName(),
207
            $this->getUsername()
208
        );
209
210
        return $this->editCounts[$domain];
211
    }
212
213
    /**
214
     * Maximum number of edits to process, based on configuration.
215
     * @return int
216
     */
217
    public function maxEdits(): int
218
    {
219
        return $this->getRepository()->maxEdits();
0 ignored issues
show
Bug introduced by
The method maxEdits() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\UserRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

219
        return $this->getRepository()->/** @scrutinizer ignore-call */ maxEdits();
Loading history...
220
    }
221
222
    /**
223
     * Does this user exist on the given project.
224
     * @param Project $project
225
     * @return bool
226
     */
227
    public function existsOnProject(Project $project): bool
228
    {
229
        return $this->getId($project) > 0;
230
    }
231
232
    /**
233
     * Is this user an Administrator on the given project?
234
     * @param Project $project The project.
235
     * @return bool
236
     */
237
    public function isAdmin(Project $project): bool
238
    {
239
        return false !== array_search('sysop', $this->getUserRights($project));
240
    }
241
242
    /**
243
     * Is this user an anonymous user (IP)?
244
     * @return bool
245
     */
246
    public function isAnon(): bool
247
    {
248
        return IPUtils::isIPAddress($this->username);
249
    }
250
251
    /**
252
     * Get the expiry of the current block on the user
253
     * @param Project $project The project.
254
     * @return DateTime|bool Expiry as DateTime, true if indefinite, or false if they are not blocked.
255
     */
256
    public function getBlockExpiry(Project $project)
257
    {
258
        $expiry = $this->getRepository()->getBlockExpiry(
0 ignored issues
show
Bug introduced by
The method getBlockExpiry() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\UserRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

258
        $expiry = $this->getRepository()->/** @scrutinizer ignore-call */ getBlockExpiry(
Loading history...
259
            $project->getDatabaseName(),
260
            $this->getUsername()
261
        );
262
263
        if ('infinity' === $expiry) {
264
            return true;
265
        } elseif (false === $expiry) {
266
            return false;
267
        } else {
268
            return new DateTime($expiry);
269
        }
270
    }
271
272
    /**
273
     * Is this user currently blocked on the given project?
274
     * @param Project $project The project.
275
     * @return bool
276
     */
277
    public function isBlocked(Project $project): bool
278
    {
279
        return false !== $this->getBlockExpiry($project);
280
    }
281
282
    /**
283
     * Does the user have more edits than maximum amount allowed for processing?
284
     * @param Project $project
285
     * @return bool
286
     */
287
    public function hasTooManyEdits(Project $project): bool
288
    {
289
        $editCount = $this->getEditCount($project);
290
        return $this->maxEdits() > 0 && $editCount > $this->maxEdits();
291
    }
292
293
    /**
294
     * Get edit count within given timeframe and namespace
295
     * @param Project $project
296
     * @param int|string $namespace Namespace ID or 'all' for all namespaces
297
     * @param int|false $start Start date as Unix timestamp.
298
     * @param int|false $end End date as Unix timestamp.
299
     * @return int
300
     */
301
    public function countEdits(Project $project, $namespace = 'all', $start = false, $end = false): int
302
    {
303
        return (int) $this->getRepository()->countEdits($project, $this, $namespace, $start, $end);
0 ignored issues
show
Bug introduced by
The method countEdits() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\UserRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

303
        return (int) $this->getRepository()->/** @scrutinizer ignore-call */ countEdits($project, $this, $namespace, $start, $end);
Loading history...
304
    }
305
306
    /**
307
     * Is this user the same as the current XTools user?
308
     * @return bool
309
     */
310
    public function isCurrentlyLoggedIn(): bool
311
    {
312
        try {
313
            $ident = $this->getRepository()->getXtoolsUserInfo();
0 ignored issues
show
Bug introduced by
The method getXtoolsUserInfo() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\UserRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

313
            $ident = $this->getRepository()->/** @scrutinizer ignore-call */ getXtoolsUserInfo();
Loading history...
314
        } catch (Exception $exception) {
315
            return false;
316
        }
317
        return isset($ident->username) && $ident->username === $this->getUsername();
318
    }
319
}
320