Passed
Push — master ( fb9a7c...4fa4cb )
by MusikAnimal
05:25
created

UserRepository::getUserRights()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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