Passed
Pull Request — main (#442)
by MusikAnimal
08:40 queued 04:21
created

UserRepository::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 7
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace App\Repository;
6
7
use App\Model\Project;
8
use App\Model\User;
9
use Doctrine\DBAL\Driver\ResultStatement;
10
use GuzzleHttp\Client;
11
use Psr\Cache\CacheItemPoolInterface;
12
use Psr\Container\ContainerInterface;
13
use Psr\Log\LoggerInterface;
14
use Symfony\Component\HttpFoundation\Session\Session;
15
use Wikimedia\IPUtils;
16
17
/**
18
 * This class provides data for the User class.
19
 * @codeCoverageIgnore
20
 */
21
class UserRepository extends Repository
22
{
23
    protected ProjectRepository $projectRepo;
24
25
    /**
26
     * @param ContainerInterface $container
27
     * @param CacheItemPoolInterface $cache
28
     * @param Client $guzzle
29
     * @param LoggerInterface $logger
30
     * @param bool $isWMF
31
     * @param int $queryTimeout
32
     * @param ProjectRepository $projectRepo
33
     */
34
    public function __construct(
35
        ContainerInterface $container,
36
        CacheItemPoolInterface $cache,
37
        Client $guzzle,
38
        LoggerInterface $logger,
39
        bool $isWMF,
40
        int $queryTimeout,
41
        ProjectRepository $projectRepo
42
    ) {
43
        $this->projectRepo = $projectRepo;
44
        parent::__construct($container, $cache, $guzzle, $logger, $isWMF, $queryTimeout);
45
    }
46
47
    /**
48
     * Get the user's ID and registration date.
49
     * @param string $databaseName The database to query.
50
     * @param string $username The username to find.
51
     * @return array|false With keys 'userId' and regDate'. false if user not found.
52
     */
53
    public function getIdAndRegistration(string $databaseName, string $username)
54
    {
55
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_id_reg');
56
        if ($this->cache->hasItem($cacheKey)) {
57
            return $this->cache->getItem($cacheKey)->get();
58
        }
59
60
        $userTable = $this->getTableName($databaseName, 'user');
61
        $sql = "SELECT user_id AS userId, user_registration AS regDate
62
                FROM $userTable
63
                WHERE user_name = :username
64
                LIMIT 1";
65
        $resultQuery = $this->executeProjectsQuery($databaseName, $sql, ['username' => $username]);
66
67
        // Cache and return.
68
        return $this->setCache($cacheKey, $resultQuery->fetchAssociative());
69
    }
70
71
    /**
72
     * Get the user's actor ID.
73
     * @param string $databaseName
74
     * @param string $username
75
     * @return int|null
76
     */
77
    public function getActorId(string $databaseName, string $username): ?int
78
    {
79
        if (IPUtils::isValidRange($username)) {
80
            return null;
81
        }
82
83
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_actor_id');
84
        if ($this->cache->hasItem($cacheKey)) {
85
            return (int)$this->cache->getItem($cacheKey)->get();
86
        }
87
88
        $actorTable = $this->getTableName($databaseName, 'actor');
89
90
        $sql = "SELECT actor_id
91
                FROM $actorTable
92
                WHERE actor_name = :username
93
                LIMIT 1";
94
        $resultQuery = $this->executeProjectsQuery($databaseName, $sql, ['username' => $username]);
95
96
        // Cache and return.
97
        return (int)$this->setCache($cacheKey, $resultQuery->fetchOne());
98
    }
99
100
    /**
101
     * Get the user's (system) edit count.
102
     * @param string $databaseName The database to query.
103
     * @param string $username The username to find.
104
     * @return int As returned by the database.
105
     */
106
    public function getEditCount(string $databaseName, string $username): int
107
    {
108
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_edit_count');
109
        if ($this->cache->hasItem($cacheKey)) {
110
            return (int)$this->cache->getItem($cacheKey)->get();
111
        }
112
113
        $userTable = $this->getTableName($databaseName, 'user');
114
        $sql = "SELECT user_editcount FROM $userTable WHERE user_name = :username LIMIT 1";
115
        $resultQuery = $this->executeProjectsQuery($databaseName, $sql, ['username' => $username]);
116
117
        return (int)$this->setCache($cacheKey, $resultQuery->fetchColumn());
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\ForwardCom...y\Result::fetchColumn() has been deprecated: Use fetchOne() instead. ( Ignorable by Annotation )

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

117
        return (int)$this->setCache($cacheKey, /** @scrutinizer ignore-deprecated */ $resultQuery->fetchColumn());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
118
    }
119
120
    /**
121
     * Search the ipblocks table to see if the user is currently blocked and return the expiry if they are.
122
     * @param string $databaseName The database to query.
123
     * @param string $username The username of the user to search for.
124
     * @return bool|string Expiry of active block or false
125
     */
126
    public function getBlockExpiry(string $databaseName, string $username)
127
    {
128
        $ipblocksTable = $this->getTableName($databaseName, 'ipblocks', 'ipindex');
129
        $sql = "SELECT ipb_expiry
130
                FROM $ipblocksTable
131
                WHERE ipb_address = :username
132
                LIMIT 1";
133
        $resultQuery = $this->executeProjectsQuery($databaseName, $sql, ['username' => $username]);
134
        return $resultQuery->fetchOne();
135
    }
136
137
    /**
138
     * Get edit count within given timeframe and namespace.
139
     * @param Project $project
140
     * @param User $user
141
     * @param int|string $namespace Namespace ID or 'all' for all namespaces
142
     * @param int|false $start Start date as Unix timestamp.
143
     * @param int|false $end End date as Unix timestamp.
144
     * @return int
145
     */
146
    public function countEdits(Project $project, User $user, $namespace = 'all', $start = false, $end = false): int
147
    {
148
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_editcount');
149
        if ($this->cache->hasItem($cacheKey)) {
150
            return (int)$this->cache->getItem($cacheKey)->get();
151
        }
152
153
        $revDateConditions = $this->getDateConditions($start, $end);
154
        [$pageJoin, $condNamespace] = $this->getPageAndNamespaceSql($project, $namespace);
155
        $revisionTable = $project->getTableName('revision');
156
        $params = [];
157
158
        if ($user->isAnon()) {
159
            [$params['startIp'], $params['endIp']] = IPUtils::parseRange($user->getUsername());
160
            $ipcTable = $project->getTableName('ip_changes');
161
            $sql = "SELECT COUNT(ipc_rev_id)
162
                    FROM $ipcTable
163
                    JOIN $revisionTable ON ipc_rev_id = rev_id
164
                    $pageJoin
165
                    WHERE ipc_hex BETWEEN :startIp AND :endIp
166
                    $condNamespace
167
                    $revDateConditions";
168
        } else {
169
            $sql = "SELECT COUNT(rev_id)
170
                FROM $revisionTable
171
                $pageJoin
172
                WHERE rev_actor = :actorId
173
                $condNamespace
174
                $revDateConditions";
175
        }
176
177
        $resultQuery = $this->executeQuery($sql, $project, $user, $namespace, $params);
178
        $result = (int)$resultQuery->fetchOne();
179
180
        // Cache and return.
181
        return $this->setCache($cacheKey, $result);
182
    }
183
184
    /**
185
     * Get information about the currently-logged in user.
186
     * @return array|object|null null if not logged in.
187
     */
188
    public function getXtoolsUserInfo()
189
    {
190
        /** @var Session $session */
191
        $session = $this->container->get('session');
192
        return $session->get('logged_in_user');
193
    }
194
195
    /**
196
     * Maximum number of edits to process, based on configuration.
197
     * @return int
198
     */
199
    public function maxEdits(): int
200
    {
201
        return (int)$this->container->getParameter('app.max_user_edits');
0 ignored issues
show
Bug introduced by
The method getParameter() does not exist on Psr\Container\ContainerInterface. It seems like you code against a sub-type of Psr\Container\ContainerInterface such as Symfony\Component\Depend...tion\ContainerInterface. ( Ignorable by Annotation )

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

201
        return (int)$this->container->/** @scrutinizer ignore-call */ getParameter('app.max_user_edits');
Loading history...
202
    }
203
204
    /**
205
     * Get SQL clauses for joining on `page` and restricting to a namespace.
206
     * @param Project $project
207
     * @param int|string $namespace Namespace ID or 'all' for all namespaces.
208
     * @return array [page join clause, page namespace clause]
209
     */
210
    protected function getPageAndNamespaceSql(Project $project, $namespace): array
211
    {
212
        if ('all' === $namespace) {
213
            return [null, null];
214
        }
215
216
        $pageTable = $project->getTableName('page');
217
        $pageJoin = "LEFT JOIN $pageTable ON rev_page = page_id";
218
        $condNamespace = 'AND page_namespace = :namespace';
219
220
        return [$pageJoin, $condNamespace];
221
    }
222
223
    /**
224
     * Get SQL fragments for filtering by user.
225
     * Used in self::getPagesCreatedInnerSql().
226
     * @param bool $dateFiltering Whether the query you're working with has date filtering.
227
     *   If false, a clause to check timestamp > 1 is added to force use of the timestamp index.
228
     * @return string[] Keys 'whereRev' and 'whereArc'.
229
     */
230
    public function getUserConditions(bool $dateFiltering = false): array
231
    {
232
        return [
233
            'whereRev' => " rev_actor = :actorId ".($dateFiltering ? '' : "AND rev_timestamp > 1 "),
234
            'whereArc' => " ar_actor = :actorId ".($dateFiltering ? '' : "AND ar_timestamp > 1 "),
235
        ];
236
    }
237
238
    /**
239
     * Prepare the given SQL, bind the given parameters, and execute the Doctrine Statement.
240
     * @param string $sql
241
     * @param Project $project
242
     * @param User $user
243
     * @param int|string|null $namespace Namespace ID, or 'all'/null for all namespaces.
244
     * @param array $extraParams Will get merged in the params array used for binding values.
245
     * @return ResultStatement
246
     */
247
    protected function executeQuery(
248
        string $sql,
249
        Project $project,
250
        User $user,
251
        $namespace = 'all',
252
        array $extraParams = []
253
    ): ResultStatement {
254
        $params = ['actorId' => $user->getActorId($project)];
255
256
        if ('all' !== $namespace) {
257
            $params['namespace'] = $namespace;
258
        }
259
260
        return $this->executeProjectsQuery($project, $sql, array_merge($params, $extraParams));
261
    }
262
263
    /**
264
     * Get a user's local user rights on the given Project.
265
     * @param Project $project
266
     * @param User $user
267
     * @return string[]
268
     */
269
    public function getUserRights(Project $project, User $user): array
270
    {
271
        if ($user->isAnon()) {
272
            return [];
273
        }
274
275
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_rights');
276
        if ($this->cache->hasItem($cacheKey)) {
277
            return $this->cache->getItem($cacheKey)->get();
278
        }
279
280
        $userGroupsTable = $project->getTableName('user_groups');
281
        $userTable = $project->getTableName('user');
282
283
        $sql = "SELECT ug_group
284
                FROM $userGroupsTable
285
                JOIN $userTable ON user_id = ug_user
286
                WHERE user_name = :username
287
                AND (ug_expiry IS NULL OR ug_expiry > CURRENT_TIMESTAMP)";
288
289
        $ret = $this->executeProjectsQuery($project, $sql, [
290
            'username' => $user->getUsername(),
291
        ])->fetchFirstColumn();
292
293
        // Cache and return.
294
        return $this->setCache($cacheKey, $ret);
295
    }
296
297
    /**
298
     * Get a user's global group membership (starting at XTools' default project if none is
299
     * provided). This requires the CentralAuth extension to be installed.
300
     * @link https://www.mediawiki.org/wiki/Extension:CentralAuth
301
     * @param string $username The username.
302
     * @param Project|null $project The project to query.
303
     * @return string[]
304
     */
305
    public function getGlobalUserRights(string $username, ?Project $project = null): array
306
    {
307
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_global_groups');
308
        if ($this->cache->hasItem($cacheKey)) {
309
            return $this->cache->getItem($cacheKey)->get();
310
        }
311
312
        // Get the default project if not provided.
313
        if (!$project instanceof Project) {
314
            $project = $this->projectRepo->getDefaultProject();
315
        }
316
317
        $params = [
318
            'meta' => 'globaluserinfo',
319
            'guiuser' => $username,
320
            'guiprop' => 'groups',
321
        ];
322
323
        $res = $this->executeApiRequest($project, $params);
324
        $result = [];
325
        if (isset($res['batchcomplete']) && isset($res['query']['globaluserinfo']['groups'])) {
326
            $result = $res['query']['globaluserinfo']['groups'];
327
        }
328
329
        // Cache and return.
330
        return $this->setCache($cacheKey, $result);
331
    }
332
}
333