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

Project::userHasOptedIn()   C

Complexity

Conditions 7
Paths 12

Size

Total Lines 37
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 8.1426

Importance

Changes 0
Metric Value
cc 7
eloc 20
nc 12
nop 1
dl 0
loc 37
ccs 15
cts 21
cp 0.7143
crap 8.1426
rs 6.7272
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the Project class.
4
 */
5
6
namespace Xtools;
7
8
/**
9
 * A Project is a single wiki that XTools is querying.
10
 */
11
class Project extends Model
12
{
13
    /** @var string The project name as supplied by the user. */
14
    protected $nameUnnormalized;
15
16
    /** @var string[] Basic metadata about the project */
17
    protected $metadata;
18
19
    /** @var string[] Project's 'dbName', 'url' and 'lang'. */
20
    protected $basicInfo;
21
22
    /** @var PageAssessments Contains methods around the page assessments config for the Project. */
23
    protected $pageAssessments;
24
25
    /**
26
     * Whether the user being queried for in this session
27
     *   has opted in to restricted statistics
28
     * @var bool
29
     */
30
    protected $userOptedIn;
31
32
    /**
33
     * Create a new Project.
34
     * @param string $nameOrUrl The project's database name or URL.
35
     */
36 114
    public function __construct($nameOrUrl)
37
    {
38 114
        $this->nameUnnormalized = $nameOrUrl;
39 114
        $this->pageAssessments = new PageAssessments($this);
40 114
    }
41
42
    /**
43
     * Get the associated PageAssessments model.
44
     * @return PageAssessments
45
     */
46 11
    public function getPageAssessments()
47
    {
48 11
        return $this->pageAssessments;
49
    }
50
51
    /**
52
     * Whether or not this project supports page assessments,
53
     * or if they exist for the given namespace.
54
     * @param int $nsId Namespace ID.
55
     * @return bool
56
     */
57
    public function hasPageAssessments($nsId = null)
58
    {
59
        if ($nsId !== null && (int)$nsId > 0) {
60
            return $this->pageAssessments->isSupportedNamespace($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()
72
    {
73
        return $this->getDatabaseName();
74
    }
75
76
    /**
77
     * Get 'dbName', 'url' and 'lang' of the project, the relevant basic info
78
     *   we can get from the meta database. This is all you need to make
79
     *   database queries. More comprehensive metadata can be fetched with
80
     *   getMetadata() at the expense of an API call, which may be cached.
81
     * @return string[]
82
     */
83 49
    protected function getBasicInfo()
84
    {
85 49
        if (empty($this->basicInfo)) {
86 49
            $this->basicInfo = $this->getRepository()->getOne($this->nameUnnormalized);
87
        }
88 48
        return $this->basicInfo;
89
    }
90
91
    /**
92
     * Get full metadata about the project. See ProjectRepository::getMetadata
93
     *   for more information.
94
     * @return string[]
95
     */
96 19
    protected function getMetadata()
97
    {
98 19
        if (empty($this->metadata)) {
99 19
            $url = $this->getBasicInfo()['url'];
100 18
            $this->metadata = $this->getRepository()->getMetadata($url);
101
        }
102 18
        return $this->metadata;
103
    }
104
105
    /**
106
     * Does this project exist?
107
     * @return bool
108
     */
109 11
    public function exists()
110
    {
111 11
        return !empty($this->getDomain());
112
    }
113
114
    /**
115
     * The unique domain name of this project, without protocol or path components.
116
     * This should be used as the canonical project identifier.
117
     *
118
     * @return string
119
     */
120 27
    public function getDomain()
121
    {
122 27
        $url = isset($this->getBasicInfo()['url']) ? $this->getBasicInfo()['url'] : '';
123 27
        return parse_url($url, PHP_URL_HOST);
124
    }
125
126
    /**
127
     * The name of the database for this project.
128
     *
129
     * @return string
130
     */
131 13
    public function getDatabaseName()
132
    {
133 13
        return isset($this->getBasicInfo()['dbName']) ? $this->getBasicInfo()['dbName'] : '';
134
    }
135
136
    /**
137
     * The language for this project.
138
     *
139
     * @return string
140
     */
141 16
    public function getLang()
142
    {
143 16
        return isset($this->getBasicInfo()['lang']) ? $this->getBasicInfo()['lang'] : '';
144
    }
145
146
    /**
147
     * The project URL is the fully-qualified domain name, with protocol and trailing slash.
148
     *
149
     * @param bool $withTrailingSlash Whether to append a slash.
150
     * @return string
151
     */
152 16
    public function getUrl($withTrailingSlash = true)
153
    {
154 16
        return rtrim($this->getBasicInfo()['url'], '/') . ($withTrailingSlash ? '/' : '');
155
    }
156
157
    /**
158
     * Get a MediawikiApi object for this Project.
159
     *
160
     * @return \Mediawiki\Api\MediawikiApi
161
     */
162
    public function getApi()
163
    {
164
        return $this->getRepository()->getMediawikiApi($this);
165
    }
166
167
    /**
168
     * The base URL path of this project (that page titles are appended to).
169
     * For some wikis the title (apparently) may not be at the end.
170
     * Replace $1 with the article name.
171
     *
172
     * @link https://www.mediawiki.org/wiki/Manual:$wgArticlePath
173
     *
174
     * @return string
175
     */
176 5
    public function getArticlePath()
177
    {
178 5
        $metadata = $this->getMetadata();
179 5
        return isset($metadata['general']['articlePath'])
180 5
            ? $metadata['general']['articlePath']
181 5
            : '/wiki/$1';
182
    }
183
184
    /**
185
     * The URL path of the directory that contains index.php, with no trailing slash.
186
     * Defaults to '/w' which is the same as the normal WMF set-up.
187
     *
188
     * @link https://www.mediawiki.org/wiki/Manual:$wgScriptPath
189
     *
190
     * @return string
191
     */
192 2
    public function getScriptPath()
193
    {
194 2
        $metadata = $this->getMetadata();
195 2
        return isset($metadata['general']['scriptPath'])
196 2
            ? $metadata['general']['scriptPath']
197 2
            : '/w';
198
    }
199
200
    /**
201
     * The URL path to index.php
202
     * Defaults to '/w/index.php' which is the same as the normal WMF set-up.
203
     *
204
     * @return string
205
     */
206 1
    public function getScript()
207
    {
208 1
        $metadata = $this->getMetadata();
209 1
        return isset($metadata['general']['script'])
210 1
            ? $metadata['general']['script']
211 1
            : $this->getScriptPath() . '/index.php';
212
    }
213
214
    /**
215
     * The full URL to api.php
216
     *
217
     * @return string
218
     */
219 8
    public function getApiUrl()
220
    {
221 8
        return rtrim($this->getUrl(), '/') . $this->getRepository()->getApiPath();
222
    }
223
224
    /**
225
     * Get this project's title, the human-language full title of the wiki (e.g. "English
226
     * Wikipedia" or
227
     */
228 1
    public function getTitle()
229
    {
230 1
        $metadata = $this->getMetadata();
231
        return $metadata['general']['wikiName'].' ('.$this->getDomain().')';
232
    }
233
234
    /**
235
     * Get an array of this project's namespaces and their IDs.
236
     *
237
     * @return string[] Keys are IDs, values are names.
238
     */
239 11
    public function getNamespaces()
240
    {
241 11
        $metadata = $this->getMetadata();
242 11
        return $metadata['namespaces'];
243
    }
244
245
    /**
246
     * Get the title of the Main Page.
247
     * @return string
248
     */
249 1
    public function getMainPage()
250
    {
251 1
        $metadata = $this->getMetadata();
252 1
        return isset($metadata['general']['mainpage'])
253 1
            ? $metadata['general']['mainpage']
254 1
            : '';
255
    }
256
257
    /**
258
     * Get a list of users who are in one of the given user groups.
259
     * @param string[] User groups to search for.
260
     * @return string[] User groups keyed by user name.
261
     */
262 1
    public function getUsersInGroups($groups)
263
    {
264 1
        $users = [];
265 1
        $usersAndGroups = $this->getRepository()->getUsersInGroups($this, $groups);
266 1
        foreach ($usersAndGroups as $userAndGroup) {
267 1
            $username = $userAndGroup['user_name'];
268 1
            if (isset($users[$username])) {
269 1
                array_push($users[$username], $userAndGroup['ug_group']);
270
            } else {
271 1
                $users[$username] = [$userAndGroup['ug_group']];
272
            }
273
        }
274 1
        return $users;
275
    }
276
277
    /**
278
     * Get the name of the page on this project that the user must create in order to opt in for
279
     * restricted statistics display.
280
     * @param User $user
281
     * @return string
282
     */
283 2
    public function userOptInPage(User $user)
284
    {
285 2
        $localPageName = 'User:' . $user->getUsername() . '/EditCounterOptIn.js';
286 2
        return $localPageName;
287
    }
288
289
    /**
290
     * Has a user opted in to having their restricted statistics displayed to anyone?
291
     * @param User $user
292
     * @return bool
293
     */
294 3
    public function userHasOptedIn(User $user)
295
    {
296
        // 1. First check to see if the whole project has opted in.
297 3
        if (!$this->userOptedIn) {
298 3
            $optedInProjects = $this->getRepository()->optedIn();
299 3
            $this->userOptedIn = in_array($this->getDatabaseName(), $optedInProjects);
300
        }
301 3
        if ($this->userOptedIn) {
302 1
            return true;
303
        }
304
305
        // 2. Then see if the currently-logged-in user is requesting their own statistics.
306 2
        if ($user->isCurrentlyLoggedIn()) {
307
            return true;
308
        }
309
310
        // 3. Then see if the user has opted in on this project.
311 2
        $userNsId = 2;
312
        // Remove namespace since we're querying the database and supplying a namespace ID.
313 2
        $optInPage = preg_replace('/^User:/', '', $this->userOptInPage($user));
314 2
        $localExists = $this->getRepository()->pageHasContent($this, $userNsId, $optInPage);
315 2
        if ($localExists) {
316
            return true;
317
        }
318
319
        // 4. Lastly, see if they've opted in globally on the default project or Meta.
320 2
        $globalPageName = $user->getUsername() . '/EditCounterGlobalOptIn.js';
321 2
        $globalProject = $this->getRepository()->getGlobalProject();
322 2
        if ($globalProject instanceof Project) {
323
            $globalExists = $globalProject->getRepository()
324
                ->pageHasContent($globalProject, $userNsId, $globalPageName);
325
            if ($globalExists) {
326
                return true;
327
            }
328
        }
329
330 2
        return false;
331
    }
332
333
    /**
334
     * Normalize and quote a table name for use in SQL.
335
     * @param string $tableName
336
     * @param string|null $tableExtension Optional table extension, which will only get used if we're on Labs.
337
     * @return string Fully-qualified and quoted table name.
338
     */
339 1
    public function getTableName($tableName, $tableExtension = null)
340
    {
341 1
        return $this->getRepository()->getTableName($this->getDatabaseName(), $tableName, $tableExtension);
342
    }
343
}
344