Passed
Pull Request — master (#158)
by MusikAnimal
04:58
created

Edit::getPage()   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 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the Edit class.
4
 */
5
6
namespace Xtools;
7
8
use Xtools\User;
9
use Symfony\Component\DependencyInjection\Container;
10
use DateTime;
11
12
/**
13
 * An Edit is a single edit to a page on one project.
14
 */
15
class Edit extends Model
16
{
17
18
    /** @var Page the page associated with this edit */
19
    protected $page;
20
21
    /** @var int ID of the revision */
22
    protected $id;
23
24
    /** @var DateTime Timestamp of the revision */
25
    protected $timestamp;
26
27
    /** @var bool Whether or not this edit was a minor edit */
28
    protected $minor;
29
30
    /** @var int|string|null Length of the page as of this edit, in bytes */
31
    protected $length;
32
33
    /** @var int|string|null The diff size of this edit */
34
    protected $lengthChange;
35
36
    /** @var User - User object of who made the edit */
37
    protected $user;
38
39
    /** @var string The edit summary */
40
    protected $comment;
41
42
    /** @var string The SHA-1 of the wikitext as of the revision. */
43
    protected $sha;
44
45
    /** @var bool Whether this edit was later reverted. */
46
    protected $reverted;
47
48
    /**
49
     * Edit constructor.
50
     * @param Page $page
51
     * @param string[] $attrs Attributes, as retrieved by PageRepository::getRevisions()
52
     */
53 14
    public function __construct(Page $page, $attrs)
54
    {
55 14
        $this->page = $page;
56
57
        // Copy over supported attributes
58 14
        $this->id = (int) $attrs['id'];
59
60
        // Allow DateTime or string (latter assumed to be of format YmdHis)
61 14
        if ($attrs['timestamp'] instanceof DateTime) {
0 ignored issues
show
introduced by
The condition $attrs['timestamp'] instanceof DateTime can never be true since $attrs['timestamp'] is never a sub-type of DateTime.
Loading history...
62
            $this->timestamp = $attrs['timestamp'];
63
        } else {
64 14
            $this->timestamp = DateTime::createFromFormat('YmdHis', $attrs['timestamp']);
0 ignored issues
show
Documentation Bug introduced by
It seems like DateTime::createFromForm...', $attrs['timestamp']) can also be of type false. However, the property $timestamp is declared as type DateTime. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
65
        }
66
67 14
        $this->minor = $attrs['minor'] === '1';
68
69
        // NOTE: Do not type cast into an integer. Null values are
70
        //   our indication that the revision was revision-deleted.
71 14
        $this->length = $attrs['length'];
72 14
        $this->lengthChange = $attrs['length_change'];
73
74 14
        $this->user = new User($attrs['username']);
75 14
        $this->comment = $attrs['comment'];
76
77 14
        if (isset($attrs['rev_sha1']) || isset($attrs['sha'])) {
78 4
            $this->sha = isset($attrs['rev_sha1'])
79 4
                ? $attrs['rev_sha1']
80
                : $attrs['sha'];
81
        }
82
83
        // This can be passed in to save as a property on the Edit instance.
84
        // Note that the Edit class knows nothing about it's value, and
85
        // is not capable of detecting whether the given edit was reverted.
86 14
        $this->reverted = isset($attrs['reverted']) ? (bool)$attrs['reverted'] : null;
87 14
    }
88
89
    /**
90
     * Unique identifier for this Edit, to be used in cache keys.
91
     * @see Repository::getCacheKey()
92
     * @return string
93
     */
94
    public function getCacheKey()
95
    {
96
        return $this->id;
97
    }
98
99
    /**
100
     * Get the page to which this edit belongs.
101
     * @return Page
102
     */
103 12
    public function getPage()
104
    {
105 12
        return $this->page;
106
    }
107
108
    /**
109
     * ID of the edit.
110
     * @return int
111
     */
112 6
    public function getId()
113
    {
114 6
        return $this->id;
115
    }
116
117
    /**
118
     * Get the edit's timestamp.
119
     * @return DateTime
120
     */
121 6
    public function getTimestamp()
122
    {
123 6
        return $this->timestamp;
124
    }
125
126
    /**
127
     * Year the revision was made.
128
     * @return string
129
     */
130 5
    public function getYear()
131
    {
132 5
        return $this->timestamp->format('Y');
133
    }
134
135
    /**
136
     * Get the numeric representation of the month the revision was made, with leading zeros.
137
     * @return string
138
     */
139 5
    public function getMonth()
140
    {
141 5
        return $this->timestamp->format('m');
142
    }
143
144
    /**
145
     * Whether or not this edit was a minor edit.
146
     * @return bool
147
     */
148 6
    public function getMinor()
149
    {
150 6
        return $this->minor;
151
    }
152
153
    /**
154
     * Alias of getMinor()
155
     * @return bool Whether or not this edit was a minor edit
156
     */
157 6
    public function isMinor()
158
    {
159 6
        return $this->getMinor();
160
    }
161
162
    /**
163
     * Length of the page as of this edit, in bytes.
164
     * @see Edit::getSize() Edit::getSize() for the size <em>change</em>.
165
     * @return int
166
     */
167 5
    public function getLength()
168
    {
169 5
        return $this->length;
170
    }
171
172
    /**
173
     * The diff size of this edit.
174
     * @return int Signed length change in bytes.
175
     */
176 5
    public function getSize()
177
    {
178 5
        return $this->lengthChange;
179
    }
180
181
    /**
182
     * Alias of getSize()
183
     * @return int The diff size of this edit
184
     */
185 1
    public function getLengthChange()
186
    {
187 1
        return $this->getSize();
188
    }
189
190
    /**
191
     * Get the user who made the edit.
192
     * @return User
193
     */
194 6
    public function getUser()
195
    {
196 6
        return $this->user;
197
    }
198
199
    /**
200
     * Get the edit summary.
201
     * @return string
202
     */
203 1
    public function getComment()
204
    {
205 1
        return $this->comment;
206
    }
207
208
    /**
209
     * Get the edit summary (alias of Edit::getComment()).
210
     * @return string
211
     */
212 1
    public function getSummary()
213
    {
214 1
        return $this->getComment();
215
    }
216
217
    /**
218
     * Get the SHA-1 of the revision.
219
     * @return string
220
     */
221 4
    public function getSha()
222
    {
223 4
        return $this->sha;
224
    }
225
226
    /**
227
     * Was this edit reported as having been reverted?
228
     * The value for this is merely passed in from precomputed data.
229
     * @return bool
230
     */
231 1
    public function isReverted()
232
    {
233 1
        return $this->reverted;
234
    }
235
236
    /**
237
     * Get edit summary as 'wikified' HTML markup
238
     * @param bool $useUnnormalizedPageTitle Use the unnormalized page title to avoid
239
     *   an API call. This should be used only if you fetched the page title via other
240
     *   means (SQL query), and is not from user input alone.
241
     * @return string Safe HTML
242
     */
243 1
    public function getWikifiedComment($useUnnormalizedPageTitle = false)
244
    {
245 1
        return self::wikifyString(
246 1
            $this->getSummary(),
247 1
            $this->getProject(),
248 1
            $this->page,
249 1
            $useUnnormalizedPageTitle
250
        );
251
    }
252
253
    /**
254
     * Public static method to wikify a summary, can be used on any arbitrary string.
255
     * Does NOT support section links unless you specify a page.
256
     * @param string $summary
257
     * @param Project $project
258
     * @param Page $page
259
     * @param bool $useUnnormalizedPageTitle Use the unnormalized page title to avoid
260
     *   an API call. This should be used only if you fetched the page title via other
261
     *   means (SQL query), and is not from user input alone.
262
     * @static
263
     * @return string
264
     */
265 2
    public static function wikifyString(
266
        $summary,
267
        Project $project,
268
        Page $page = null,
269
        $useUnnormalizedPageTitle = false
270
    ) {
271 2
        $summary = htmlspecialchars($summary, ENT_NOQUOTES);
272 2
        $sectionMatch = null;
273 2
        $isSection = preg_match_all("/^\/\* (.*?) \*\//", $summary, $sectionMatch);
274
275 2
        if ($isSection && isset($page)) {
276
            $pageUrl = $project->getUrl(false) . str_replace(
277
                '$1',
278
                $page->getTitle($useUnnormalizedPageTitle),
279
                $project->getArticlePath()
280
            );
281
            $sectionTitle = $sectionMatch[1][0];
282
283
            // Must have underscores for the link to properly go to the section.
284
            $sectionTitleLink = htmlspecialchars(str_replace(' ', '_', $sectionTitle));
285
286
            $sectionWikitext = "<a target='_blank' href='$pageUrl#$sectionTitleLink'>&rarr;</a>" .
287
                "<em class='text-muted'>" . htmlspecialchars($sectionTitle) . ":</em> ";
288
            $summary = str_replace($sectionMatch[0][0], $sectionWikitext, $summary);
289
        }
290
291 2
        $linkMatch = null;
292
293 2
        while (preg_match_all("/\[\[:?(.*?)\]\]/", $summary, $linkMatch)) {
294 1
            $wikiLinkParts = explode('|', $linkMatch[1][0]);
295 1
            $wikiLinkPath = htmlspecialchars($wikiLinkParts[0]);
296 1
            $wikiLinkText = htmlspecialchars(
297 1
                isset($wikiLinkParts[1]) ? $wikiLinkParts[1] : $wikiLinkPath
298
            );
299
300
            // Use normalized page title (underscored, capitalized).
301 1
            $pageUrl = $project->getUrl(false) . str_replace(
302 1
                '$1',
303 1
                ucfirst(str_replace(' ', '_', $wikiLinkPath)),
304 1
                $project->getArticlePath()
305
            );
306
307 1
            $link = "<a target='_blank' href='$pageUrl'>$wikiLinkText</a>";
308 1
            $summary = str_replace($linkMatch[0][0], $link, $summary);
309
        }
310
311 2
        return $summary;
312
    }
313
314
    /**
315
     * Get edit summary as 'wikified' HTML markup (alias of Edit::getWikifiedSummary()).
316
     * @return string
317
     */
318 1
    public function getWikifiedSummary()
319
    {
320 1
        return $this->getWikifiedComment();
321
    }
322
323
    /**
324
     * Get the project this edit was made on
325
     * @return Project
326
     */
327 12
    public function getProject()
328
    {
329 12
        return $this->getPage()->getProject();
330
    }
331
332
    /**
333
     * Get the full URL to the diff of the edit
334
     * @return string
335
     */
336 1
    public function getDiffUrl()
337
    {
338 1
        $project = $this->getProject();
339 1
        $path = str_replace('$1', 'Special:Diff/' . $this->id, $project->getArticlePath());
340 1
        return rtrim($project->getUrl(), '/') . $path;
341
    }
342
343
    /**
344
     * Get the full permanent URL to the page at the time of the edit
345
     * @return string
346
     */
347 1
    public function getPermaUrl()
348
    {
349 1
        $project = $this->getProject();
350 1
        $path = str_replace('$1', 'Special:PermaLink/' . $this->id, $project->getArticlePath());
351 1
        return rtrim($project->getUrl(), '/') . $path;
352
    }
353
354
    /**
355
     * Was the edit a revert, based on the edit summary?
356
     * @param Container $container The DI container.
357
     * @return bool
358
     */
359 5
    public function isRevert(Container $container)
360
    {
361 5
        $automatedEditsHelper = $container->get('app.automated_edits_helper');
362 5
        return $automatedEditsHelper->isRevert($this->comment, $this->getProject()->getDomain());
363
    }
364
365
    /**
366
     * Get the name of the tool that was used to make this edit.
367
     * @param Container $container The DI container.
368
     * @return string|false The name of the tool that was used to make the edit
369
     */
370 7
    public function getTool(Container $container)
371
    {
372 7
        $automatedEditsHelper = $container->get('app.automated_edits_helper');
373 7
        return $automatedEditsHelper->getTool($this->comment, $this->getProject()->getDomain());
374
    }
375
376
    /**
377
     * Was the edit (semi-)automated, based on the edit summary?
378
     * @param  Container $container [description]
379
     * @return bool
380
     */
381 2
    public function isAutomated(Container $container)
382
    {
383 2
        return (bool) $this->getTool($container);
384
    }
385
386
    /**
387
     * Was the edit made by a logged out user?
388
     * @return bool
389
     */
390 5
    public function isAnon()
391
    {
392 5
        return $this->getUser()->isAnon();
393
    }
394
}
395