Project::getScript()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

90
            /** @scrutinizer ignore-call */ 
91
            $this->basicInfo = $this->repository->getOne($this->nameUnnormalized);
Loading history...
91
        }
92
        return $this->basicInfo;
93
    }
94
95
    /**
96
     * Get full metadata about the project. See ProjectRepository::getMetadata() for more information.
97
     * @return array|null null if project not found.
98
     */
99
    protected function getMetadata(): ?array
100
    {
101
        if (!isset($this->metadata)) {
102
            $info = $this->getBasicInfo();
103
            if (!isset($info['url'])) {
104
                // Project is probably not replicated.
105
                return null;
106
            }
107
            $url = $this->getBasicInfo()['url'];
108
            $this->metadata = $this->getRepository()->getMetadata($url);
0 ignored issues
show
Bug introduced by
The method getMetadata() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\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

108
            $this->metadata = $this->getRepository()->/** @scrutinizer ignore-call */ getMetadata($url);
Loading history...
109
        }
110
        return $this->metadata;
111
    }
112
113
    /**
114
     * Does this project exist?
115
     * @return bool
116
     */
117
    public function exists(): bool
118
    {
119
        return !empty($this->getDomain());
120
    }
121
122
    /**
123
     * The unique domain name of this project, without protocol or path components.
124
     * This should be used as the canonical project identifier.
125
     * @return string|null null if nonexistent.
126
     */
127
    public function getDomain(): ?string
128
    {
129
        $url = $this->getBasicInfo()['url'] ?? '';
130
        return parse_url($url, PHP_URL_HOST);
131
    }
132
133
    /**
134
     * The name of the database for this project.
135
     * @return string
136
     */
137
    public function getDatabaseName(): string
138
    {
139
        return $this->getBasicInfo()['dbName'] ?? '';
140
    }
141
142
    /**
143
     * The language for this project.
144
     * @return string
145
     */
146
    public function getLang(): string
147
    {
148
        return $this->getBasicInfo()['lang'] ?? '';
149
    }
150
151
    /**
152
     * The project URL is the fully-qualified domain name, with protocol and trailing slash.
153
     * @param bool $withTrailingSlash Whether to append a slash.
154
     * @return string
155
     */
156
    public function getUrl(bool $withTrailingSlash = true): string
157
    {
158
        return rtrim($this->getBasicInfo()['url'], '/') . ($withTrailingSlash ? '/' : '');
159
    }
160
161
    /**
162
     * @param Page|string $page Full page title including namespace, or a Page object.
163
     * @param bool $useUnnormalizedPageTitle Use the unnormalized page title to avoid
164
     *    an API call. This should be used only if you fetched the page title via other
165
     *    means (SQL query), and is not from user input alone. Only applicable if $page
166
     *    is a Page object.
167
     * @return string
168
     */
169
    public function getUrlForPage($page, bool $useUnnormalizedPageTitle = false): string
170
    {
171
        if ($page instanceof Page) {
172
            $page = $page->getTitle($useUnnormalizedPageTitle);
173
        }
174
        return str_replace('$1', $page, $this->getUrl(false) . $this->getArticlePath());
175
    }
176
177
    /**
178
     * The base URL path of this project (that page titles are appended to).
179
     * For some wikis the title (apparently) may not be at the end.
180
     * Replace $1 with the article name.
181
     * @link https://www.mediawiki.org/wiki/Manual:$wgArticlePath
182
     * @return string
183
     */
184
    public function getArticlePath(): string
185
    {
186
        $metadata = $this->getMetadata();
187
        return $metadata['general']['articlePath'] ?? '/wiki/$1';
188
    }
189
190
    /**
191
     * The URL path of the directory that contains index.php, with no trailing slash.
192
     * Defaults to '/w' which is the same as the normal WMF set-up.
193
     * @link https://www.mediawiki.org/wiki/Manual:$wgScriptPath
194
     * @return string
195
     */
196
    public function getScriptPath(): string
197
    {
198
        $metadata = $this->getMetadata();
199
        return $metadata['general']['scriptPath'] ?? '/w';
200
    }
201
202
    /**
203
     * The URL path to index.php
204
     * Defaults to '/w/index.php' which is the same as the normal WMF set-up.
205
     * @return string
206
     */
207
    public function getScript(): string
208
    {
209
        $metadata = $this->getMetadata();
210
        return $metadata['general']['script'] ?? $this->getScriptPath() . '/index.php';
211
    }
212
213
    /**
214
     * The full URL to api.php.
215
     * @return string
216
     */
217
    public function getApiUrl(): string
218
    {
219
        return rtrim($this->getUrl(), '/') . $this->getRepository()->getApiPath();
0 ignored issues
show
Bug introduced by
The method getApiPath() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\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

219
        return rtrim($this->getUrl(), '/') . $this->getRepository()->/** @scrutinizer ignore-call */ getApiPath();
Loading history...
220
    }
221
222
    /**
223
     * Get the project's title, the human-language full title of the wiki (e.g. "English Wikipedia (en.wikipedia.org)").
224
     */
225
    public function getTitle(): string
226
    {
227
        $metadata = $this->getMetadata();
228
        return $metadata['general']['wikiName'].' ('.$this->getDomain().')';
229
    }
230
231
    /**
232
     * Get an array of this project's namespaces and their IDs.
233
     * @return string[] Keys are IDs, values are names.
234
     */
235
    public function getNamespaces(): array
236
    {
237
        $metadata = $this->getMetadata();
238
        return $metadata['namespaces'];
239
    }
240
241
    /**
242
     * Get the title of the Main Page.
243
     * @return string
244
     */
245
    public function getMainPage(): string
246
    {
247
        $metadata = $this->getMetadata();
248
        return $metadata['general']['mainpage'] ?? '';
249
    }
250
251
    /**
252
     * List of extensions that are installed on the wiki.
253
     * @return string[]
254
     */
255
    public function getInstalledExtensions(): array
256
    {
257
        // Quick cache, valid only for the same request.
258
        static $installedExtensions = null;
259
        if (is_array($installedExtensions)) {
260
            return $installedExtensions;
261
        }
262
263
        return $installedExtensions = $this->getRepository()->getInstalledExtensions($this);
0 ignored issues
show
Bug introduced by
The method getInstalledExtensions() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\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

263
        return $installedExtensions = $this->getRepository()->/** @scrutinizer ignore-call */ getInstalledExtensions($this);
Loading history...
264
    }
265
266
    /**
267
     * Get a list of users who are in one of the given user groups.
268
     * @param string[] $groups User groups to search for.
269
     * @param string[] $globalGroups Global groups to search for.
270
     * @return string[] User groups keyed by user name.
271
     */
272
    public function getUsersInGroups(array $groups, array $globalGroups): array
273
    {
274
        $users = [];
275
        $usersAndGroups = $this->getRepository()->getUsersInGroups($this, $groups, $globalGroups);
0 ignored issues
show
Bug introduced by
The method getUsersInGroups() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\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

275
        $usersAndGroups = $this->getRepository()->/** @scrutinizer ignore-call */ getUsersInGroups($this, $groups, $globalGroups);
Loading history...
276
        foreach ($usersAndGroups as $userAndGroup) {
277
            $username = $userAndGroup['user_name'];
278
            if (isset($users[$username])) {
279
                $users[$username][] = $userAndGroup['user_group'];
280
            } else {
281
                $users[$username] = [$userAndGroup['user_group']];
282
            }
283
        }
284
        return $users;
285
    }
286
287
    /**
288
     * Get the name of the page on this project that the user must create in order to opt in for restricted statistics.
289
     * @param User $user
290
     * @return string
291
     */
292
    public function userOptInPage(User $user): string
293
    {
294
        return 'User:' . $user->getUsername() . '/EditCounterOptIn.js';
295
    }
296
297
    /**
298
     * Has a user opted in to having their restricted statistics displayed to anyone?
299
     * @param User $user
300
     * @return bool
301
     */
302
    public function userHasOptedIn(User $user): bool
303
    {
304
        // 1. First check to see if the whole project has opted in.
305
        if (!isset($this->userOptedIn)) {
306
            $optedInProjects = $this->getRepository()->optedIn();
0 ignored issues
show
Bug introduced by
The method optedIn() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\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

306
            $optedInProjects = $this->getRepository()->/** @scrutinizer ignore-call */ optedIn();
Loading history...
307
            $this->userOptedIn = in_array($this->getDatabaseName(), $optedInProjects);
308
        }
309
        if ($this->userOptedIn) {
310
            return true;
311
        }
312
313
        // 2. Then see if the currently-logged-in user is requesting their own statistics.
314
        if ($user->isCurrentlyLoggedIn()) {
315
            return true;
316
        }
317
318
        // 3. Then see if the user has opted in on this project.
319
        $userNsId = 2;
320
        // Remove namespace since we're querying the database and supplying a namespace ID.
321
        $optInPage = preg_replace('/^User:/', '', $this->userOptInPage($user));
322
        $localExists = $this->getRepository()->pageHasContent($this, $userNsId, $optInPage);
0 ignored issues
show
Bug introduced by
The method pageHasContent() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\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

322
        $localExists = $this->getRepository()->/** @scrutinizer ignore-call */ pageHasContent($this, $userNsId, $optInPage);
Loading history...
323
        if ($localExists) {
324
            return true;
325
        }
326
327
        // 4. Lastly, see if they've opted in globally on the default project or Meta.
328
        $globalPageName = $user->getUsername() . '/EditCounterGlobalOptIn.js';
329
        $globalProject = $this->getRepository()->getGlobalProject();
0 ignored issues
show
Bug introduced by
The method getGlobalProject() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\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

329
        $globalProject = $this->getRepository()->/** @scrutinizer ignore-call */ getGlobalProject();
Loading history...
330
        $globalExists = $globalProject->getRepository()
331
            ->pageHasContent($globalProject, $userNsId, $globalPageName);
332
        if ($globalExists) {
333
            return true;
334
        }
335
336
        return false;
337
    }
338
339
    /**
340
     * Normalize and quote a table name for use in SQL.
341
     * @param string $tableName
342
     * @param string|null $tableExtension Optional table extension, which will only get used if we're on Labs.
343
     * @return string Fully-qualified and quoted table name.
344
     */
345
    public function getTableName(string $tableName, ?string $tableExtension = null): string
346
    {
347
        return $this->getRepository()->getTableName($this->getDatabaseName(), $tableName, $tableExtension);
348
    }
349
}
350