Passed
Push — master ( c88656...e5f428 )
by MusikAnimal
08:08
created

Page::getWikidataErrors()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 37
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4.25

Importance

Changes 0
Metric Value
cc 4
eloc 23
nc 5
nop 0
dl 0
loc 37
ccs 15
cts 20
cp 0.75
crap 4.25
rs 9.552
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the Page class.
4
 */
5
6
namespace Xtools;
7
8
use DateTime;
9
10
/**
11
 * A Page is a single wiki page in one project.
12
 */
13
class Page extends Model
14
{
15
    /** @var string The page name as provided at instantiation. */
16
    protected $unnormalizedPageName;
17
18
    /** @var string[] Metadata about this page. */
19
    protected $pageInfo;
20
21
    /** @var string[] Revision history of this page. */
22
    protected $revisions;
23
24
    /** @var int Number of revisions for this page. */
25
    protected $numRevisions;
26
27
    /** @var string[] List of Wikidata sitelinks for this page. */
28
    protected $wikidataItems;
29
30
    /** @var int Number of Wikidata sitelinks for this page. */
31
    protected $numWikidataItems;
32
33
    /**
34
     * Page constructor.
35
     * @param Project $project
36
     * @param string $pageName
37
     */
38 40
    public function __construct(Project $project, $pageName)
39
    {
40 40
        $this->project = $project;
41 40
        $this->unnormalizedPageName = $pageName;
42 40
    }
43
44
    /**
45
     * Unique identifier for this Page, to be used in cache keys.
46
     * Use of md5 ensures the cache key does not contain reserved characters.
47
     * @see Repository::getCacheKey()
48
     * @return string
49
     * @codeCoverageIgnore
50
     */
51
    public function getCacheKey()
52
    {
53
        return md5($this->getId());
54
    }
55
56
    /**
57
     * Get basic information about this page from the repository.
58
     * @return string[]
59
     */
60 9
    protected function getPageInfo()
61
    {
62 9
        if (empty($this->pageInfo)) {
63 9
            $this->pageInfo = $this->getRepository()
64 9
                ->getPageInfo($this->project, $this->unnormalizedPageName);
65
        }
66 9
        return $this->pageInfo;
67
    }
68
69
    /**
70
     * Get the page's title.
71
     * @param bool $useUnnormalized Use the unnormalized page title to avoid an
72
     *    API call. This should be used only if you fetched the page title via
73
     *    other means (SQL query), and is not from user input alone.
74
     * @return string
75
     */
76 3
    public function getTitle($useUnnormalized = false)
77
    {
78 3
        if ($useUnnormalized) {
79 1
            return $this->unnormalizedPageName;
80
        }
81 3
        $info = $this->getPageInfo();
82 3
        return isset($info['title']) ? $info['title'] : $this->unnormalizedPageName;
83
    }
84
85
    /**
86
     * Get the page's title without the namespace.
87
     * @return string
88
     */
89 1
    public function getTitleWithoutNamespace()
90
    {
91 1
        $info = $this->getPageInfo();
92 1
        $title = isset($info['title']) ? $info['title'] : $this->unnormalizedPageName;
93 1
        $nsName = $this->getNamespaceName();
94 1
        return str_replace($nsName . ':', '', $title);
95
    }
96
97
    /**
98
     * Get this page's database ID.
99
     * @return int
100
     */
101 1
    public function getId()
102
    {
103 1
        $info = $this->getPageInfo();
104 1
        return isset($info['pageid']) ? $info['pageid'] : null;
105
    }
106
107
    /**
108
     * Get this page's length in bytes.
109
     * @return int
110
     */
111 1
    public function getLength()
112
    {
113 1
        $info = $this->getPageInfo();
114 1
        return isset($info['length']) ? $info['length'] : null;
115
    }
116
117
    /**
118
     * Get HTML for the stylized display of the title.
119
     * The text will be the same as Page::getTitle().
120
     * @return string
121
     */
122 1
    public function getDisplayTitle()
123
    {
124 1
        $info = $this->getPageInfo();
125 1
        if (isset($info['displaytitle'])) {
126 1
            return $info['displaytitle'];
127
        }
128 1
        return $this->getTitle();
129
    }
130
131
    /**
132
     * Get the full URL of this page.
133
     * @return string
134
     */
135 1
    public function getUrl()
136
    {
137 1
        $info = $this->getPageInfo();
138 1
        return isset($info['fullurl']) ? $info['fullurl'] : null;
139
    }
140
141
    /**
142
     * Get the numerical ID of the namespace of this page.
143
     * @return int
144
     */
145 2
    public function getNamespace()
146
    {
147 2
        $info = $this->getPageInfo();
148 2
        return isset($info['ns']) ? $info['ns'] : null;
149
    }
150
151
    /**
152
     * Get the name of the namespace of this page.
153
     * @return string
154
     */
155 1
    public function getNamespaceName()
156
    {
157 1
        $info = $this->getPageInfo();
158 1
        return isset($info['ns'])
159 1
            ? $this->getProject()->getNamespaces()[$info['ns']]
160 1
            : null;
161
    }
162
163
    /**
164
     * Get the number of page watchers.
165
     * @return int
166
     */
167 1
    public function getWatchers()
168
    {
169 1
        $info = $this->getPageInfo();
170 1
        return isset($info['watchers']) ? $info['watchers'] : null;
171
    }
172
173
    /**
174
     * Get the HTML content of the body of the page.
175
     * @param DateTime|int $target If a DateTime object, the
176
     *   revision at that time will be returned. If an integer, it is
177
     *   assumed to be the actual revision ID.
178
     * @return string
179
     */
180 1
    public function getHTMLContent($target = null)
181
    {
182 1
        if (is_a($target, 'DateTime')) {
183
            $target = $this->getRepository()->getRevisionIdAtDate($this, $target);
184
        }
185 1
        return $this->getRepository()->getHTMLContent($this, $target);
186
    }
187
188
    /**
189
     * Whether or not this page exists.
190
     * @return bool
191
     */
192 1
    public function exists()
193
    {
194 1
        $info = $this->getPageInfo();
195 1
        return !isset($info['missing']) && !isset($info['invalid']);
196
    }
197
198
    /**
199
     * Get the Project to which this page belongs.
200
     * @return Project
201
     */
202 16
    public function getProject()
203
    {
204 16
        return $this->project;
205
    }
206
207
    /**
208
     * Get the language code for this page.
209
     * If not set, the language code for the project is returned.
210
     * @return string
211
     */
212 2
    public function getLang()
213
    {
214 2
        $info = $this->getPageInfo();
215 2
        if (isset($info['pagelanguage'])) {
216 2
            return $info['pagelanguage'];
217
        } else {
218
            return $this->getProject()->getLang();
219
        }
220
    }
221
222
    /**
223
     * Get the Wikidata ID of this page.
224
     * @return string
225
     */
226 4
    public function getWikidataId()
227
    {
228 4
        $info = $this->getPageInfo();
229 4
        if (isset($info['pageprops']['wikibase_item'])) {
230 3
            return $info['pageprops']['wikibase_item'];
231
        } else {
232 1
            return null;
233
        }
234
    }
235
236
    /**
237
     * Get the number of revisions the page has.
238
     * @param User $user Optionally limit to those of this user.
239
     * @param false|int $start
240
     * @param false|int $end
241
     * @return int
242
     */
243 4
    public function getNumRevisions(User $user = null, $start = false, $end = false)
244
    {
245
        // If a user is given, we will not cache the result via instance variable.
246 4
        if ($user !== null) {
247
            return (int) $this->getRepository()->getNumRevisions($this, $user, $start, $end);
248
        }
249
250
        // Return cached value, if present.
251 4
        if ($this->numRevisions !== null) {
252
            return $this->numRevisions;
253
        }
254
255
        // Otherwise, return the count of all revisions if already present.
256 4
        if ($this->revisions !== null) {
257
            $this->numRevisions = count($this->revisions);
258
        } else {
259
            // Otherwise do a COUNT in the event fetching all revisions is not desired.
260 4
            $this->numRevisions = (int) $this->getRepository()->getNumRevisions($this, null, $start, $end);
261
        }
262
263 4
        return $this->numRevisions;
264
    }
265
266
    /**
267
     * Get all edits made to this page.
268
     * @param User|null $user Specify to get only revisions by the given user.
269
     * @param false|int $start
270
     * @param false|int $end
271
     * @return array
272
     */
273
    public function getRevisions(User $user = null, $start = false, $end = false)
274
    {
275
        if ($this->revisions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->revisions of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
276
            return $this->revisions;
277
        }
278
279
        $this->revisions = $this->getRepository()->getRevisions($this, $user, $start, $end);
280
281
        return $this->revisions;
282
    }
283
284
    /**
285
     * Get the full page wikitext.
286
     * @return string|null Null if nothing was found.
287
     */
288 1
    public function getWikitext()
289
    {
290 1
        $content = $this->getRepository()->getPagesWikitext(
291 1
            $this->getProject(),
292 1
            [ $this->getTitle() ]
293
        );
294
295 1
        return isset($content[$this->getTitle()])
296 1
            ? $content[$this->getTitle()]
297 1
            : null;
298
    }
299
300
    /**
301
     * Get the statement for a single revision, so that you can iterate row by row.
302
     * @see PageRepository::getRevisionsStmt()
303
     * @param User|null $user Specify to get only revisions by the given user.
304
     * @param int $limit Max number of revisions to process.
305
     * @param int $numRevisions Number of revisions, if known. This is used solely to determine the
306
     *   OFFSET if we are given a $limit. If $limit is set and $numRevisions is not set, a
307
     *   separate query is ran to get the nuber of revisions.
308
     * @param false|int $start
309
     * @param false|int $end
310
     * @return \Doctrine\DBAL\Driver\PDOStatement
311
     */
312
    public function getRevisionsStmt(
313
        User $user = null,
314
        $limit = null,
315
        $numRevisions = null,
316
        $start = false,
317
        $end = false
318
    ) {
319
        // If we have a limit, we need to know the total number of revisions so that PageRepo
320
        // will properly set the OFFSET. See PageRepository::getRevisionsStmt() for more info.
321
        if (isset($limit) && $numRevisions === null) {
322
            $numRevisions = $this->getNumRevisions($user, $start, $end);
323
        }
324
        return $this->getRepository()->getRevisionsStmt($this, $user, $limit, $numRevisions, $start, $end);
325
    }
326
327
    /**
328
     * Get various basic info used in the API, including the
329
     *   number of revisions, unique authors, initial author
330
     *   and edit count of the initial author.
331
     * This is combined into one query for better performance.
332
     * Caching is intentionally disabled, because using the gadget,
333
     *   this will get hit for a different page constantly, where
334
     *   the likelihood of cache benefiting us is slim.
335
     * @return string[]
336
     */
337
    public function getBasicEditingInfo()
338
    {
339
        return $this->getRepository()->getBasicEditingInfo($this);
340
    }
341
342
    /**
343
     * Get CheckWiki errors for this page
344
     * @return string[] See getErrors() for format
345
     */
346 1
    public function getCheckWikiErrors()
347
    {
348 1
        return $this->getRepository()->getCheckWikiErrors($this);
349
    }
350
351
    /**
352
     * Get Wikidata errors for this page
353
     * @return string[] See getErrors() for format
354
     */
355 2
    public function getWikidataErrors()
356
    {
357 2
        $errors = [];
358
359 2
        if (empty($this->getWikidataId())) {
360
            return [];
361
        }
362
363 2
        $wikidataInfo = $this->getRepository()->getWikidataInfo($this);
364
365 2
        $terms = array_map(function ($entry) {
366 2
            return $entry['term'];
367 2
        }, $wikidataInfo);
368
369 2
        $lang = $this->getLang();
370
371 2
        if (!in_array('label', $terms)) {
372
            $errors[] = [
373
                'prio' => 2,
374
                'name' => 'Wikidata',
375
                'notice' => "Label for language <em>$lang</em> is missing", // FIXME: i18n
376
                'explanation' => "See: <a target='_blank' " .
377
                    "href='//www.wikidata.org/wiki/Help:Label'>Help:Label</a>",
378
            ];
379
        }
380
381 2
        if (!in_array('description', $terms)) {
382 2
            $errors[] = [
383 2
                'prio' => 3,
384 2
                'name' => 'Wikidata',
385 2
                'notice' => "Description for language <em>$lang</em> is missing", // FIXME: i18n
386
                'explanation' => "See: <a target='_blank' " .
387
                    "href='//www.wikidata.org/wiki/Help:Description'>Help:Description</a>",
388
            ];
389
        }
390
391 2
        return $errors;
392
    }
393
394
    /**
395
     * Get Wikidata and CheckWiki errors, if present
396
     * @return string[] List of errors in the format:
397
     *    [[
398
     *         'prio' => int,
399
     *         'name' => string,
400
     *         'notice' => string (HTML),
401
     *         'explanation' => string (HTML)
402
     *     ], ... ]
403
     */
404 1
    public function getErrors()
405
    {
406
        // Includes label and description
407 1
        $wikidataErrors = $this->getWikidataErrors();
408
409 1
        $checkWikiErrors = $this->getCheckWikiErrors();
410
411 1
        return array_merge($wikidataErrors, $checkWikiErrors);
412
    }
413
414
    /**
415
     * Get all wikidata items for the page, not just languages of sister projects
416
     * @return int Number of records.
417
     */
418 1
    public function getWikidataItems()
419
    {
420 1
        if (!is_array($this->wikidataItems)) {
0 ignored issues
show
introduced by
The condition is_array($this->wikidataItems) is always true.
Loading history...
421 1
            $this->wikidataItems = $this->getRepository()->getWikidataItems($this);
422
        }
423 1
        return $this->wikidataItems;
424
    }
425
426
    /**
427
     * Count wikidata items for the page, not just languages of sister projects
428
     * @return int Number of records.
429
     */
430 2
    public function countWikidataItems()
431
    {
432 2
        if (is_array($this->wikidataItems)) {
0 ignored issues
show
introduced by
The condition is_array($this->wikidataItems) is always true.
Loading history...
433
            $this->numWikidataItems = count($this->wikidataItems);
434 2
        } elseif ($this->numWikidataItems === null) {
435 2
            $this->numWikidataItems = $this->getRepository()->countWikidataItems($this);
436
        }
437 2
        return $this->numWikidataItems;
438
    }
439
440
    /**
441
     * Get number of in and outgoing links and redirects to this page.
442
     * @return string[] Counts with the keys 'links_ext_count', 'links_out_count',
443
     *                  'links_in_count' and 'redirects_count'
444
     */
445 2
    public function countLinksAndRedirects()
446
    {
447 2
        return $this->getRepository()->countLinksAndRedirects($this);
448
    }
449
450
    /**
451
     * Get the sum of pageviews for the given page and timeframe.
452
     * @param string|DateTime $start In the format YYYYMMDD
453
     * @param string|DateTime $end In the format YYYYMMDD
454
     * @return int
455
     */
456 1
    public function getPageviews($start, $end)
457
    {
458
        try {
459 1
            $pageviews = $this->getRepository()->getPageviews($this, $start, $end);
460
        } catch (\GuzzleHttp\Exception\ClientException $e) {
461
            // 404 means zero pageviews
462
            return 0;
463
        }
464
465 1
        return array_sum(array_map(function ($item) {
466 1
            return (int) $item['views'];
467 1
        }, $pageviews['items']));
468
    }
469
470
    /**
471
     * Get the sum of pageviews over the last N days
472
     * @param int $days Default 30
473
     * @return int Number of pageviews
474
     */
475 1
    public function getLastPageviews($days = 30)
476
    {
477 1
        $start = date('Ymd', strtotime("-$days days"));
478 1
        $end = date('Ymd');
479 1
        return $this->getPageviews($start, $end);
480
    }
481
482
    /**
483
     * Is the page the project's Main Page?
484
     * @return bool
485
     */
486 1
    public function isMainPage()
487
    {
488 1
        return $this->getProject()->getMainPage() === $this->getTitle();
489
    }
490
}
491