Passed
Push — master ( b13246...0cce72 )
by MusikAnimal
05:03
created

Page::countWikidataItems()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 0
dl 0
loc 8
ccs 5
cts 6
cp 0.8333
crap 3.0416
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the Page class.
4
 */
5
6
namespace Xtools;
7
8
/**
9
 * A Page is a single wiki page in one project.
10
 */
11
class Page extends Model
12
{
13
14
    /** @var Project The project that this page belongs to. */
15
    protected $project;
16
17
    /** @var string The page name as provided at instantiation. */
18
    protected $unnormalizedPageName;
19
20
    /** @var string[] Metadata about this page. */
21
    protected $pageInfo;
22
23
    /** @var string[] Revision history of this page. */
24
    protected $revisions;
25
26
    /** @var int Number of revisions for this page. */
27
    protected $numRevisions;
28
29
    /** @var string[] List of Wikidata sitelinks for this page. */
30
    protected $wikidataItems;
31
32
    /** @var int Number of Wikidata sitelinks for this page. */
33
    protected $numWikidataItems;
34
35
    /**
36
     * Page constructor.
37
     * @param Project $project
38
     * @param string $pageName
39
     */
40 30
    public function __construct(Project $project, $pageName)
41
    {
42 30
        $this->project = $project;
43 30
        $this->unnormalizedPageName = $pageName;
44 30
    }
45
46
    /**
47
     * Unique identifier for this Page, to be used in cache keys.
48
     * Use of md5 ensures the cache key does not contain reserved characters.
49
     * @see Repository::getCacheKey()
50
     * @return string
51
     */
52
    public function getCacheKey()
53
    {
54
        return md5($this->getId());
55
    }
56
57
    /**
58
     * Get basic information about this page from the repository.
59
     * @return \string[]
60
     */
61 7
    protected function getPageInfo()
62
    {
63 7
        if (empty($this->pageInfo)) {
64 7
            $this->pageInfo = $this->getRepository()
65 7
                    ->getPageInfo($this->project, $this->unnormalizedPageName);
1 ignored issue
show
Bug introduced by
The method getPageInfo() does not exist on Xtools\Repository. It seems like you code against a sub-type of Xtools\Repository such as Xtools\PagesRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

65
                    ->/** @scrutinizer ignore-call */ getPageInfo($this->project, $this->unnormalizedPageName);
Loading history...
66
        }
67 7
        return $this->pageInfo;
68
    }
69
70
    /**
71
     * Get the page's title.
72
     * @param bool $useUnnormalized Use the unnormalized page title to avoid an
73
     *    API call. This should be used only if you fetched the page title via
74
     *    other means (SQL query), and is not from user input alone.
75
     * @return string
76
     */
77 3
    public function getTitle($useUnnormalized = false)
78
    {
79 3
        if ($useUnnormalized) {
80 1
            return $this->unnormalizedPageName;
81
        }
82 3
        $info = $this->getPageInfo();
83 3
        return isset($info['title']) ? $info['title'] : $this->unnormalizedPageName;
84
    }
85
86
    /**
87
     * Get the page's title without the namespace.
88
     * @return string
89
     */
90 1
    public function getTitleWithoutNamespace()
91
    {
92 1
        $info = $this->getPageInfo();
93 1
        $title = isset($info['title']) ? $info['title'] : $this->unnormalizedPageName;
94 1
        $nsName = $this->getNamespaceName();
95 1
        return str_replace($nsName . ':', '', $title);
96
    }
97
98
    /**
99
     * Get this page's database ID.
100
     * @return int
101
     */
102 2
    public function getId()
103
    {
104 2
        $info = $this->getPageInfo();
105 2
        return isset($info['pageid']) ? $info['pageid'] : null;
106
    }
107
108
    /**
109
     * Get this page's length in bytes.
110
     * @return int
111
     */
112 1
    public function getLength()
113
    {
114 1
        $info = $this->getPageInfo();
115 1
        return isset($info['length']) ? $info['length'] : null;
116
    }
117
118
    /**
119
     * Get HTML for the stylized display of the title.
120
     * The text will be the same as Page::getTitle().
121
     * @return string
122
     */
123 1
    public function getDisplayTitle()
124
    {
125 1
        $info = $this->getPageInfo();
126 1
        if (isset($info['displaytitle'])) {
127 1
            return $info['displaytitle'];
128
        }
129 1
        return $this->getTitle();
130
    }
131
132
    /**
133
     * Get the full URL of this page.
134
     * @return string
135
     */
136 1
    public function getUrl()
137
    {
138 1
        $info = $this->getPageInfo();
139 1
        return isset($info['fullurl']) ? $info['fullurl'] : null;
140
    }
141
142
    /**
143
     * Get the numerical ID of the namespace of this page.
144
     * @return int
145
     */
146 2
    public function getNamespace()
147
    {
148 2
        $info = $this->getPageInfo();
149 2
        return isset($info['ns']) ? $info['ns'] : null;
150
    }
151
152
    /**
153
     * Get the name of the namespace of this page.
154
     * @return string
155
     */
156 1
    public function getNamespaceName()
157
    {
158 1
        $info = $this->getPageInfo();
159 1
        return isset($info['ns'])
160 1
            ? $this->getProject()->getNamespaces()[$info['ns']]
161 1
            : null;
162
    }
163
164
    /**
165
     * Get the number of page watchers.
166
     * @return int
167
     */
168 1
    public function getWatchers()
169
    {
170 1
        $info = $this->getPageInfo();
171 1
        return isset($info['watchers']) ? $info['watchers'] : null;
172
    }
173
174
    /**
175
     * Whether or not this page exists.
176
     * @return bool
177
     */
178 1
    public function exists()
179
    {
180 1
        $info = $this->getPageInfo();
181 1
        return !isset($info['missing']) && !isset($info['invalid']);
182
    }
183
184
    /**
185
     * Get the Project to which this page belongs.
186
     * @return Project
187
     */
188 13
    public function getProject()
189
    {
190 13
        return $this->project;
191
    }
192
193
    /**
194
     * Get the language code for this page.
195
     * If not set, the language code for the project is returned.
196
     * @return string
197
     */
198
    public function getLang()
199
    {
200
        $info = $this->getPageInfo();
201
        if (isset($info['pagelanguage'])) {
202
            return $info['pagelanguage'];
203
        } else {
204
            return $this->getProject()->getLang();
205
        }
206
    }
207
208
    /**
209
     * Get the Wikidata ID of this page.
210
     * @return string
211
     */
212 2
    public function getWikidataId()
213
    {
214 2
        $info = $this->getPageInfo();
215 2
        if (isset($info['pageprops']['wikibase_item'])) {
216 1
            return $info['pageprops']['wikibase_item'];
217
        } else {
218 1
            return null;
219
        }
220
    }
221
222
    /**
223
     * Get the number of revisions the page has.
224
     * @param User $user Optionally limit to those of this user.
225
     * @return int
226
     */
227 4
    public function getNumRevisions(User $user = null)
228
    {
229
        // If a user is given, we will not cache the result via instance variable.
230 4
        if ($user !== null) {
231
            return (int) $this->getRepository()->getNumRevisions($this, $user);
1 ignored issue
show
Bug introduced by
The method getNumRevisions() does not exist on Xtools\Repository. It seems like you code against a sub-type of Xtools\Repository such as Xtools\PagesRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

231
            return (int) $this->getRepository()->/** @scrutinizer ignore-call */ getNumRevisions($this, $user);
Loading history...
232
        }
233
234
        // Return cached value, if present.
235 4
        if ($this->numRevisions !== null) {
236
            return $this->numRevisions;
237
        }
238
239
        // Otherwise, return the count of all revisions if already present.
240 4
        if ($this->revisions !== null) {
241
            $this->numRevisions = count($this->revisions);
242
        } else {
243
            // Otherwise do a COUNT in the event fetching all revisions is not desired.
244 4
            $this->numRevisions = (int) $this->getRepository()->getNumRevisions($this);
245
        }
246
247 4
        return $this->numRevisions;
248
    }
249
250
    /**
251
     * Get all edits made to this page.
252
     * @param User|null $user Specify to get only revisions by the given user.
253
     * @return array
254
     */
255
    public function getRevisions(User $user = null)
256
    {
257
        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...
258
            return $this->revisions;
259
        }
260
261
        $this->revisions = $this->getRepository()->getRevisions($this, $user);
1 ignored issue
show
Bug introduced by
The method getRevisions() does not exist on Xtools\Repository. It seems like you code against a sub-type of Xtools\Repository such as Xtools\PagesRepository or Xtools\EditCounterRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

261
        $this->revisions = $this->getRepository()->/** @scrutinizer ignore-call */ getRevisions($this, $user);
Loading history...
262
263
        return $this->revisions;
264
    }
265
266
    /**
267
     * Get the full page wikitext.
268
     * @return string|null Null if nothing was found.
269
     */
270 1
    public function getWikitext()
271
    {
272 1
        $content = $this->getRepository()->getPagesWikitext(
1 ignored issue
show
Bug introduced by
The method getPagesWikitext() does not exist on Xtools\Repository. It seems like you code against a sub-type of Xtools\Repository such as Xtools\PagesRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

272
        $content = $this->getRepository()->/** @scrutinizer ignore-call */ getPagesWikitext(
Loading history...
273 1
            $this->getProject(),
274 1
            [ $this->getTitle() ]
275
        );
276
277 1
        return isset($content[$this->getTitle()])
278 1
            ? $content[$this->getTitle()]
279 1
            : null;
280
    }
281
282
    /**
283
     * Get the statement for a single revision, so that you can iterate row by row.
284
     * @see PagesRepository::getRevisionsStmt()
285
     * @param User|null $user Specify to get only revisions by the given user.
286
     * @param int $limit Max number of revisions to process.
287
     * @param int $numRevisions Number of revisions, if known. This is used solely to determine the
288
     *   OFFSET if we are given a $limit. If $limit is set and $numRevisions is not set, a
289
     *   separate query is ran to get the nuber of revisions.
290
     * @return Doctrine\DBAL\Driver\PDOStatement
0 ignored issues
show
Bug introduced by
The type Xtools\Doctrine\DBAL\Driver\PDOStatement was not found. Did you mean Doctrine\DBAL\Driver\PDOStatement? If so, make sure to prefix the type with \.
Loading history...
291
     */
292
    public function getRevisionsStmt(User $user = null, $limit = null, $numRevisions = null)
293
    {
294
        // If we have a limit, we need to know the total number of revisions so that PagesRepo
295
        // will properly set the OFFSET. See PagesRepository::getRevisionsStmt() for more info.
296
        if (isset($limit) && $numRevisions === null) {
297
            $numRevisions = $this->getNumRevisions($user);
298
        }
299
        return $this->getRepository()->getRevisionsStmt($this, $user, $limit, $numRevisions);
1 ignored issue
show
Bug introduced by
The method getRevisionsStmt() does not exist on Xtools\Repository. It seems like you code against a sub-type of Xtools\Repository such as Xtools\PagesRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

299
        return $this->getRepository()->/** @scrutinizer ignore-call */ getRevisionsStmt($this, $user, $limit, $numRevisions);
Loading history...
300
    }
301
302
    /**
303
     * Get various basic info used in the API, including the
304
     *   number of revisions, unique authors, initial author
305
     *   and edit count of the initial author.
306
     * This is combined into one query for better performance.
307
     * Caching is intentionally disabled, because using the gadget,
308
     *   this will get hit for a different page constantly, where
309
     *   the likelihood of cache benefiting us is slim.
310
     * @return string[]
311
     */
312
    public function getBasicEditingInfo()
313
    {
314
        return $this->getRepository()->getBasicEditingInfo($this);
1 ignored issue
show
Bug introduced by
The method getBasicEditingInfo() does not exist on Xtools\Repository. It seems like you code against a sub-type of Xtools\Repository such as Xtools\PagesRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

314
        return $this->getRepository()->/** @scrutinizer ignore-call */ getBasicEditingInfo($this);
Loading history...
315
    }
316
317
    /**
318
     * Get assessments of this page
319
     * @see https://www.mediawiki.org/wiki/Extension:PageAssessments
320
     * @return string[]|false `false` if unsupported, or array in the format of:
321
     *         [
322
     *             'assessment' => 'C', // overall assessment
323
     *             'wikiprojects' => [
324
     *                 'Biography' => [
325
     *                     'assessment' => 'C',
326
     *                     'badge' => 'url',
327
     *                 ],
328
     *                 ...
329
     *             ],
330
     *             'wikiproject_prefix' => 'Wikipedia:WikiProject_',
331
     *         ]
332
     */
333 1
    public function getAssessments()
334
    {
335 1
        if (!$this->project->hasPageAssessments() || $this->getNamespace() !== 0) {
336
            return false;
337
        }
338
339 1
        $projectDomain = $this->project->getDomain();
340 1
        $config = $this->project->getRepository()->getAssessmentsConfig($projectDomain);
1 ignored issue
show
Bug introduced by
The method getAssessmentsConfig() does not exist on Xtools\Repository. It seems like you code against a sub-type of Xtools\Repository such as Xtools\ProjectRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

340
        $config = $this->project->getRepository()->/** @scrutinizer ignore-call */ getAssessmentsConfig($projectDomain);
Loading history...
341 1
        $data = $this->getRepository()->getAssessments($this->project, [$this->getId()]);
1 ignored issue
show
Bug introduced by
The method getAssessments() does not exist on Xtools\Repository. It seems like you code against a sub-type of Xtools\Repository such as Xtools\PagesRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

341
        $data = $this->getRepository()->/** @scrutinizer ignore-call */ getAssessments($this->project, [$this->getId()]);
Loading history...
342
343
        // Set the default decorations for the overall quality assessment
344
        // This will be replaced with the first valid class defined for any WikiProject
345 1
        $overallQuality = $config['class']['Unknown'];
346 1
        $overallQuality['value'] = '???';
347 1
        $overallQuality['badge'] = $this->project->getAssessmentBadgeURL($overallQuality['badge']);
348
349 1
        $decoratedAssessments = [];
350
351 1
        foreach ($data as $assessment) {
352 1
            $classValue = $assessment['class'];
353
354
            // Use ??? as the presented value when the class is unknown or is not defined in the config
355 1
            if ($classValue === 'Unknown' || $classValue === '' || !isset($config['class'][$classValue])) {
356
                $classAttrs = $config['class']['Unknown'];
357
                $assessment['class']['value'] = '???';
358
                $assessment['class']['category'] = $classAttrs['category'];
359
                $assessment['class']['color'] = $classAttrs['color'];
360
                $assessment['class']['badge'] = "https://upload.wikimedia.org/wikipedia/commons/"
361
                    . $classAttrs['badge'];
362
            } else {
363 1
                $classAttrs = $config['class'][$classValue];
364 1
                $assessment['class'] = [
365 1
                    'value' => $classValue,
366 1
                    'color' => $classAttrs['color'],
367 1
                    'category' => $classAttrs['category'],
368
                ];
369
370
                // add full URL to badge icon
371 1
                if ($classAttrs['badge'] !== '') {
372 1
                    $assessment['class']['badge'] = $this->project->getAssessmentBadgeURL($classValue);
373
                }
374
            }
375
376 1
            if ($overallQuality['value'] === '???') {
377 1
                $overallQuality = $assessment['class'];
378 1
                $overallQuality['category'] = $classAttrs['category'];
379
            }
380
381 1
            $importanceValue = $assessment['importance'];
382 1
            $importanceUnknown = $importanceValue === 'Unknown' || $importanceValue === '';
383
384 1
            if ($importanceUnknown || !isset($config['importance'][$importanceValue])) {
385
                $importanceAttrs = $config['importance']['Unknown'];
386
                $assessment['importance'] = $importanceAttrs;
387
                $assessment['importance']['value'] = '???';
388
                $assessment['importance']['category'] = $importanceAttrs['category'];
389
            } else {
390 1
                $importanceAttrs = $config['importance'][$importanceValue];
391 1
                $assessment['importance'] = [
392 1
                    'value' => $importanceValue,
393 1
                    'color' => $importanceAttrs['color'],
394 1
                    'weight' => $importanceAttrs['weight'], // numerical weight for sorting purposes
395 1
                    'category' => $importanceAttrs['category'],
396
                ];
397
            }
398
399 1
            $decoratedAssessments[$assessment['wikiproject']] = $assessment;
400
        }
401
402
        return [
403 1
            'assessment' => $overallQuality,
404 1
            'wikiprojects' => $decoratedAssessments,
405 1
            'wikiproject_prefix' => $config['wikiproject_prefix']
406
        ];
407
    }
408
409
    /**
410
     * Get CheckWiki errors for this page
411
     * @return string[] See getErrors() for format
412
     */
413 1
    public function getCheckWikiErrors()
414
    {
415 1
        return $this->getRepository()->getCheckWikiErrors($this);
1 ignored issue
show
Bug introduced by
The method getCheckWikiErrors() does not exist on Xtools\Repository. It seems like you code against a sub-type of Xtools\Repository such as Xtools\PagesRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

415
        return $this->getRepository()->/** @scrutinizer ignore-call */ getCheckWikiErrors($this);
Loading history...
416
    }
417
418
    /**
419
     * Get Wikidata errors for this page
420
     * @return string[] See getErrors() for format
421
     */
422 1
    public function getWikidataErrors()
423
    {
424
        // FIXME: wikidatawiki_p.wb_entity_per_page is no more.
425
        // Figure out how to check for missing description, etc.
426 1
        return [];
427
428
        // $errors = [];
429
430
        // if (empty($this->getWikidataId())) {
431
        //     return [];
432
        // }
433
434
        // $wikidataInfo = $this->getRepository()->getWikidataInfo($this);
435
436
        // $terms = array_map(function ($entry) {
437
        //     return $entry['term'];
438
        // }, $wikidataInfo);
439
440
        // $lang = $this->getLang();
441
442
        // if (!in_array('label', $terms)) {
443
        //     $errors[] = [
444
        //         'prio' => 2,
445
        //         'name' => 'Wikidata',
446
        //         'notice' => "Label for language <em>$lang</em> is missing", // FIXME: i18n
447
        //         'explanation' => "See: <a target='_blank' " .
448
        //             "href='//www.wikidata.org/wiki/Help:Label'>Help:Label</a>",
449
        //     ];
450
        // }
451
452
        // if (!in_array('description', $terms)) {
453
        //     $errors[] = [
454
        //         'prio' => 3,
455
        //         'name' => 'Wikidata',
456
        //         'notice' => "Description for language <em>$lang</em> is missing", // FIXME: i18n
457
        //         'explanation' => "See: <a target='_blank' " .
458
        //             "href='//www.wikidata.org/wiki/Help:Description'>Help:Description</a>",
459
        //     ];
460
        // }
461
462
        // return $errors;
463
    }
464
465
    /**
466
     * Get Wikidata and CheckWiki errors, if present
467
     * @return string[] List of errors in the format:
468
     *    [[
469
     *         'prio' => int,
470
     *         'name' => string,
471
     *         'notice' => string (HTML),
472
     *         'explanation' => string (HTML)
473
     *     ], ... ]
474
     */
475 1
    public function getErrors()
476
    {
477
        // Includes label and description
478 1
        $wikidataErrors = $this->getWikidataErrors();
479
480 1
        $checkWikiErrors = $this->getCheckWikiErrors();
481
482 1
        return array_merge($wikidataErrors, $checkWikiErrors);
483
    }
484
485
    /**
486
     * Get all wikidata items for the page, not just languages of sister projects
487
     * @return int Number of records.
488
     */
489 1
    public function getWikidataItems()
490
    {
491 1
        if (!is_array($this->wikidataItems)) {
492 1
            $this->wikidataItems = $this->getRepository()->getWikidataItems($this);
493
        }
494 1
        return $this->wikidataItems;
495
    }
496
497
    /**
498
     * Count wikidata items for the page, not just languages of sister projects
499
     * @return int Number of records.
500
     */
501 2
    public function countWikidataItems()
502
    {
503 2
        if (is_array($this->wikidataItems)) {
504
            $this->numWikidataItems = count($this->wikidataItems);
505 2
        } elseif ($this->numWikidataItems === null) {
506 2
            $this->numWikidataItems = $this->getRepository()->countWikidataItems($this);
507
        }
508 2
        return $this->numWikidataItems;
509
    }
510
511
    /**
512
     * Get number of in and outgoing links and redirects to this page.
513
     * @return string[] Counts with the keys 'links_ext_count', 'links_out_count',
514
     *                  'links_in_count' and 'redirects_count'
515
     */
516 2
    public function countLinksAndRedirects()
517
    {
518 2
        return $this->getRepository()->countLinksAndRedirects($this);
1 ignored issue
show
Bug introduced by
The method countLinksAndRedirects() does not exist on Xtools\Repository. It seems like you code against a sub-type of Xtools\Repository such as Xtools\PagesRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

518
        return $this->getRepository()->/** @scrutinizer ignore-call */ countLinksAndRedirects($this);
Loading history...
519
    }
520
521
    /**
522
     * Get the sum of pageviews for the given page and timeframe.
523
     * @param string|DateTime $start In the format YYYYMMDD
0 ignored issues
show
Bug introduced by
The type Xtools\DateTime was not found. Did you mean DateTime? If so, make sure to prefix the type with \.
Loading history...
524
     * @param string|DateTime $end In the format YYYYMMDD
525
     * @return string[]
526
     */
527 1
    public function getPageviews($start, $end)
528
    {
529
        try {
530 1
            $pageviews = $this->getRepository()->getPageviews($this, $start, $end);
1 ignored issue
show
Bug introduced by
The method getPageviews() does not exist on Xtools\Repository. It seems like you code against a sub-type of Xtools\Repository such as Xtools\PagesRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

530
            $pageviews = $this->getRepository()->/** @scrutinizer ignore-call */ getPageviews($this, $start, $end);
Loading history...
531
        } catch (\GuzzleHttp\Exception\ClientException $e) {
532
            // 404 means zero pageviews
533
            return 0;
534
        }
535
536 1
        return array_sum(array_map(function ($item) {
537 1
            return (int) $item['views'];
538 1
        }, $pageviews['items']));
539
    }
540
541
    /**
542
     * Get the sum of pageviews over the last N days
543
     * @param int $days Default 30
544
     * @return int Number of pageviews
545
     */
546 1
    public function getLastPageviews($days = 30)
547
    {
548 1
        $start = date('Ymd', strtotime("-$days days"));
549 1
        $end = date('Ymd');
550 1
        return $this->getPageviews($start, $end);
551
    }
552
553
    /**
554
     * Is the page the project's Main Page?
555
     * @return bool
556
     */
557 1
    public function isMainPage()
558
    {
559 1
        return $this->getProject()->getMainPage() === $this->getTitle();
560
    }
561
}
562