Passed
Push — master ( 2f87b4...a0f968 )
by MusikAnimal
05:26
created

ProjectRepository::getAssessmentsConfig()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 1
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the ProjectRepository class.
4
 */
5
6
namespace Xtools;
7
8
use Mediawiki\Api\MediawikiApi;
9
use Mediawiki\Api\SimpleRequest;
10
use Psr\Container\ContainerInterface;
11
12
/**
13
 * This class provides data to the Project class.
14
 * @codeCoverageIgnore
15
 */
16
class ProjectRepository extends Repository
17
{
18
    /** @var array Project's 'dbName', 'url' and 'lang'. */
19
    protected $basicInfo;
20
21
    /** @var string[] Basic metadata if XTools is in single-wiki mode. */
22
    protected $singleBasicInfo;
23
24
    /** @var array Full Project metadata, including $basicInfo. */
25
    protected $metadata;
26
27
    /** @var string The cache key for the 'all project' metadata. */
28
    protected $cacheKeyAllProjects = 'allprojects';
29
30
    /**
31
     * Convenience method to get a new Project object based on a given identification string.
32
     * @param string $projectIdent The domain name, database name, or URL of a project.
33
     * @param ContainerInterface $container Symfony's container.
34
     * @return Project
35
     */
36
    public static function getProject($projectIdent, ContainerInterface $container)
37
    {
38
        $project = new Project($projectIdent);
39
        $projectRepo = new ProjectRepository();
40
        $projectRepo->setContainer($container);
41
42
        // The associated PageAssessmentsRepository also needs the container.
43
        $paRepo = new PageAssessmentsRepository();
44
        $paRepo->setContainer($container);
45
        $project->getPageAssessments()->setRepository($paRepo);
46
47
        if ($container->getParameter('app.single_wiki')) {
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

47
        if ($container->/** @scrutinizer ignore-call */ getParameter('app.single_wiki')) {
Loading history...
48
            $projectRepo->setSingleBasicInfo([
49
                'url' => $container->getParameter('wiki_url'),
50
                'dbName' => $container->getParameter('database_replica_name'),
51
            ]);
52
        }
53
        $project->setRepository($projectRepo);
54
55
        return $project;
56
    }
57
58
    /**
59
     * Get the XTools default project.
60
     * @param ContainerInterface $container
61
     * @return Project
62
     */
63
    public static function getDefaultProject(ContainerInterface $container)
64
    {
65
        $defaultProjectName = $container->getParameter('default_project');
66
        return self::getProject($defaultProjectName, $container);
67
    }
68
69
    /**
70
     * Get the global 'meta' project, which is either Meta (if this is Labs) or the default project.
71
     * @return Project
72
     */
73
    public function getGlobalProject()
74
    {
75
        if ($this->isLabs()) {
76
            return self::getProject('metawiki', $this->container);
77
        } else {
78
            return self::getDefaultProject($this->container);
79
        }
80
    }
81
82
    /**
83
     * For single-wiki installations, you must manually set the wiki URL and database name
84
     * (because there's no meta.wiki database to query).
85
     * @param $metadata
86
     * @throws \Exception
87
     */
88
    public function setSingleBasicInfo($metadata)
89
    {
90
        if (!array_key_exists('url', $metadata) || !array_key_exists('dbName', $metadata)) {
91
            $error = "Single-wiki metadata should contain 'url', 'dbName' and 'lang' keys.";
92
            throw new \Exception($error);
93
        }
94
        $this->singleBasicInfo = array_intersect_key($metadata, [
95
            'url' => '',
96
            'dbName' => '',
97
            'lang' => '',
98
        ]);
99
    }
100
101
    /**
102
     * Get the 'dbName', 'url' and 'lang' of all projects.
103
     * @return string[][] Each item has 'dbName', 'url' and 'lang' keys.
104
     */
105
    public function getAll()
106
    {
107
        $this->log->debug(__METHOD__." Getting all projects' metadata");
108
        // Single wiki mode?
109
        if (!empty($this->singleBasicInfo)) {
110
            return [$this->getOne('')];
111
        }
112
113
        // Maybe we've already fetched it.
114
        if ($this->cache->hasItem($this->cacheKeyAllProjects)) {
115
            return $this->cache->getItem($this->cacheKeyAllProjects)->get();
116
        }
117
118
        // Otherwise, fetch all from the database.
119
        $wikiQuery = $this->getMetaConnection()->createQueryBuilder();
120
        $wikiQuery->select(['dbname AS dbName', 'url', 'lang'])->from('wiki');
121
        $projects = $this->executeQueryBuilder($wikiQuery)->fetchAll();
122
        $projectsMetadata = [];
123
        foreach ($projects as $project) {
124
            $projectsMetadata[$project['dbName']] = $project;
125
        }
126
127
        // Cache for one day and return.
128
        return $this->setCache(
129
            $this->cacheKeyAllProjects,
130
            $projectsMetadata,
131
            'P1D'
132
        );
133
    }
134
135
    /**
136
     * Get the 'dbName', 'url' and 'lang' of a project. This is all you need
137
     *   to make database queries. More comprehensive metadata can be fetched
138
     *   with getMetadata() at the expense of an API call.
139
     * @param string $project A project URL, domain name, or database name.
140
     * @return string[]|bool With 'dbName', 'url' and 'lang' keys; or false if not found.
141
     */
142
    public function getOne($project)
143
    {
144
        $this->log->debug(__METHOD__." Getting metadata about $project");
145
        // For single-wiki setups, every project is the same.
146
        if (!empty($this->singleBasicInfo)) {
147
            return $this->singleBasicInfo;
148
        }
149
150
        // For muli-wiki setups, first check the cache.
151
        // First the all-projects cache, then the individual one.
152
        if ($this->cache->hasItem($this->cacheKeyAllProjects)) {
153
            foreach ($this->cache->getItem($this->cacheKeyAllProjects)->get() as $projMetadata) {
154
                if ($projMetadata['dbName'] == "$project"
155
                    || $projMetadata['url'] == "$project"
156
                    || $projMetadata['url'] == "https://$project"
157
                    || $projMetadata['url'] == "https://$project.org"
158
                    || $projMetadata['url'] == "https://www.$project") {
159
                    $this->log->debug(__METHOD__ . " Using cached data for $project");
160
                    return $projMetadata;
161
                }
162
            }
163
        }
164
        $cacheKey = "project.$project";
165
        if ($this->cache->hasItem($cacheKey)) {
166
            return $this->cache->getItem($cacheKey)->get();
167
        }
168
169
        // Otherwise, fetch the project's metadata from the meta.wiki table.
170
        $wikiQuery = $this->getMetaConnection()->createQueryBuilder();
171
        $wikiQuery->select(['dbname AS dbName', 'url', 'lang'])
172
            ->from('wiki')
173
            ->where($wikiQuery->expr()->eq('dbname', ':project'))
174
            // The meta database will have the project's URL stored as https://en.wikipedia.org
175
            // so we need to query for it accordingly, trying different variations the user
176
            // might have inputted.
177
            ->orwhere($wikiQuery->expr()->like('url', ':projectUrl'))
178
            ->orwhere($wikiQuery->expr()->like('url', ':projectUrl2'))
179
            ->orwhere($wikiQuery->expr()->like('url', ':projectUrl3'))
180
            ->orwhere($wikiQuery->expr()->like('url', ':projectUrl4'))
181
            ->setParameter('project', $project)
182
            ->setParameter('projectUrl', "https://$project")
183
            ->setParameter('projectUrl2', "https://$project.org")
184
            ->setParameter('projectUrl3', "https://www.$project")
185
            ->setParameter('projectUrl4', "https://www.$project.org");
186
        $wikiStatement = $this->executeQueryBuilder($wikiQuery);
187
188
        // Fetch and cache the wiki data.
189
        $basicInfo = $wikiStatement->fetch();
190
191
        // Cache for one hour and return.
192
        return $this->setCache($cacheKey, $basicInfo, 'PT1H');
193
    }
194
195
    /**
196
     * Get metadata about a project, including the 'dbName', 'url' and 'lang'
197
     *
198
     * @param string $projectUrl The project's URL.
199
     * @return array|null With 'dbName', 'url', 'lang', 'general' and 'namespaces' keys.
200
     *   'general' contains: 'wikiName', 'articlePath', 'scriptPath', 'script',
201
     *   'timezone', and 'timezoneOffset'; 'namespaces' contains all namespace
202
     *   names, keyed by their IDs.  If this function returns null, the API call
203
     *   failed.
204
     */
205
    public function getMetadata($projectUrl)
206
    {
207
        // First try variable cache
208
        if (!empty($this->metadata)) {
209
            return $this->metadata;
210
        }
211
212
        // Redis cache
213
        $cacheKey = $this->getCacheKey(
214
            // Removed non-alphanumeric characters
215
            preg_replace("/[^A-Za-z0-9]/", '', $projectUrl),
216
            'project_metadata'
217
        );
218
219
        if ($this->cache->hasItem($cacheKey)) {
220
            $this->metadata = $this->cache->getItem($cacheKey)->get();
221
            return $this->metadata;
222
        }
223
224
        try {
225
            $api = MediawikiApi::newFromPage($projectUrl);
226
        } catch (\Exception $e) {
227
            return null;
228
        }
229
230
        $params = ['meta' => 'siteinfo', 'siprop' => 'general|namespaces'];
231
        $query = new SimpleRequest('query', $params);
232
233
        $this->metadata = [
234
            'general' => [],
235
            'namespaces' => [],
236
        ];
237
238
        // Even if general info could not be fetched,
239
        //   return dbName, url and lang if already known
240
        if (!empty($this->basicInfo)) {
241
            $this->metadata['dbName'] = $this->basicInfo['dbName'];
242
            $this->metadata['url'] = $this->basicInfo['url'];
243
            $this->metadata['lang'] = $this->basicInfo['lang'];
244
        }
245
246
        $res = $api->getRequest($query);
247
248
        if (isset($res['query']['general'])) {
249
            $info = $res['query']['general'];
250
251
            $this->metadata['dbName'] = $info['wikiid'];
252
            $this->metadata['url'] = $info['server'];
253
            $this->metadata['lang'] = $info['lang'];
254
255
            $this->metadata['general'] = [
256
                'wikiName' => $info['sitename'],
257
                'articlePath' => $info['articlepath'],
258
                'scriptPath' => $info['scriptpath'],
259
                'script' => $info['script'],
260
                'timezone' => $info['timezone'],
261
                'timeOffset' => $info['timeoffset'],
262
                'mainpage' => $info['mainpage'],
263
            ];
264
        }
265
266
        if (isset($res['query']['namespaces'])) {
267
            foreach ($res['query']['namespaces'] as $namespace) {
268
                if ($namespace['id'] < 0) {
269
                    continue;
270
                }
271
272
                if (isset($namespace['name'])) {
273
                    $name = $namespace['name'];
274
                } elseif (isset($namespace['*'])) {
275
                    $name = $namespace['*'];
276
                } else {
277
                    continue;
278
                }
279
280
                $this->metadata['namespaces'][$namespace['id']] = $name;
281
            }
282
        }
283
284
        // Cache for one hour and return.
285
        return $this->setCache($cacheKey, $this->metadata, 'PT1H');
286
    }
287
288
    /**
289
     * Get a list of projects that have opted in to having all their users' restricted statistics
290
     * available to anyone.
291
     *
292
     * @return string[]
293
     */
294
    public function optedIn()
295
    {
296
        $optedIn = $this->container->getParameter('opted_in');
297
        // In case there's just one given.
298
        if (!is_array($optedIn)) {
299
            $optedIn = [ $optedIn ];
300
        }
301
        return $optedIn;
302
    }
303
304
    /**
305
     * The path to api.php
306
     *
307
     * @return string
308
     */
309
    public function getApiPath()
310
    {
311
        return $this->container->getParameter('api_path');
312
    }
313
314
    /**
315
     * Get a page from the given Project.
316
     * @param Project $project The project.
317
     * @param string $pageName The name of the page.
318
     * @return Page
319
     */
320
    public function getPage(Project $project, $pageName)
321
    {
322
        $pageRepo = new PageRepository();
323
        $pageRepo->setContainer($this->container);
324
        $page = new Page($project, $pageName);
325
        $page->setRepository($pageRepo);
326
        return $page;
327
    }
328
329
    /**
330
     * Check to see if a page exists on this project and has some content.
331
     * @param Project $project The project.
332
     * @param int $namespaceId The page namespace ID.
333
     * @param string $pageTitle The page title, without namespace.
334
     * @return bool
335
     */
336
    public function pageHasContent(Project $project, $namespaceId, $pageTitle)
337
    {
338
        $pageTable = $this->getTableName($project->getDatabaseName(), 'page');
339
        $query = "SELECT page_id "
340
             . " FROM $pageTable "
341
             . " WHERE page_namespace = :ns AND page_title = :title AND page_len > 0 "
342
             . " LIMIT 1";
343
        $params = [
344
            'ns' => $namespaceId,
345
            'title' => str_replace(' ', '_', $pageTitle),
346
        ];
347
        $pages = $this->executeProjectsQuery($query, $params)->fetchAll();
348
        return count($pages) > 0;
349
    }
350
351
    /**
352
     * Get a list of users who are in one of the given user groups.
353
     * @param  Project $project
354
     * @param  string[] List of user groups to look for.
0 ignored issues
show
Bug introduced by
The type Xtools\List was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
355
     * @return string[] with keys 'user_name' and 'ug_group'
356
     */
357
    public function getUsersInGroups(Project $project, $groups)
358
    {
359
        $cacheKey = $this->getCacheKey(func_get_args(), 'project_useringroups');
360
        if ($this->cache->hasItem($cacheKey)) {
361
            return $this->cache->getItem($cacheKey)->get();
362
        }
363
364
        $userTable = $project->getTableName('user');
365
        $userGroupsTable = $project->getTableName('user_groups');
366
367
        $query = $this->getProjectsConnection()->createQueryBuilder();
368
        $query->select(['user_name', 'ug_group'])
369
            ->from($userTable)
370
            ->join($userTable, $userGroupsTable, null, 'ug_user = user_id')
371
            ->where($query->expr()->in('ug_group', ':groups'))
372
            ->groupBy('user_name, ug_group')
373
            ->setParameter(
374
                'groups',
375
                $groups,
376
                \Doctrine\DBAL\Connection::PARAM_STR_ARRAY
377
            );
378
        $admins = $this->executeQueryBuilder($query)->fetchAll();
379
380
        // Cache for one hour and return.
381
        return $this->setCache($cacheKey, $admins, 'PT1H');
382
    }
383
}
384