Passed
Pull Request — main (#442)
by MusikAnimal
09:06 queued 04:53
created

Project::userOptInPage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
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
     * The base URL path of this project (that page titles are appended to).
163
     * For some wikis the title (apparently) may not be at the end.
164
     * Replace $1 with the article name.
165
     * @link https://www.mediawiki.org/wiki/Manual:$wgArticlePath
166
     * @return string
167
     */
168
    public function getArticlePath(): string
169
    {
170
        $metadata = $this->getMetadata();
171
        return $metadata['general']['articlePath'] ?? '/wiki/$1';
172
    }
173
174
    /**
175
     * The URL path of the directory that contains index.php, with no trailing slash.
176
     * Defaults to '/w' which is the same as the normal WMF set-up.
177
     * @link https://www.mediawiki.org/wiki/Manual:$wgScriptPath
178
     * @return string
179
     */
180
    public function getScriptPath(): string
181
    {
182
        $metadata = $this->getMetadata();
183
        return $metadata['general']['scriptPath'] ?? '/w';
184
    }
185
186
    /**
187
     * The URL path to index.php
188
     * Defaults to '/w/index.php' which is the same as the normal WMF set-up.
189
     * @return string
190
     */
191
    public function getScript(): string
192
    {
193
        $metadata = $this->getMetadata();
194
        return $metadata['general']['script'] ?? $this->getScriptPath() . '/index.php';
195
    }
196
197
    /**
198
     * The full URL to api.php.
199
     * @return string
200
     */
201
    public function getApiUrl(): string
202
    {
203
        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

203
        return rtrim($this->getUrl(), '/') . $this->getRepository()->/** @scrutinizer ignore-call */ getApiPath();
Loading history...
204
    }
205
206
    /**
207
     * Get the project's title, the human-language full title of the wiki (e.g. "English Wikipedia (en.wikipedia.org)").
208
     */
209
    public function getTitle(): string
210
    {
211
        $metadata = $this->getMetadata();
212
        return $metadata['general']['wikiName'].' ('.$this->getDomain().')';
213
    }
214
215
    /**
216
     * Get an array of this project's namespaces and their IDs.
217
     * @return string[] Keys are IDs, values are names.
218
     */
219
    public function getNamespaces(): array
220
    {
221
        $metadata = $this->getMetadata();
222
        return $metadata['namespaces'];
223
    }
224
225
    /**
226
     * Get the title of the Main Page.
227
     * @return string
228
     */
229
    public function getMainPage(): string
230
    {
231
        $metadata = $this->getMetadata();
232
        return $metadata['general']['mainpage'] ?? '';
233
    }
234
235
    /**
236
     * List of extensions that are installed on the wiki.
237
     * @return string[]
238
     */
239
    public function getInstalledExtensions(): array
240
    {
241
        // Quick cache, valid only for the same request.
242
        static $installedExtensions = null;
243
        if (is_array($installedExtensions)) {
244
            return $installedExtensions;
245
        }
246
247
        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

247
        return $installedExtensions = $this->getRepository()->/** @scrutinizer ignore-call */ getInstalledExtensions($this);
Loading history...
248
    }
249
250
    /**
251
     * Get a list of users who are in one of the given user groups.
252
     * @param string[] $groups User groups to search for.
253
     * @param string[] $globalGroups Global groups to search for.
254
     * @return string[] User groups keyed by user name.
255
     */
256
    public function getUsersInGroups(array $groups, array $globalGroups): array
257
    {
258
        $users = [];
259
        $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

259
        $usersAndGroups = $this->getRepository()->/** @scrutinizer ignore-call */ getUsersInGroups($this, $groups, $globalGroups);
Loading history...
260
        foreach ($usersAndGroups as $userAndGroup) {
261
            $username = $userAndGroup['user_name'];
262
            if (isset($users[$username])) {
263
                $users[$username][] = $userAndGroup['user_group'];
264
            } else {
265
                $users[$username] = [$userAndGroup['user_group']];
266
            }
267
        }
268
        return $users;
269
    }
270
271
    /**
272
     * Get the name of the page on this project that the user must create in order to opt in for restricted statistics.
273
     * @param User $user
274
     * @return string
275
     */
276
    public function userOptInPage(User $user): string
277
    {
278
        return 'User:' . $user->getUsername() . '/EditCounterOptIn.js';
279
    }
280
281
    /**
282
     * Has a user opted in to having their restricted statistics displayed to anyone?
283
     * @param User $user
284
     * @return bool
285
     */
286
    public function userHasOptedIn(User $user): bool
287
    {
288
        // 1. First check to see if the whole project has opted in.
289
        if (!isset($this->userOptedIn)) {
290
            $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

290
            $optedInProjects = $this->getRepository()->/** @scrutinizer ignore-call */ optedIn();
Loading history...
291
            $this->userOptedIn = in_array($this->getDatabaseName(), $optedInProjects);
292
        }
293
        if ($this->userOptedIn) {
294
            return true;
295
        }
296
297
        // 2. Then see if the currently-logged-in user is requesting their own statistics.
298
        if ($user->isCurrentlyLoggedIn()) {
299
            return true;
300
        }
301
302
        // 3. Then see if the user has opted in on this project.
303
        $userNsId = 2;
304
        // Remove namespace since we're querying the database and supplying a namespace ID.
305
        $optInPage = preg_replace('/^User:/', '', $this->userOptInPage($user));
306
        $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

306
        $localExists = $this->getRepository()->/** @scrutinizer ignore-call */ pageHasContent($this, $userNsId, $optInPage);
Loading history...
307
        if ($localExists) {
308
            return true;
309
        }
310
311
        // 4. Lastly, see if they've opted in globally on the default project or Meta.
312
        $globalPageName = $user->getUsername() . '/EditCounterGlobalOptIn.js';
313
        $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

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