Total Complexity | 52 |
Total Lines | 490 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like Edit often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Edit, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
15 | class Edit extends Model |
||
16 | { |
||
17 | public const DELETED_TEXT = 1; |
||
18 | public const DELETED_COMMENT = 2; |
||
19 | public const DELETED_USER = 4; |
||
20 | public const DELETED_RESTRICTED = 8; |
||
21 | |||
22 | protected UserRepository $userRepo; |
||
23 | |||
24 | /** @var int ID of the revision */ |
||
25 | protected int $id; |
||
26 | |||
27 | /** @var DateTime Timestamp of the revision */ |
||
28 | protected DateTime $timestamp; |
||
29 | |||
30 | /** @var bool Whether or not this edit was a minor edit */ |
||
31 | protected bool $minor; |
||
32 | |||
33 | /** @var int|null Length of the page as of this edit, in bytes */ |
||
34 | protected ?int $length; |
||
35 | |||
36 | /** @var int|null The diff size of this edit */ |
||
37 | protected ?int $lengthChange; |
||
38 | |||
39 | /** @var string The edit summary */ |
||
40 | protected string $comment; |
||
41 | |||
42 | /** @var string|null The SHA-1 of the wikitext as of the revision. */ |
||
43 | protected ?string $sha = null; |
||
44 | |||
45 | /** @var bool|null Whether this edit was later reverted. */ |
||
46 | protected ?bool $reverted; |
||
47 | |||
48 | /** @var int Deletion status of the revision. */ |
||
49 | protected int $deleted; |
||
50 | |||
51 | /** |
||
52 | * Edit constructor. |
||
53 | * @param EditRepository $repository |
||
54 | * @param UserRepository $userRepo |
||
55 | * @param Page $page |
||
56 | * @param string[] $attrs Attributes, as retrieved by PageRepository::getRevisions() |
||
57 | */ |
||
58 | public function __construct(EditRepository $repository, UserRepository $userRepo, Page $page, array $attrs = []) |
||
59 | { |
||
60 | $this->repository = $repository; |
||
61 | $this->userRepo = $userRepo; |
||
62 | $this->page = $page; |
||
63 | |||
64 | // Copy over supported attributes |
||
65 | $this->id = isset($attrs['id']) ? (int)$attrs['id'] : (int)$attrs['rev_id']; |
||
66 | |||
67 | // Allow DateTime or string (latter assumed to be of format YmdHis) |
||
68 | if ($attrs['timestamp'] instanceof DateTime) { |
||
69 | $this->timestamp = $attrs['timestamp']; |
||
70 | } else { |
||
71 | $this->timestamp = DateTime::createFromFormat('YmdHis', $attrs['timestamp']); |
||
72 | } |
||
73 | |||
74 | $this->deleted = (int)($attrs['rev_deleted'] ?? 0); |
||
75 | |||
76 | if (($this->deleted & self::DELETED_USER) || ($this->deleted & self::DELETED_RESTRICTED)) { |
||
77 | $this->user = null; |
||
78 | } else { |
||
79 | $this->user = $attrs['user'] ?? ($attrs['username'] ? new User($this->userRepo, $attrs['username']) : null); |
||
80 | } |
||
81 | |||
82 | $this->minor = 1 === (int)$attrs['minor']; |
||
83 | $this->length = isset($attrs['length']) ? (int)$attrs['length'] : null; |
||
84 | $this->lengthChange = isset($attrs['length_change']) ? (int)$attrs['length_change'] : null; |
||
85 | $this->comment = $attrs['comment'] ?? ''; |
||
86 | |||
87 | if (isset($attrs['rev_sha1']) || isset($attrs['sha'])) { |
||
88 | $this->sha = $attrs['rev_sha1'] ?? $attrs['sha']; |
||
89 | } |
||
90 | |||
91 | // This can be passed in to save as a property on the Edit instance. |
||
92 | // Note that the Edit class knows nothing about it's value, and |
||
93 | // is not capable of detecting whether the given edit was actually reverted. |
||
94 | $this->reverted = isset($attrs['reverted']) ? (bool)$attrs['reverted'] : null; |
||
95 | } |
||
96 | |||
97 | /** |
||
98 | * Get Edits given revision rows (JOINed on the page table). |
||
99 | * @param PageRepository $pageRepo |
||
100 | * @param EditRepository $editRepo |
||
101 | * @param UserRepository $userRepo |
||
102 | * @param Project $project |
||
103 | * @param User $user |
||
104 | * @param array $revs Each must contain 'page_title' and 'namespace'. |
||
105 | * @return Edit[] |
||
106 | */ |
||
107 | public static function getEditsFromRevs( |
||
108 | PageRepository $pageRepo, |
||
109 | EditRepository $editRepo, |
||
110 | UserRepository $userRepo, |
||
111 | Project $project, |
||
112 | User $user, |
||
113 | array $revs |
||
114 | ): array { |
||
115 | return array_map(function ($rev) use ($pageRepo, $editRepo, $userRepo, $project, $user) { |
||
116 | /** Page object to be passed to the Edit constructor. */ |
||
117 | $page = Page::newFromRow($pageRepo, $project, $rev); |
||
118 | $rev['user'] = $user; |
||
119 | |||
120 | return new self($editRepo, $userRepo, $page, $rev); |
||
121 | }, $revs); |
||
122 | } |
||
123 | |||
124 | /** |
||
125 | * Unique identifier for this Edit, to be used in cache keys. |
||
126 | * @see Repository::getCacheKey() |
||
127 | * @return string |
||
128 | */ |
||
129 | public function getCacheKey(): string |
||
130 | { |
||
131 | return (string)$this->id; |
||
132 | } |
||
133 | |||
134 | /** |
||
135 | * ID of the edit. |
||
136 | * @return int |
||
137 | */ |
||
138 | public function getId(): int |
||
139 | { |
||
140 | return $this->id; |
||
141 | } |
||
142 | |||
143 | /** |
||
144 | * Get the edit's timestamp. |
||
145 | * @return DateTime |
||
146 | */ |
||
147 | public function getTimestamp(): DateTime |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * Get the edit's timestamp as a UTC string, as with YYYY-MM-DDTHH:MM:SSZ |
||
154 | * @return string |
||
155 | */ |
||
156 | public function getUTCTimestamp(): string |
||
157 | { |
||
158 | return $this->getTimestamp()->format('Y-m-d\TH:i:s\Z'); |
||
159 | } |
||
160 | |||
161 | /** |
||
162 | * Year the revision was made. |
||
163 | * @return string |
||
164 | */ |
||
165 | public function getYear(): string |
||
166 | { |
||
167 | return $this->timestamp->format('Y'); |
||
168 | } |
||
169 | |||
170 | /** |
||
171 | * Get the numeric representation of the month the revision was made, with leading zeros. |
||
172 | * @return string |
||
173 | */ |
||
174 | public function getMonth(): string |
||
175 | { |
||
176 | return $this->timestamp->format('m'); |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * Whether or not this edit was a minor edit. |
||
181 | * @return bool |
||
182 | */ |
||
183 | public function getMinor(): bool |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * Alias of getMinor() |
||
190 | * @return bool Whether or not this edit was a minor edit |
||
191 | */ |
||
192 | public function isMinor(): bool |
||
193 | { |
||
194 | return $this->getMinor(); |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Length of the page as of this edit, in bytes. |
||
199 | * @see Edit::getSize() Edit::getSize() for the size <em>change</em>. |
||
200 | * @return int|null |
||
201 | */ |
||
202 | public function getLength(): ?int |
||
203 | { |
||
204 | return $this->length; |
||
205 | } |
||
206 | |||
207 | /** |
||
208 | * The diff size of this edit. |
||
209 | * @return int|null Signed length change in bytes. |
||
210 | */ |
||
211 | public function getSize(): ?int |
||
212 | { |
||
213 | return $this->lengthChange; |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * Alias of getSize() |
||
218 | * @return int|null The diff size of this edit |
||
219 | */ |
||
220 | public function getLengthChange(): ?int |
||
221 | { |
||
222 | return $this->getSize(); |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * Get the user who made the edit. |
||
227 | * @return User|null null can happen for instance if the username was suppressed. |
||
228 | */ |
||
229 | public function getUser(): ?User |
||
232 | } |
||
233 | |||
234 | /** |
||
235 | * Get the edit summary. |
||
236 | * @return string |
||
237 | */ |
||
238 | public function getComment(): string |
||
239 | { |
||
240 | return (string)$this->comment; |
||
241 | } |
||
242 | |||
243 | /** |
||
244 | * Get the edit summary (alias of Edit::getComment()). |
||
245 | * @return string |
||
246 | */ |
||
247 | public function getSummary(): string |
||
248 | { |
||
249 | return $this->getComment(); |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * Get the SHA-1 of the revision. |
||
254 | * @return string|null |
||
255 | */ |
||
256 | public function getSha(): ?string |
||
257 | { |
||
258 | return $this->sha; |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Was this edit reported as having been reverted? |
||
263 | * The value for this is merely passed in from precomputed data. |
||
264 | * @return bool|null |
||
265 | */ |
||
266 | public function isReverted(): ?bool |
||
267 | { |
||
268 | return $this->reverted; |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * Set the reverted property. |
||
273 | * @param bool $reverted |
||
274 | */ |
||
275 | public function setReverted(bool $reverted): void |
||
278 | } |
||
279 | |||
280 | /** |
||
281 | * Get deletion status of the revision. |
||
282 | * @return int |
||
283 | */ |
||
284 | public function getDeleted(): int |
||
285 | { |
||
286 | return $this->deleted; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Was the username deleted from public view? |
||
291 | * @return bool |
||
292 | */ |
||
293 | public function deletedUser(): bool |
||
296 | } |
||
297 | |||
298 | /** |
||
299 | * Was the edit summary deleted from public view? |
||
300 | * @return bool |
||
301 | */ |
||
302 | public function deletedSummary(): bool |
||
303 | { |
||
304 | return ($this->deleted & self::DELETED_COMMENT) > 0; |
||
305 | } |
||
306 | |||
307 | /** |
||
308 | * Get edit summary as 'wikified' HTML markup |
||
309 | * @param bool $useUnnormalizedPageTitle Use the unnormalized page title to avoid |
||
310 | * an API call. This should be used only if you fetched the page title via other |
||
311 | * means (SQL query), and is not from user input alone. |
||
312 | * @return string Safe HTML |
||
313 | */ |
||
314 | public function getWikifiedComment(bool $useUnnormalizedPageTitle = false): string |
||
321 | ); |
||
322 | } |
||
323 | |||
324 | /** |
||
325 | * Public static method to wikify a summary, can be used on any arbitrary string. |
||
326 | * Does NOT support section links unless you specify a page. |
||
327 | * @param string $summary |
||
328 | * @param Project $project |
||
329 | * @param Page|null $page |
||
330 | * @param bool $useUnnormalizedPageTitle Use the unnormalized page title to avoid |
||
331 | * an API call. This should be used only if you fetched the page title via other |
||
332 | * means (SQL query), and is not from user input alone. |
||
333 | * @static |
||
334 | * @return string |
||
335 | */ |
||
336 | public static function wikifyString( |
||
337 | string $summary, |
||
338 | Project $project, |
||
339 | ?Page $page = null, |
||
340 | bool $useUnnormalizedPageTitle = false |
||
341 | ): string { |
||
342 | $summary = htmlspecialchars(html_entity_decode($summary), ENT_NOQUOTES); |
||
343 | |||
344 | // First link raw URLs. Courtesy of https://stackoverflow.com/a/11641499/604142 |
||
345 | $summary = preg_replace( |
||
346 | '%\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))%s', |
||
347 | '<a target="_blank" href="$1">$1</a>', |
||
348 | $summary |
||
349 | ); |
||
350 | |||
351 | $sectionMatch = null; |
||
352 | $isSection = preg_match_all("/^\/\* (.*?) \*\//", $summary, $sectionMatch); |
||
353 | |||
354 | if ($isSection && isset($page)) { |
||
|
|||
355 | $pageUrl = $project->getUrlForPage($page->getTitle($useUnnormalizedPageTitle)); |
||
356 | $sectionTitle = $sectionMatch[1][0]; |
||
357 | |||
358 | // Must have underscores for the link to properly go to the section. |
||
359 | $sectionTitleLink = htmlspecialchars(str_replace(' ', '_', $sectionTitle)); |
||
360 | |||
361 | $sectionWikitext = "<a target='_blank' href='$pageUrl#$sectionTitleLink'>→</a>" . |
||
362 | "<em class='text-muted'>" . htmlspecialchars($sectionTitle) . ":</em> "; |
||
363 | $summary = str_replace($sectionMatch[0][0], $sectionWikitext, $summary); |
||
364 | } |
||
365 | |||
366 | $linkMatch = null; |
||
367 | |||
368 | while (preg_match_all("/\[\[:?(.*?)]]/", $summary, $linkMatch)) { |
||
369 | $wikiLinkParts = explode('|', $linkMatch[1][0]); |
||
370 | $wikiLinkPath = htmlspecialchars($wikiLinkParts[0]); |
||
371 | $wikiLinkText = htmlspecialchars( |
||
372 | $wikiLinkParts[1] ?? $wikiLinkPath |
||
373 | ); |
||
374 | |||
375 | // Use normalized page title (underscored, capitalized). |
||
376 | $pageUrl = $project->getUrlForPage(ucfirst(str_replace(' ', '_', $wikiLinkPath))); |
||
377 | |||
378 | $link = "<a target='_blank' href='$pageUrl'>$wikiLinkText</a>"; |
||
379 | $summary = str_replace($linkMatch[0][0], $link, $summary); |
||
380 | } |
||
381 | |||
382 | return $summary; |
||
383 | } |
||
384 | |||
385 | /** |
||
386 | * Get edit summary as 'wikified' HTML markup (alias of Edit::getWikifiedComment()). |
||
387 | * @return string |
||
388 | */ |
||
389 | public function getWikifiedSummary(): string |
||
390 | { |
||
391 | return $this->getWikifiedComment(); |
||
392 | } |
||
393 | |||
394 | /** |
||
395 | * Get the project this edit was made on |
||
396 | * @return Project |
||
397 | */ |
||
398 | public function getProject(): Project |
||
399 | { |
||
400 | return $this->getPage()->getProject(); |
||
401 | } |
||
402 | |||
403 | /** |
||
404 | * Get the full URL to the diff of the edit |
||
405 | * @return string |
||
406 | */ |
||
407 | public function getDiffUrl(): string |
||
408 | { |
||
409 | return rtrim($this->getProject()->getUrlForPage('Special:Diff/' . $this->id), '/'); |
||
410 | } |
||
411 | |||
412 | /** |
||
413 | * Get the full permanent URL to the page at the time of the edit |
||
414 | * @return string |
||
415 | */ |
||
416 | public function getPermaUrl(): string |
||
417 | { |
||
418 | return rtrim($this->getProject()->getUrlForPage('Special:PermaLink/' . $this->id), '/'); |
||
419 | } |
||
420 | |||
421 | /** |
||
422 | * Was the edit a revert, based on the edit summary? |
||
423 | * @return bool |
||
424 | */ |
||
425 | public function isRevert(): bool |
||
426 | { |
||
427 | return $this->repository->getAutoEditsHelper()->isRevert($this->comment, $this->getProject()); |
||
428 | } |
||
429 | |||
430 | /** |
||
431 | * Get the name of the tool that was used to make this edit. |
||
432 | * @return array|null The name of the tool(s) that was used to make the edit. |
||
433 | */ |
||
434 | public function getTool(): ?array |
||
435 | { |
||
436 | return $this->repository->getAutoEditsHelper()->getTool($this->comment, $this->getProject()); |
||
437 | } |
||
438 | |||
439 | /** |
||
440 | * Was the edit (semi-)automated, based on the edit summary? |
||
441 | * @return bool |
||
442 | */ |
||
443 | public function isAutomated(): bool |
||
444 | { |
||
445 | return (bool)$this->getTool(); |
||
446 | } |
||
447 | |||
448 | /** |
||
449 | * Was the edit made by a logged out user? |
||
450 | * @return bool|null |
||
451 | */ |
||
452 | public function isAnon(): ?bool |
||
453 | { |
||
454 | return $this->getUser() ? $this->getUser()->isAnon() : null; |
||
455 | } |
||
456 | |||
457 | /** |
||
458 | * Get HTML for the diff of this Edit. |
||
459 | * @return string|null Raw HTML, must be wrapped in a <table> tag. Null if no comparison could be made. |
||
460 | */ |
||
461 | public function getDiffHtml(): ?string |
||
464 | } |
||
465 | |||
466 | /** |
||
467 | * Formats the data as an array for use in JSON APIs. |
||
468 | * @param bool $includeProject |
||
469 | * @return array |
||
470 | * @internal This method assumes the Edit was constructed with data already filled in from a database query. |
||
471 | */ |
||
472 | public function getForJson(bool $includeProject = false): array |
||
473 | { |
||
507 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
integer
values, zero is a special case, in particular the following results might be unexpected: