Passed
Push — master ( 0c65d8...e43fc5 )
by MusikAnimal
18:43
created

Project::getCanonicalNamespaces()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the Project class.
4
 */
5
6
declare(strict_types = 1);
7
8
namespace AppBundle\Model;
9
10
/**
11
 * A Project is a single wiki that XTools is querying.
12
 */
13
class Project extends Model
14
{
15
    /** @var string The project name as supplied by the user. */
16
    protected $nameUnnormalized;
17
18
    /** @var string[] Basic metadata about the project */
19
    protected $metadata;
20
21
    /** @var string[] Project's 'dbName', 'url' and 'lang'. */
22
    protected $basicInfo;
23
24
    /** @var PageAssessments Contains methods around the page assessments config for the Project. */
25
    protected $pageAssessments;
26
27
    /**
28
     * Whether the user being queried for in this session has opted in to restricted statistics.
29
     * @var bool
30
     */
31
    protected $userOptedIn;
32
33
    /**
34
     * Create a new Project.
35
     * @param string $nameOrUrl The project's database name or URL.
36
     */
37 114
    public function __construct(string $nameOrUrl)
38
    {
39 114
        $this->nameUnnormalized = $nameOrUrl;
40 114
        $this->pageAssessments = new PageAssessments($this);
41 114
    }
42
43
    /**
44
     * Get the associated PageAssessments model.
45
     * @return PageAssessments
46
     */
47 14
    public function getPageAssessments(): PageAssessments
48
    {
49 14
        return $this->pageAssessments;
50
    }
51
52
    /**
53
     * Whether or not this project supports page assessments, or if they exist for the given namespace.
54
     * @param int|string|null $nsId Namespace ID, null if checking if project has page assessments for any namespace.
55
     * @return bool
56
     */
57
    public function hasPageAssessments($nsId = null): bool
58
    {
59
        if (null !== $nsId && (int)$nsId > 0) {
60
            return $this->pageAssessments->isSupportedNamespace((int)$nsId);
61
        } else {
62
            return $this->pageAssessments->isEnabled();
63
        }
64
    }
65
66
    /**
67
     * Unique identifier this Project, to be used in cache keys.
68
     * @see Repository::getCacheKey()
69
     * @return string
70
     */
71
    public function getCacheKey(): string
72
    {
73
        return $this->getDatabaseName();
74
    }
75
76
    /**
77
     * Get 'dbName', 'url' and 'lang' of the project, the relevant basic info we can get from the meta database.
78
     * This is all you need to make database queries. More comprehensive metadata can be fetched with getMetadata()
79
     * at the expense of an API call, which may be cached.
80
     * @return string[]|bool false if not found.
81
     */
82 52
    protected function getBasicInfo()
83
    {
84 52
        if (empty($this->basicInfo)) {
85 52
            $this->basicInfo = $this->getRepository()->getOne($this->nameUnnormalized);
0 ignored issues
show
Bug introduced by
The method getOne() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\ProjectRepository. ( Ignorable by Annotation )

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

85
            $this->basicInfo = $this->getRepository()->/** @scrutinizer ignore-call */ getOne($this->nameUnnormalized);
Loading history...
86
        }
87 52
        return $this->basicInfo;
88
    }
89
90
    /**
91
     * Get full metadata about the project. See ProjectRepository::getMetadata() for more information.
92
     * @return array|null null if project not found.
93
     */
94 19
    protected function getMetadata(): ?array
95
    {
96 19
        if (empty($this->metadata)) {
97 19
            $url = $this->getBasicInfo()['url'];
98 19
            $this->metadata = $this->getRepository()->getMetadata($url);
0 ignored issues
show
Bug introduced by
The method getMetadata() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\ProjectRepository. ( Ignorable by Annotation )

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

98
            $this->metadata = $this->getRepository()->/** @scrutinizer ignore-call */ getMetadata($url);
Loading history...
99
        }
100 19
        return $this->metadata;
101
    }
102
103
    /**
104
     * Does this project exist?
105
     * @return bool
106
     */
107 14
    public function exists(): bool
108
    {
109 14
        return !empty($this->getDomain());
110
    }
111
112
    /**
113
     * The unique domain name of this project, without protocol or path components.
114
     * This should be used as the canonical project identifier.
115
     * @return string|null null if nonexistent.
116
     */
117 30
    public function getDomain(): ?string
118
    {
119 30
        $url = $this->getBasicInfo()['url'] ?? '';
120 30
        return parse_url($url, PHP_URL_HOST);
121
    }
122
123
    /**
124
     * The name of the database for this project.
125
     * @return string
126
     */
127 13
    public function getDatabaseName(): string
128
    {
129 13
        return $this->getBasicInfo()['dbName'] ?? '';
130
    }
131
132
    /**
133
     * The language for this project.
134
     * @return string
135
     */
136 16
    public function getLang(): string
137
    {
138 16
        return $this->getBasicInfo()['lang'] ?? '';
139
    }
140
141
    /**
142
     * The project URL is the fully-qualified domain name, with protocol and trailing slash.
143
     * @param bool $withTrailingSlash Whether to append a slash.
144
     * @return string
145
     */
146 17
    public function getUrl(bool $withTrailingSlash = true): string
147
    {
148 17
        return rtrim($this->getBasicInfo()['url'], '/') . ($withTrailingSlash ? '/' : '');
149
    }
150
151
    /**
152
     * The base URL path of this project (that page titles are appended to).
153
     * For some wikis the title (apparently) may not be at the end.
154
     * Replace $1 with the article name.
155
     * @link https://www.mediawiki.org/wiki/Manual:$wgArticlePath
156
     * @return string
157
     */
158 5
    public function getArticlePath(): string
159
    {
160 5
        $metadata = $this->getMetadata();
161 5
        return $metadata['general']['articlePath'] ?? '/wiki/$1';
162
    }
163
164
    /**
165
     * The URL path of the directory that contains index.php, with no trailing slash.
166
     * Defaults to '/w' which is the same as the normal WMF set-up.
167
     * @link https://www.mediawiki.org/wiki/Manual:$wgScriptPath
168
     * @return string
169
     */
170 2
    public function getScriptPath(): string
171
    {
172 2
        $metadata = $this->getMetadata();
173 2
        return $metadata['general']['scriptPath'] ?? '/w';
174
    }
175
176
    /**
177
     * The URL path to index.php
178
     * Defaults to '/w/index.php' which is the same as the normal WMF set-up.
179
     * @return string
180
     */
181 1
    public function getScript(): string
182
    {
183 1
        $metadata = $this->getMetadata();
184 1
        return $metadata['general']['script'] ?? $this->getScriptPath() . '/index.php';
185
    }
186
187
    /**
188
     * The full URL to api.php.
189
     * @return string
190
     */
191 11
    public function getApiUrl(): string
192
    {
193 11
        return rtrim($this->getUrl(), '/') . $this->getRepository()->getApiPath();
0 ignored issues
show
Bug introduced by
The method getApiPath() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\ProjectRepository. ( Ignorable by Annotation )

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

193
        return rtrim($this->getUrl(), '/') . $this->getRepository()->/** @scrutinizer ignore-call */ getApiPath();
Loading history...
194
    }
195
196
    /**
197
     * Get the project's title, the human-language full title of the wiki (e.g. "English Wikipedia (en.wikipedia.org)").
198
     */
199
    public function getTitle(): string
200
    {
201
        $metadata = $this->getMetadata();
202
        return $metadata['general']['wikiName'].' ('.$this->getDomain().')';
203
    }
204
205
    /**
206
     * Get an array of this project's namespaces and their IDs.
207
     * @return string[] Keys are IDs, values are names.
208
     */
209 12
    public function getNamespaces(): array
210
    {
211 12
        $metadata = $this->getMetadata();
212 12
        return $metadata['namespaces'];
213
    }
214
215
    /**
216
     * Get canonical namespace names. Any namespaces that do not have a canonical form are returned with the normal
217
     * namespace name, as produced by self::getNamespaces().
218
     * @return array
219
     */
220 1
    public function getCanonicalNamespaces(): array
221
    {
222 1
        $metadata = $this->getMetadata();
223 1
        return array_replace($metadata['namespaces'], $metadata['canonicalNamespaces']);
224
    }
225
226
    /**
227
     * Get the title of the Main Page.
228
     * @return string
229
     */
230 1
    public function getMainPage(): string
231
    {
232 1
        $metadata = $this->getMetadata();
233 1
        return $metadata['general']['mainpage'] ?? '';
234
    }
235
236
    /**
237
     * List of extensions that are installed on the wiki.
238
     * @return string[]
239
     */
240
    public function getInstalledExtensions(): array
241
    {
242
        // Quick cache, valid only for the same request.
243
        static $installedExtensions = null;
244
        if (is_array($installedExtensions)) {
245
            return $installedExtensions;
246
        }
247
248
        return $installedExtensions = $this->getRepository()->getInstalledExtensions($this);
0 ignored issues
show
Bug introduced by
The method getInstalledExtensions() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\ProjectRepository. ( Ignorable by Annotation )

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

248
        return $installedExtensions = $this->getRepository()->/** @scrutinizer ignore-call */ getInstalledExtensions($this);
Loading history...
249
    }
250
251
    /**
252
     * Get a list of users who are in one of the given user groups.
253
     * @param string[] $groups User groups to search for.
254
     * @param string[] $globalGroups Global groups to search for.
255
     * @return string[] User groups keyed by user name.
256
     */
257 1
    public function getUsersInGroups(array $groups, array $globalGroups): array
258
    {
259 1
        $users = [];
260 1
        $usersAndGroups = $this->getRepository()->getUsersInGroups($this, $groups, $globalGroups);
0 ignored issues
show
Bug introduced by
The method getUsersInGroups() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\ProjectRepository. ( Ignorable by Annotation )

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

260
        $usersAndGroups = $this->getRepository()->/** @scrutinizer ignore-call */ getUsersInGroups($this, $groups, $globalGroups);
Loading history...
261 1
        foreach ($usersAndGroups as $userAndGroup) {
262 1
            $username = $userAndGroup['user_name'];
263 1
            if (isset($users[$username])) {
264 1
                $users[$username][] = $userAndGroup['user_group'];
265
            } else {
266 1
                $users[$username] = [$userAndGroup['user_group']];
267
            }
268
        }
269 1
        return $users;
270
    }
271
272
    /**
273
     * Get the name of the page on this project that the user must create in order to opt in for restricted statistics.
274
     * @param User $user
275
     * @return string
276
     */
277 2
    public function userOptInPage(User $user): string
278
    {
279 2
        $localPageName = 'User:' . $user->getUsername() . '/EditCounterOptIn.js';
280 2
        return $localPageName;
281
    }
282
283
    /**
284
     * Has a user opted in to having their restricted statistics displayed to anyone?
285
     * @param User $user
286
     * @return bool
287
     */
288 3
    public function userHasOptedIn(User $user): bool
289
    {
290
        // 1. First check to see if the whole project has opted in.
291 3
        if (!$this->userOptedIn) {
292 3
            $optedInProjects = $this->getRepository()->optedIn();
0 ignored issues
show
Bug introduced by
The method optedIn() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\ProjectRepository. ( Ignorable by Annotation )

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

292
            $optedInProjects = $this->getRepository()->/** @scrutinizer ignore-call */ optedIn();
Loading history...
293 3
            $this->userOptedIn = in_array($this->getDatabaseName(), $optedInProjects);
294
        }
295 3
        if ($this->userOptedIn) {
296 1
            return true;
297
        }
298
299
        // 2. Then see if the currently-logged-in user is requesting their own statistics.
300 2
        if ($user->isCurrentlyLoggedIn()) {
301
            return true;
302
        }
303
304
        // 3. Then see if the user has opted in on this project.
305 2
        $userNsId = 2;
306
        // Remove namespace since we're querying the database and supplying a namespace ID.
307 2
        $optInPage = preg_replace('/^User:/', '', $this->userOptInPage($user));
308 2
        $localExists = $this->getRepository()->pageHasContent($this, $userNsId, $optInPage);
0 ignored issues
show
Bug introduced by
The method pageHasContent() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\ProjectRepository. ( Ignorable by Annotation )

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

308
        $localExists = $this->getRepository()->/** @scrutinizer ignore-call */ pageHasContent($this, $userNsId, $optInPage);
Loading history...
309 2
        if ($localExists) {
310
            return true;
311
        }
312
313
        // 4. Lastly, see if they've opted in globally on the default project or Meta.
314 2
        $globalPageName = $user->getUsername() . '/EditCounterGlobalOptIn.js';
315 2
        $globalProject = $this->getRepository()->getGlobalProject();
0 ignored issues
show
Bug introduced by
The method getGlobalProject() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\ProjectRepository. ( Ignorable by Annotation )

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

315
        $globalProject = $this->getRepository()->/** @scrutinizer ignore-call */ getGlobalProject();
Loading history...
316 2
        if ($globalProject instanceof Project) {
317 2
            $globalExists = $globalProject->getRepository()
318 2
                ->pageHasContent($globalProject, $userNsId, $globalPageName);
319 2
            if ($globalExists) {
320
                return true;
321
            }
322
        }
323
324 2
        return false;
325
    }
326
327
    /**
328
     * Normalize and quote a table name for use in SQL.
329
     * @param string $tableName
330
     * @param string|null $tableExtension Optional table extension, which will only get used if we're on Labs.
331
     * @return string Fully-qualified and quoted table name.
332
     */
333 1
    public function getTableName(string $tableName, ?string $tableExtension = null): string
334
    {
335 1
        return $this->getRepository()->getTableName($this->getDatabaseName(), $tableName, $tableExtension);
336
    }
337
}
338