Passed
Branch master (0917e1)
by MusikAnimal
11:12
created

Project::userOptInPage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

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
ccs 2
cts 2
cp 1
crap 1
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 100
    public function __construct(string $nameOrUrl)
38
    {
39 100
        $this->nameUnnormalized = $nameOrUrl;
40 100
        $this->pageAssessments = new PageAssessments($this);
41 100
    }
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 the title of the Main Page.
217
     * @return string
218
     */
219 1
    public function getMainPage(): string
220
    {
221 1
        $metadata = $this->getMetadata();
222 1
        return $metadata['general']['mainpage'] ?? '';
223
    }
224
225
    /**
226
     * List of extensions that are installed on the wiki.
227
     * @return string[]
228
     */
229
    public function getInstalledExtensions(): array
230
    {
231
        // Quick cache, valid only for the same request.
232
        static $installedExtensions = null;
233
        if (is_array($installedExtensions)) {
234
            return $installedExtensions;
235
        }
236
237
        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

237
        return $installedExtensions = $this->getRepository()->/** @scrutinizer ignore-call */ getInstalledExtensions($this);
Loading history...
238
    }
239
240
    /**
241
     * Get a list of users who are in one of the given user groups.
242
     * @param string[] $groups User groups to search for.
243
     * @param string[] $globalGroups Global groups to search for.
244
     * @return string[] User groups keyed by user name.
245
     */
246 1
    public function getUsersInGroups(array $groups, array $globalGroups): array
247
    {
248 1
        $users = [];
249 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

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

280
            $optedInProjects = $this->getRepository()->/** @scrutinizer ignore-call */ optedIn();
Loading history...
281 3
            $this->userOptedIn = in_array($this->getDatabaseName(), $optedInProjects);
282
        }
283 3
        if ($this->userOptedIn) {
284 1
            return true;
285
        }
286
287
        // 2. Then see if the currently-logged-in user is requesting their own statistics.
288 2
        if ($user->isCurrentlyLoggedIn()) {
289
            return true;
290
        }
291
292
        // 3. Then see if the user has opted in on this project.
293 2
        $userNsId = 2;
294
        // Remove namespace since we're querying the database and supplying a namespace ID.
295 2
        $optInPage = preg_replace('/^User:/', '', $this->userOptInPage($user));
296 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

296
        $localExists = $this->getRepository()->/** @scrutinizer ignore-call */ pageHasContent($this, $userNsId, $optInPage);
Loading history...
297 2
        if ($localExists) {
298
            return true;
299
        }
300
301
        // 4. Lastly, see if they've opted in globally on the default project or Meta.
302 2
        $globalPageName = $user->getUsername() . '/EditCounterGlobalOptIn.js';
303 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

303
        $globalProject = $this->getRepository()->/** @scrutinizer ignore-call */ getGlobalProject();
Loading history...
304 2
        if ($globalProject instanceof Project) {
305 2
            $globalExists = $globalProject->getRepository()
306 2
                ->pageHasContent($globalProject, $userNsId, $globalPageName);
307 2
            if ($globalExists) {
308
                return true;
309
            }
310
        }
311
312 2
        return false;
313
    }
314
315
    /**
316
     * Normalize and quote a table name for use in SQL.
317
     * @param string $tableName
318
     * @param string|null $tableExtension Optional table extension, which will only get used if we're on Labs.
319
     * @return string Fully-qualified and quoted table name.
320
     */
321 1
    public function getTableName(string $tableName, ?string $tableExtension = null): string
322
    {
323 1
        return $this->getRepository()->getTableName($this->getDatabaseName(), $tableName, $tableExtension);
324
    }
325
}
326