Passed
Push — master ( 98dfb9...fc72f2 )
by MusikAnimal
06:46
created

UserRepository::getIdAndRegistration()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 2
dl 0
loc 16
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the UserRepository class.
4
 */
5
6
declare(strict_types = 1);
7
8
namespace AppBundle\Repository;
9
10
use AppBundle\Model\Project;
11
use AppBundle\Model\User;
12
use Doctrine\DBAL\Driver\ResultStatement;
13
use Mediawiki\Api\SimpleRequest;
14
use Symfony\Component\DependencyInjection\ContainerInterface;
15
use Symfony\Component\HttpFoundation\Session\Session;
16
17
/**
18
 * This class provides data for the User class.
19
 * @codeCoverageIgnore
20
 */
21
class UserRepository extends Repository
22
{
23
    /**
24
     * Convenience method to get a new User object.
25
     * @param string $username The username.
26
     * @param ContainerInterface $container The DI container.
27
     * @return User
28
     */
29
    public static function getUser(string $username, ContainerInterface $container): User
30
    {
31
        $user = new User($username);
32
        $userRepo = new UserRepository();
33
        $userRepo->setContainer($container);
34
        $user->setRepository($userRepo);
35
        return $user;
36
    }
37
38
    /**
39
     * Get the user's ID and registration date.
40
     * @param string $databaseName The database to query.
41
     * @param string $username The username to find.
42
     * @return array|false With keys 'userId' and 'regDate'. false if user not found.
43
     */
44
    public function getIdAndRegistration(string $databaseName, string $username)
45
    {
46
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_id_reg');
47
        if ($this->cache->hasItem($cacheKey)) {
48
            return $this->cache->getItem($cacheKey)->get();
49
        }
50
51
        $userTable = $this->getTableName($databaseName, 'user');
52
        $sql = "SELECT user_id AS userId, user_registration AS regDate
53
                FROM $userTable
54
                WHERE user_name = :username
55
                LIMIT 1";
56
        $resultQuery = $this->executeProjectsQuery($sql, ['username' => $username]);
57
58
        // Cache and return.
59
        return $this->setCache($cacheKey, $resultQuery->fetch());
60
    }
61
62
    /**
63
     * Get the user's registration date.
64
     * @param string $databaseName The database to query.
65
     * @param string $username The username to find.
66
     * @return string|null As returned by the database. Some very old accounts may return null.
67
     */
68
    public function getRegistrationDate(string $databaseName, string $username): ?string
69
    {
70
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_registration');
71
        if ($this->cache->hasItem($cacheKey)) {
72
            return $this->cache->getItem($cacheKey)->get();
73
        }
74
75
        $userTable = $this->getTableName($databaseName, 'user');
76
        $sql = "SELECT user_registration FROM $userTable WHERE user_name = :username LIMIT 1";
77
        $resultQuery = $this->executeProjectsQuery($sql, ['username' => $username]);
78
        $registrationDate = $resultQuery->fetchColumn();
79
80
        // Cache and return.
81
        return $this->setCache($cacheKey, $registrationDate);
82
    }
83
84
    /**
85
     * Get the user's (system) edit count.
86
     * @param string $databaseName The database to query.
87
     * @param string $username The username to find.
88
     * @return string|null As returned by the database.
89
     */
90
    public function getEditCount(string $databaseName, string $username): ?string
91
    {
92
        // Quick cache of edit count, valid on for the same request.
93
        static $editCount = null;
94
        if (null !== $editCount) {
95
            return $editCount;
96
        }
97
98
        $userTable = $this->getTableName($databaseName, 'user');
99
        $sql = "SELECT user_editcount FROM $userTable WHERE user_name = :username LIMIT 1";
100
        $resultQuery = $this->executeProjectsQuery($sql, ['username' => $username]);
101
102
        $editCount = $resultQuery->fetchColumn();
103
        return $editCount;
104
    }
105
106
    /**
107
     * Search the ipblocks table to see if the user is currently blocked
108
     * and return the expiry if they are.
109
     * @param string $databaseName The database to query.
110
     * @param string $username The username of the user to search for.
111
     * @return bool|string Expiry of active block or false
112
     */
113
    public function getBlockExpiry(string $databaseName, string $username)
114
    {
115
        $ipblocksTable = $this->getTableName($databaseName, 'ipblocks', 'ipindex');
116
        $sql = "SELECT ipb_expiry
117
                FROM $ipblocksTable
118
                WHERE ipb_address = :username
119
                LIMIT 1";
120
        $resultQuery = $this->executeProjectsQuery($sql, ['username' => $username]);
121
        return $resultQuery->fetchColumn();
122
    }
123
124
    /**
125
     * Get edit count within given timeframe and namespace.
126
     * @param Project $project
127
     * @param User $user
128
     * @param int|string $namespace Namespace ID or 'all' for all namespaces
129
     * @param string $start Start date in a format accepted by strtotime()
130
     * @param string $end End date in a format accepted by strtotime()
131
     * @return int
132
     */
133
    public function countEdits(Project $project, User $user, $namespace = 'all', $start = '', $end = ''): int
134
    {
135
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_editcount');
136
        if ($this->cache->hasItem($cacheKey)) {
137
            return (int)$this->cache->getItem($cacheKey)->get();
138
        }
139
140
        [$condBegin, $condEnd] = $this->getRevTimestampConditions($start, $end);
141
        [$pageJoin, $condNamespace] = $this->getPageAndNamespaceSql($project, $namespace);
142
        $revisionTable = $project->getTableName('revision');
143
        $userClause = $user->isAnon() ? 'rev_user_text = :username' : 'rev_user = :userId';
144
145
        $sql = "SELECT COUNT(rev_id)
146
                FROM $revisionTable
147
                $pageJoin
148
                WHERE $userClause
149
                $condNamespace
150
                $condBegin
151
                $condEnd";
152
153
        $resultQuery = $this->executeQuery($sql, $project, $user, $namespace, $start, $end);
154
        $result = (int)$resultQuery->fetchColumn();
155
156
        // Cache and return.
157
        return $this->setCache($cacheKey, $result);
158
    }
159
160
    /**
161
     * Get information about the currently-logged in user.
162
     * @return array|object|null null if not logged in.
163
     */
164
    public function getXtoolsUserInfo()
165
    {
166
        /** @var Session $session */
167
        $session = $this->container->get('session');
168
        return $session->get('logged_in_user');
169
    }
170
171
    /**
172
     * Maximum number of edits to process, based on configuration.
173
     * @return int
174
     */
175
    public function maxEdits(): int
176
    {
177
        return (int)$this->container->getParameter('app.max_user_edits');
178
    }
179
180
    /**
181
     * Get SQL clauses for joining on `page` and restricting to a namespace.
182
     * @param Project $project
183
     * @param int|string $namespace Namespace ID or 'all' for all namespaces.
184
     * @return array [page join clause, page namespace clause]
185
     */
186
    protected function getPageAndNamespaceSql(Project $project, $namespace): array
187
    {
188
        if ('all' === $namespace) {
189
            return [null, null];
190
        }
191
192
        $pageTable = $project->getTableName('page');
193
        $pageJoin = 'all' !== $namespace ? "LEFT JOIN $pageTable ON rev_page = page_id" : null;
194
        $condNamespace = 'AND page_namespace = :namespace';
195
196
        return [$pageJoin, $condNamespace];
197
    }
198
199
    /**
200
     * Get SQL clauses for rev_timestamp, based on whether values for the given start and end parameters exist.
201
     * @param string $start
202
     * @param string $end
203
     * @param string $tableAlias Alias of table FOLLOWED BY DOT.
204
     * @param bool $archive Whether to use the archive table instead of revision.
205
     * @return string[] Clauses for start and end timestamps.
206
     * @todo FIXME: merge with Repository::getDateConditions
207
     */
208
    protected function getRevTimestampConditions(
209
        string $start,
210
        string $end,
211
        string $tableAlias = '',
212
        bool $archive = false
213
    ): array {
214
        $condBegin = '';
215
        $condEnd = '';
216
        $prefix = $archive ? 'ar' : 'rev';
217
218
        if (!empty($start)) {
219
            $condBegin = "AND {$tableAlias}{$prefix}_timestamp >= :start ";
220
        }
221
        if (!empty($end)) {
222
            $condEnd = "AND {$tableAlias}{$prefix}_timestamp <= :end ";
223
        }
224
225
        return [$condBegin, $condEnd];
226
    }
227
228
    /**
229
     * Get SQL fragments for rev_user or rev_user_text, depending on if the user is logged out.
230
     * Used in self::getPagesCreatedInnerSql().
231
     * @param Project $project
232
     * @param User $user
233
     * @param bool $dateFiltering Whether the query you're working with has date filtering.
234
     *   If false, a clause to check timestamp > 1 is added to force use of the timestamp index.
235
     * @return string[] Keys 'whereRev' and 'whereArc'.
236
     */
237
    public function getUserConditions(Project $project, User $user, bool $dateFiltering = false): array
238
    {
239
        $userId = $user->getId($project);
240
241
        if (0 == $userId) { // IP Editor or undefined username.
242
            return [
243
                'whereRev' => " rev_user_text = :username AND rev_user = '0' ",
244
                'whereArc' => " ar_user_text = :username AND ar_user = '0' ",
245
            ];
246
        } else {
247
            return [
248
                'whereRev' => " rev_user = :userId ".($dateFiltering ? '' : "AND rev_timestamp > 1 "),
249
                'whereArc' => " ar_user = :userId ".($dateFiltering ? '' : "AND ar_timestamp > 1 "),
250
            ];
251
        }
252
    }
253
254
    /**
255
     * Prepare the given SQL, bind the given parameters, and execute the Doctrine Statement.
256
     * @param string $sql
257
     * @param Project $project
258
     * @param User $user
259
     * @param int|string $namespace Namespace ID or 'all' for all namespaces.
260
     * @param string $start
261
     * @param string $end
262
     * @param array $extraParams Will get merged in the params array used for binding values.
263
     * @return ResultStatement
264
     * @throws \Doctrine\DBAL\Exception\DriverException
265
     */
266
    protected function executeQuery(
267
        string $sql,
268
        Project $project,
269
        User $user,
270
        $namespace = 'all',
271
        string $start = '',
272
        string $end = '',
273
        array $extraParams = []
274
    ): ResultStatement {
275
        if ($user->isAnon()) {
276
            $params = ['username' => $user->getUsername()];
277
        } else {
278
            $params = ['userId' => $user->getId($project)];
279
        }
280
281
        if (!empty($start)) {
282
            $params['start'] = date('Ymd000000', strtotime($start));
283
        }
284
        if (!empty($end)) {
285
            $params['end'] = date('Ymd235959', strtotime($end));
286
        }
287
        if ('all' !== $namespace) {
288
            $params['namespace'] = $namespace;
289
        }
290
291
        return $this->executeProjectsQuery($sql, array_merge($params, $extraParams));
292
    }
293
294
    /**
295
     * Get a user's local user rights on the given Project.
296
     * @param Project $project
297
     * @param User $user
298
     * @return string[]
299
     */
300
    public function getUserRights(Project $project, User $user): array
301
    {
302
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_rights');
303
        if ($this->cache->hasItem($cacheKey)) {
304
            return $this->cache->getItem($cacheKey)->get();
305
        }
306
307
        $userGroupsTable = $project->getTableName('user_groups');
308
        $userTable = $project->getTableName('user');
309
310
        $sql = "SELECT ug_group
311
                FROM $userGroupsTable
312
                JOIN $userTable ON user_id = ug_user
313
                WHERE user_name = :username";
314
315
        $ret = $this->executeProjectsQuery($sql, [
316
            'username' => $user->getUsername(),
317
        ])->fetchAll(\PDO::FETCH_COLUMN);
318
319
        // Cache and return.
320
        return $this->setCache($cacheKey, $ret);
321
    }
322
323
    /**
324
     * Get a user's global group membership (starting at XTools' default project if none is
325
     * provided). This requires the CentralAuth extension to be installed.
326
     * @link https://www.mediawiki.org/wiki/Extension:CentralAuth
327
     * @param string $username The username.
328
     * @param Project $project The project to query.
329
     * @return string[]
330
     */
331
    public function getGlobalUserRights(string $username, ?Project $project = null): array
332
    {
333
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_global_groups');
334
        if ($this->cache->hasItem($cacheKey)) {
335
            return $this->cache->getItem($cacheKey)->get();
336
        }
337
338
        // Get the default project if not provided.
339
        if (!$project instanceof Project) {
340
            $project = ProjectRepository::getDefaultProject($this->container);
341
        }
342
343
        // Create the API query.
344
        $api = $this->getMediawikiApi($project);
345
        $params = [
346
            'meta' => 'globaluserinfo',
347
            'guiuser' => $username,
348
            'guiprop' => 'groups',
349
        ];
350
        $query = new SimpleRequest('query', $params);
351
352
        // Get the result.
353
        $res = $api->getRequest($query);
354
        $result = [];
355
        if (isset($res['batchcomplete']) && isset($res['query']['globaluserinfo']['groups'])) {
356
            $result = $res['query']['globaluserinfo']['groups'];
357
        }
358
359
        // Cache and return.
360
        return $this->setCache($cacheKey, $result);
361
    }
362
}
363