This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Implements Special:Undelete |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @ingroup SpecialPage |
||
22 | */ |
||
23 | |||
24 | /** |
||
25 | * Used to show archived pages and eventually restore them. |
||
26 | * |
||
27 | * @ingroup SpecialPage |
||
28 | */ |
||
29 | class PageArchive { |
||
30 | /** @var Title */ |
||
31 | protected $title; |
||
32 | |||
33 | /** @var Status */ |
||
34 | protected $fileStatus; |
||
35 | |||
36 | /** @var Status */ |
||
37 | protected $revisionStatus; |
||
38 | |||
39 | /** @var Config */ |
||
40 | protected $config; |
||
41 | |||
42 | function __construct( $title, Config $config = null ) { |
||
43 | if ( is_null( $title ) ) { |
||
44 | throw new MWException( __METHOD__ . ' given a null title.' ); |
||
45 | } |
||
46 | $this->title = $title; |
||
47 | View Code Duplication | if ( $config === null ) { |
|
48 | wfDebug( __METHOD__ . ' did not have a Config object passed to it' ); |
||
49 | $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' ); |
||
50 | } |
||
51 | $this->config = $config; |
||
52 | } |
||
53 | |||
54 | public function doesWrites() { |
||
55 | return true; |
||
56 | } |
||
57 | |||
58 | /** |
||
59 | * List all deleted pages recorded in the archive table. Returns result |
||
60 | * wrapper with (ar_namespace, ar_title, count) fields, ordered by page |
||
61 | * namespace/title. |
||
62 | * |
||
63 | * @return ResultWrapper |
||
64 | */ |
||
65 | public static function listAllPages() { |
||
66 | $dbr = wfGetDB( DB_REPLICA ); |
||
67 | |||
68 | return self::listPages( $dbr, '' ); |
||
0 ignored issues
–
show
|
|||
69 | } |
||
70 | |||
71 | /** |
||
72 | * List deleted pages recorded in the archive table matching the |
||
73 | * given title prefix. |
||
74 | * Returns result wrapper with (ar_namespace, ar_title, count) fields. |
||
75 | * |
||
76 | * @param string $prefix Title prefix |
||
77 | * @return ResultWrapper |
||
78 | */ |
||
79 | public static function listPagesByPrefix( $prefix ) { |
||
80 | $dbr = wfGetDB( DB_REPLICA ); |
||
81 | |||
82 | $title = Title::newFromText( $prefix ); |
||
83 | if ( $title ) { |
||
84 | $ns = $title->getNamespace(); |
||
85 | $prefix = $title->getDBkey(); |
||
86 | } else { |
||
87 | // Prolly won't work too good |
||
88 | // @todo handle bare namespace names cleanly? |
||
89 | $ns = 0; |
||
90 | } |
||
91 | |||
92 | $conds = [ |
||
93 | 'ar_namespace' => $ns, |
||
94 | 'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ), |
||
95 | ]; |
||
96 | |||
97 | return self::listPages( $dbr, $conds ); |
||
0 ignored issues
–
show
It seems like
$dbr defined by wfGetDB(DB_REPLICA) on line 80 can be null ; however, PageArchive::listPages() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
98 | } |
||
99 | |||
100 | /** |
||
101 | * @param IDatabase $dbr |
||
102 | * @param string|array $condition |
||
103 | * @return bool|ResultWrapper |
||
104 | */ |
||
105 | protected static function listPages( $dbr, $condition ) { |
||
106 | return $dbr->select( |
||
107 | [ 'archive' ], |
||
108 | [ |
||
109 | 'ar_namespace', |
||
110 | 'ar_title', |
||
111 | 'count' => 'COUNT(*)' |
||
112 | ], |
||
113 | $condition, |
||
114 | __METHOD__, |
||
115 | [ |
||
116 | 'GROUP BY' => [ 'ar_namespace', 'ar_title' ], |
||
117 | 'ORDER BY' => [ 'ar_namespace', 'ar_title' ], |
||
118 | 'LIMIT' => 100, |
||
119 | ] |
||
120 | ); |
||
121 | } |
||
122 | |||
123 | /** |
||
124 | * List the revisions of the given page. Returns result wrapper with |
||
125 | * (ar_minor_edit, ar_timestamp, ar_user, ar_user_text, ar_comment) fields. |
||
126 | * |
||
127 | * @return ResultWrapper |
||
128 | */ |
||
129 | function listRevisions() { |
||
130 | $dbr = wfGetDB( DB_REPLICA ); |
||
131 | |||
132 | $tables = [ 'archive' ]; |
||
133 | |||
134 | $fields = [ |
||
135 | 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', |
||
136 | 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1', |
||
137 | ]; |
||
138 | |||
139 | if ( $this->config->get( 'ContentHandlerUseDB' ) ) { |
||
140 | $fields[] = 'ar_content_format'; |
||
141 | $fields[] = 'ar_content_model'; |
||
142 | } |
||
143 | |||
144 | $conds = [ 'ar_namespace' => $this->title->getNamespace(), |
||
145 | 'ar_title' => $this->title->getDBkey() ]; |
||
146 | |||
147 | $options = [ 'ORDER BY' => 'ar_timestamp DESC' ]; |
||
148 | |||
149 | $join_conds = []; |
||
150 | |||
151 | ChangeTags::modifyDisplayQuery( |
||
152 | $tables, |
||
153 | $fields, |
||
154 | $conds, |
||
155 | $join_conds, |
||
156 | $options, |
||
157 | '' |
||
158 | ); |
||
159 | |||
160 | return $dbr->select( $tables, |
||
161 | $fields, |
||
162 | $conds, |
||
0 ignored issues
–
show
It seems like
$conds defined by array('ar_namespace' => ...his->title->getDBkey()) on line 144 can also be of type array ; however, Database::select() does only seem to accept string , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
163 | __METHOD__, |
||
164 | $options, |
||
165 | $join_conds |
||
166 | ); |
||
167 | } |
||
168 | |||
169 | /** |
||
170 | * List the deleted file revisions for this page, if it's a file page. |
||
171 | * Returns a result wrapper with various filearchive fields, or null |
||
172 | * if not a file page. |
||
173 | * |
||
174 | * @return ResultWrapper |
||
175 | * @todo Does this belong in Image for fuller encapsulation? |
||
176 | */ |
||
177 | function listFiles() { |
||
178 | if ( $this->title->getNamespace() != NS_FILE ) { |
||
179 | return null; |
||
180 | } |
||
181 | |||
182 | $dbr = wfGetDB( DB_REPLICA ); |
||
183 | return $dbr->select( |
||
184 | 'filearchive', |
||
185 | ArchivedFile::selectFields(), |
||
186 | [ 'fa_name' => $this->title->getDBkey() ], |
||
187 | __METHOD__, |
||
188 | [ 'ORDER BY' => 'fa_timestamp DESC' ] |
||
189 | ); |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * Return a Revision object containing data for the deleted revision. |
||
194 | * Note that the result *may* or *may not* have a null page ID. |
||
195 | * |
||
196 | * @param string $timestamp |
||
197 | * @return Revision|null |
||
198 | */ |
||
199 | function getRevision( $timestamp ) { |
||
200 | $dbr = wfGetDB( DB_REPLICA ); |
||
201 | |||
202 | $fields = [ |
||
203 | 'ar_rev_id', |
||
204 | 'ar_text', |
||
205 | 'ar_comment', |
||
206 | 'ar_user', |
||
207 | 'ar_user_text', |
||
208 | 'ar_timestamp', |
||
209 | 'ar_minor_edit', |
||
210 | 'ar_flags', |
||
211 | 'ar_text_id', |
||
212 | 'ar_deleted', |
||
213 | 'ar_len', |
||
214 | 'ar_sha1', |
||
215 | ]; |
||
216 | |||
217 | if ( $this->config->get( 'ContentHandlerUseDB' ) ) { |
||
218 | $fields[] = 'ar_content_format'; |
||
219 | $fields[] = 'ar_content_model'; |
||
220 | } |
||
221 | |||
222 | $row = $dbr->selectRow( 'archive', |
||
223 | $fields, |
||
224 | [ 'ar_namespace' => $this->title->getNamespace(), |
||
225 | 'ar_title' => $this->title->getDBkey(), |
||
226 | 'ar_timestamp' => $dbr->timestamp( $timestamp ) ], |
||
227 | __METHOD__ ); |
||
228 | |||
229 | if ( $row ) { |
||
230 | return Revision::newFromArchiveRow( $row, [ 'title' => $this->title ] ); |
||
231 | } |
||
232 | |||
233 | return null; |
||
234 | } |
||
235 | |||
236 | /** |
||
237 | * Return the most-previous revision, either live or deleted, against |
||
238 | * the deleted revision given by timestamp. |
||
239 | * |
||
240 | * May produce unexpected results in case of history merges or other |
||
241 | * unusual time issues. |
||
242 | * |
||
243 | * @param string $timestamp |
||
244 | * @return Revision|null Null when there is no previous revision |
||
245 | */ |
||
246 | function getPreviousRevision( $timestamp ) { |
||
247 | $dbr = wfGetDB( DB_REPLICA ); |
||
248 | |||
249 | // Check the previous deleted revision... |
||
250 | $row = $dbr->selectRow( 'archive', |
||
251 | 'ar_timestamp', |
||
252 | [ 'ar_namespace' => $this->title->getNamespace(), |
||
253 | 'ar_title' => $this->title->getDBkey(), |
||
254 | 'ar_timestamp < ' . |
||
255 | $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ], |
||
256 | __METHOD__, |
||
257 | [ |
||
258 | 'ORDER BY' => 'ar_timestamp DESC', |
||
259 | 'LIMIT' => 1 ] ); |
||
260 | $prevDeleted = $row ? wfTimestamp( TS_MW, $row->ar_timestamp ) : false; |
||
261 | |||
262 | $row = $dbr->selectRow( [ 'page', 'revision' ], |
||
263 | [ 'rev_id', 'rev_timestamp' ], |
||
264 | [ |
||
265 | 'page_namespace' => $this->title->getNamespace(), |
||
266 | 'page_title' => $this->title->getDBkey(), |
||
267 | 'page_id = rev_page', |
||
268 | 'rev_timestamp < ' . |
||
269 | $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ], |
||
270 | __METHOD__, |
||
271 | [ |
||
272 | 'ORDER BY' => 'rev_timestamp DESC', |
||
273 | 'LIMIT' => 1 ] ); |
||
274 | $prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false; |
||
275 | $prevLiveId = $row ? intval( $row->rev_id ) : null; |
||
276 | |||
277 | if ( $prevLive && $prevLive > $prevDeleted ) { |
||
0 ignored issues
–
show
The expression
$prevLive of type string|false is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
278 | // Most prior revision was live |
||
279 | return Revision::newFromId( $prevLiveId ); |
||
280 | } elseif ( $prevDeleted ) { |
||
0 ignored issues
–
show
The expression
$prevDeleted of type string|false is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
281 | // Most prior revision was deleted |
||
282 | return $this->getRevision( $prevDeleted ); |
||
283 | } |
||
284 | |||
285 | // No prior revision on this page. |
||
286 | return null; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Get the text from an archive row containing ar_text, ar_flags and ar_text_id |
||
291 | * |
||
292 | * @param object $row Database row |
||
293 | * @return string |
||
294 | */ |
||
295 | function getTextFromRow( $row ) { |
||
296 | if ( is_null( $row->ar_text_id ) ) { |
||
297 | // An old row from MediaWiki 1.4 or previous. |
||
298 | // Text is embedded in this row in classic compression format. |
||
299 | return Revision::getRevisionText( $row, 'ar_' ); |
||
300 | } |
||
301 | |||
302 | // New-style: keyed to the text storage backend. |
||
303 | $dbr = wfGetDB( DB_REPLICA ); |
||
304 | $text = $dbr->selectRow( 'text', |
||
305 | [ 'old_text', 'old_flags' ], |
||
306 | [ 'old_id' => $row->ar_text_id ], |
||
307 | __METHOD__ ); |
||
308 | |||
309 | return Revision::getRevisionText( $text ); |
||
310 | } |
||
311 | |||
312 | /** |
||
313 | * Fetch (and decompress if necessary) the stored text of the most |
||
314 | * recently edited deleted revision of the page. |
||
315 | * |
||
316 | * If there are no archived revisions for the page, returns NULL. |
||
317 | * |
||
318 | * @return string|null |
||
319 | */ |
||
320 | function getLastRevisionText() { |
||
321 | $dbr = wfGetDB( DB_REPLICA ); |
||
322 | $row = $dbr->selectRow( 'archive', |
||
323 | [ 'ar_text', 'ar_flags', 'ar_text_id' ], |
||
324 | [ 'ar_namespace' => $this->title->getNamespace(), |
||
325 | 'ar_title' => $this->title->getDBkey() ], |
||
326 | __METHOD__, |
||
327 | [ 'ORDER BY' => 'ar_timestamp DESC' ] ); |
||
328 | |||
329 | if ( $row ) { |
||
330 | return $this->getTextFromRow( $row ); |
||
331 | } |
||
332 | |||
333 | return null; |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Quick check if any archived revisions are present for the page. |
||
338 | * |
||
339 | * @return bool |
||
340 | */ |
||
341 | function isDeleted() { |
||
342 | $dbr = wfGetDB( DB_REPLICA ); |
||
343 | $n = $dbr->selectField( 'archive', 'COUNT(ar_title)', |
||
344 | [ 'ar_namespace' => $this->title->getNamespace(), |
||
345 | 'ar_title' => $this->title->getDBkey() ], |
||
346 | __METHOD__ |
||
347 | ); |
||
348 | |||
349 | return ( $n > 0 ); |
||
350 | } |
||
351 | |||
352 | /** |
||
353 | * Restore the given (or all) text and file revisions for the page. |
||
354 | * Once restored, the items will be removed from the archive tables. |
||
355 | * The deletion log will be updated with an undeletion notice. |
||
356 | * |
||
357 | * This also sets Status objects, $this->fileStatus and $this->revisionStatus |
||
358 | * (depending what operations are attempted). |
||
359 | * |
||
360 | * @param array $timestamps Pass an empty array to restore all revisions, |
||
361 | * otherwise list the ones to undelete. |
||
362 | * @param string $comment |
||
363 | * @param array $fileVersions |
||
364 | * @param bool $unsuppress |
||
365 | * @param User $user User performing the action, or null to use $wgUser |
||
366 | * @param string|string[] $tags Change tags to add to log entry |
||
367 | * ($user should be able to add the specified tags before this is called) |
||
368 | * @return array(number of file revisions restored, number of image revisions |
||
0 ignored issues
–
show
The doc-type
array(number could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types. ![]() |
|||
369 | * restored, log message) on success, false on failure. |
||
370 | */ |
||
371 | function undelete( $timestamps, $comment = '', $fileVersions = [], |
||
372 | $unsuppress = false, User $user = null, $tags = null |
||
373 | ) { |
||
374 | // If both the set of text revisions and file revisions are empty, |
||
375 | // restore everything. Otherwise, just restore the requested items. |
||
376 | $restoreAll = empty( $timestamps ) && empty( $fileVersions ); |
||
377 | |||
378 | $restoreText = $restoreAll || !empty( $timestamps ); |
||
379 | $restoreFiles = $restoreAll || !empty( $fileVersions ); |
||
380 | |||
381 | if ( $restoreFiles && $this->title->getNamespace() == NS_FILE ) { |
||
382 | $img = wfLocalFile( $this->title ); |
||
383 | $img->load( File::READ_LATEST ); |
||
384 | $this->fileStatus = $img->restore( $fileVersions, $unsuppress ); |
||
385 | if ( !$this->fileStatus->isOK() ) { |
||
386 | return false; |
||
387 | } |
||
388 | $filesRestored = $this->fileStatus->successCount; |
||
389 | } else { |
||
390 | $filesRestored = 0; |
||
391 | } |
||
392 | |||
393 | if ( $restoreText ) { |
||
394 | $this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment ); |
||
395 | if ( !$this->revisionStatus->isOK() ) { |
||
396 | return false; |
||
397 | } |
||
398 | |||
399 | $textRestored = $this->revisionStatus->getValue(); |
||
400 | } else { |
||
401 | $textRestored = 0; |
||
402 | } |
||
403 | |||
404 | // Touch the log! |
||
405 | |||
406 | if ( $textRestored && $filesRestored ) { |
||
407 | $reason = wfMessage( 'undeletedrevisions-files' ) |
||
408 | ->numParams( $textRestored, $filesRestored )->inContentLanguage()->text(); |
||
409 | } elseif ( $textRestored ) { |
||
410 | $reason = wfMessage( 'undeletedrevisions' )->numParams( $textRestored ) |
||
411 | ->inContentLanguage()->text(); |
||
412 | } elseif ( $filesRestored ) { |
||
413 | $reason = wfMessage( 'undeletedfiles' )->numParams( $filesRestored ) |
||
414 | ->inContentLanguage()->text(); |
||
415 | } else { |
||
416 | wfDebug( "Undelete: nothing undeleted...\n" ); |
||
417 | |||
418 | return false; |
||
419 | } |
||
420 | |||
421 | if ( trim( $comment ) != '' ) { |
||
422 | $reason .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $comment; |
||
423 | } |
||
424 | |||
425 | if ( $user === null ) { |
||
426 | global $wgUser; |
||
427 | $user = $wgUser; |
||
428 | } |
||
429 | |||
430 | $logEntry = new ManualLogEntry( 'delete', 'restore' ); |
||
431 | $logEntry->setPerformer( $user ); |
||
432 | $logEntry->setTarget( $this->title ); |
||
433 | $logEntry->setComment( $reason ); |
||
434 | $logEntry->setTags( $tags ); |
||
0 ignored issues
–
show
It seems like
$tags defined by parameter $tags on line 372 can also be of type null ; however, ManualLogEntry::setTags() does only seem to accept string|array<integer,string> , maybe add an additional type check?
This check looks at variables that have been passed in as parameters and are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble. ![]() |
|||
435 | |||
436 | Hooks::run( 'ArticleUndeleteLogEntry', [ $this, &$logEntry, $user ] ); |
||
437 | |||
438 | $logid = $logEntry->insert(); |
||
439 | $logEntry->publish( $logid ); |
||
440 | |||
441 | return [ $textRestored, $filesRestored, $reason ]; |
||
442 | } |
||
443 | |||
444 | /** |
||
445 | * This is the meaty bit -- It restores archived revisions of the given page |
||
446 | * to the revision table. |
||
447 | * |
||
448 | * @param array $timestamps Pass an empty array to restore all revisions, |
||
449 | * otherwise list the ones to undelete. |
||
450 | * @param bool $unsuppress Remove all ar_deleted/fa_deleted restrictions of seletected revs |
||
451 | * @param string $comment |
||
452 | * @throws ReadOnlyError |
||
453 | * @return Status Status object containing the number of revisions restored on success |
||
454 | */ |
||
455 | private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) { |
||
456 | if ( wfReadOnly() ) { |
||
457 | throw new ReadOnlyError(); |
||
458 | } |
||
459 | |||
460 | $dbw = wfGetDB( DB_MASTER ); |
||
461 | $dbw->startAtomic( __METHOD__ ); |
||
462 | |||
463 | $restoreAll = empty( $timestamps ); |
||
464 | |||
465 | # Does this page already exist? We'll have to update it... |
||
466 | $article = WikiPage::factory( $this->title ); |
||
467 | # Load latest data for the current page (bug 31179) |
||
468 | $article->loadPageData( 'fromdbmaster' ); |
||
469 | $oldcountable = $article->isCountable(); |
||
470 | |||
471 | $page = $dbw->selectRow( 'page', |
||
472 | [ 'page_id', 'page_latest' ], |
||
473 | [ 'page_namespace' => $this->title->getNamespace(), |
||
474 | 'page_title' => $this->title->getDBkey() ], |
||
475 | __METHOD__, |
||
476 | [ 'FOR UPDATE' ] // lock page |
||
477 | ); |
||
478 | |||
479 | if ( $page ) { |
||
480 | $makepage = false; |
||
481 | # Page already exists. Import the history, and if necessary |
||
482 | # we'll update the latest revision field in the record. |
||
483 | |||
484 | # Get the time span of this page |
||
485 | $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp', |
||
486 | [ 'rev_id' => $page->page_latest ], |
||
487 | __METHOD__ ); |
||
488 | |||
489 | View Code Duplication | if ( $previousTimestamp === false ) { |
|
490 | wfDebug( __METHOD__ . ": existing page refers to a page_latest that does not exist\n" ); |
||
491 | |||
492 | $status = Status::newGood( 0 ); |
||
493 | $status->warning( 'undeleterevision-missing' ); |
||
494 | $dbw->endAtomic( __METHOD__ ); |
||
495 | |||
496 | return $status; |
||
497 | } |
||
498 | } else { |
||
499 | # Have to create a new article... |
||
500 | $makepage = true; |
||
501 | $previousTimestamp = 0; |
||
502 | } |
||
503 | |||
504 | $oldWhere = [ |
||
505 | 'ar_namespace' => $this->title->getNamespace(), |
||
506 | 'ar_title' => $this->title->getDBkey(), |
||
507 | ]; |
||
508 | if ( !$restoreAll ) { |
||
509 | $oldWhere['ar_timestamp'] = array_map( [ &$dbw, 'timestamp' ], $timestamps ); |
||
510 | } |
||
511 | |||
512 | $fields = [ |
||
513 | 'ar_id', |
||
514 | 'ar_rev_id', |
||
515 | 'rev_id', |
||
516 | 'ar_text', |
||
517 | 'ar_comment', |
||
518 | 'ar_user', |
||
519 | 'ar_user_text', |
||
520 | 'ar_timestamp', |
||
521 | 'ar_minor_edit', |
||
522 | 'ar_flags', |
||
523 | 'ar_text_id', |
||
524 | 'ar_deleted', |
||
525 | 'ar_page_id', |
||
526 | 'ar_len', |
||
527 | 'ar_sha1' |
||
528 | ]; |
||
529 | |||
530 | if ( $this->config->get( 'ContentHandlerUseDB' ) ) { |
||
531 | $fields[] = 'ar_content_format'; |
||
532 | $fields[] = 'ar_content_model'; |
||
533 | } |
||
534 | |||
535 | /** |
||
536 | * Select each archived revision... |
||
537 | */ |
||
538 | $result = $dbw->select( |
||
539 | [ 'archive', 'revision' ], |
||
540 | $fields, |
||
541 | $oldWhere, |
||
542 | __METHOD__, |
||
543 | /* options */ |
||
544 | [ 'ORDER BY' => 'ar_timestamp' ], |
||
545 | [ 'revision' => [ 'LEFT JOIN', 'ar_rev_id=rev_id' ] ] |
||
546 | ); |
||
547 | |||
548 | $rev_count = $result->numRows(); |
||
549 | View Code Duplication | if ( !$rev_count ) { |
|
550 | wfDebug( __METHOD__ . ": no revisions to restore\n" ); |
||
551 | |||
552 | $status = Status::newGood( 0 ); |
||
553 | $status->warning( "undelete-no-results" ); |
||
554 | $dbw->endAtomic( __METHOD__ ); |
||
555 | |||
556 | return $status; |
||
557 | } |
||
558 | |||
559 | // We use ar_id because there can be duplicate ar_rev_id even for the same |
||
560 | // page. In this case, we may be able to restore the first one. |
||
561 | $restoreFailedArIds = []; |
||
562 | |||
563 | // Map rev_id to the ar_id that is allowed to use it. When checking later, |
||
564 | // if it doesn't match, the current ar_id can not be restored. |
||
565 | |||
566 | // Value can be an ar_id or -1 (-1 means no ar_id can use it, since the |
||
567 | // rev_id is taken before we even start the restore). |
||
568 | $allowedRevIdToArIdMap = []; |
||
569 | |||
570 | $latestRestorableRow = null; |
||
571 | |||
572 | foreach ( $result as $row ) { |
||
573 | if ( $row->ar_rev_id ) { |
||
574 | // rev_id is taken even before we start restoring. |
||
575 | if ( $row->ar_rev_id === $row->rev_id ) { |
||
576 | $restoreFailedArIds[] = $row->ar_id; |
||
577 | $allowedRevIdToArIdMap[$row->ar_rev_id] = -1; |
||
578 | } else { |
||
579 | // rev_id is not taken yet in the DB, but it might be taken |
||
580 | // by a prior revision in the same restore operation. If |
||
581 | // not, we need to reserve it. |
||
582 | if ( isset( $allowedRevIdToArIdMap[$row->ar_rev_id] ) ) { |
||
583 | $restoreFailedArIds[] = $row->ar_id; |
||
584 | } else { |
||
585 | $allowedRevIdToArIdMap[$row->ar_rev_id] = $row->ar_id; |
||
586 | $latestRestorableRow = $row; |
||
587 | } |
||
588 | } |
||
589 | } else { |
||
590 | // If ar_rev_id is null, there can't be a collision, and a |
||
591 | // rev_id will be chosen automatically. |
||
592 | $latestRestorableRow = $row; |
||
593 | } |
||
594 | } |
||
595 | |||
596 | $result->seek( 0 ); // move back |
||
597 | |||
598 | $oldPageId = 0; |
||
599 | if ( $latestRestorableRow !== null ) { |
||
600 | $oldPageId = (int)$latestRestorableRow->ar_page_id; // pass this to ArticleUndelete hook |
||
601 | |||
602 | // grab the content to check consistency with global state before restoring the page. |
||
603 | $revision = Revision::newFromArchiveRow( $latestRestorableRow, |
||
604 | [ |
||
605 | 'title' => $article->getTitle(), // used to derive default content model |
||
606 | ] |
||
607 | ); |
||
608 | $user = User::newFromName( $revision->getUserText( Revision::RAW ), false ); |
||
0 ignored issues
–
show
It seems like
$revision->getUserText(\Revision::RAW) targeting Revision::getUserText() can also be of type boolean ; however, User::newFromName() does only seem to accept string , maybe add an additional type check?
This check looks at variables that are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble. ![]() |
|||
609 | $content = $revision->getContent( Revision::RAW ); |
||
610 | |||
611 | // NOTE: article ID may not be known yet. prepareSave() should not modify the database. |
||
612 | $status = $content->prepareSave( $article, 0, -1, $user ); |
||
0 ignored issues
–
show
It seems like
$user defined by \User::newFromName($revi...\Revision::RAW), false) on line 608 can also be of type false ; however, Content::prepareSave() does only seem to accept object<User> , did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new ![]() It seems like
$article defined by \WikiPage::factory($this->title) on line 466 can be null ; however, Content::prepareSave() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
613 | if ( !$status->isOK() ) { |
||
614 | $dbw->endAtomic( __METHOD__ ); |
||
615 | |||
616 | return $status; |
||
617 | } |
||
618 | } |
||
619 | |||
620 | $newid = false; // newly created page ID |
||
621 | $restored = 0; // number of revisions restored |
||
622 | /** @var Revision $revision */ |
||
623 | $revision = null; |
||
624 | |||
625 | // If there are no restorable revisions, we can skip most of the steps. |
||
626 | if ( $latestRestorableRow === null ) { |
||
627 | $failedRevisionCount = $rev_count; |
||
628 | } else { |
||
629 | if ( $makepage ) { |
||
630 | // Check the state of the newest to-be version... |
||
631 | View Code Duplication | if ( !$unsuppress |
|
632 | && ( $latestRestorableRow->ar_deleted & Revision::DELETED_TEXT ) |
||
633 | ) { |
||
634 | $dbw->endAtomic( __METHOD__ ); |
||
635 | |||
636 | return Status::newFatal( "undeleterevdel" ); |
||
637 | } |
||
638 | // Safe to insert now... |
||
639 | $newid = $article->insertOn( $dbw, $latestRestorableRow->ar_page_id ); |
||
0 ignored issues
–
show
It seems like
$dbw defined by wfGetDB(DB_MASTER) on line 460 can be null ; however, WikiPage::insertOn() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
640 | if ( $newid === false ) { |
||
641 | // The old ID is reserved; let's pick another |
||
642 | $newid = $article->insertOn( $dbw ); |
||
0 ignored issues
–
show
It seems like
$dbw defined by wfGetDB(DB_MASTER) on line 460 can be null ; however, WikiPage::insertOn() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
643 | } |
||
644 | $pageId = $newid; |
||
645 | } else { |
||
646 | // Check if a deleted revision will become the current revision... |
||
647 | View Code Duplication | if ( $latestRestorableRow->ar_timestamp > $previousTimestamp ) { |
|
648 | // Check the state of the newest to-be version... |
||
649 | if ( !$unsuppress |
||
650 | && ( $latestRestorableRow->ar_deleted & Revision::DELETED_TEXT ) |
||
651 | ) { |
||
652 | $dbw->endAtomic( __METHOD__ ); |
||
653 | |||
654 | return Status::newFatal( "undeleterevdel" ); |
||
655 | } |
||
656 | } |
||
657 | |||
658 | $newid = false; |
||
659 | $pageId = $article->getId(); |
||
660 | } |
||
661 | |||
662 | foreach ( $result as $row ) { |
||
663 | // Check for key dupes due to needed archive integrity. |
||
664 | if ( $row->ar_rev_id && $allowedRevIdToArIdMap[$row->ar_rev_id] !== $row->ar_id ) { |
||
665 | continue; |
||
666 | } |
||
667 | // Insert one revision at a time...maintaining deletion status |
||
668 | // unless we are specifically removing all restrictions... |
||
669 | $revision = Revision::newFromArchiveRow( $row, |
||
670 | [ |
||
671 | 'page' => $pageId, |
||
672 | 'title' => $this->title, |
||
673 | 'deleted' => $unsuppress ? 0 : $row->ar_deleted |
||
674 | ] ); |
||
675 | |||
676 | $revision->insertOn( $dbw ); |
||
0 ignored issues
–
show
It seems like
$dbw defined by wfGetDB(DB_MASTER) on line 460 can be null ; however, Revision::insertOn() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
677 | $restored++; |
||
678 | |||
679 | Hooks::run( 'ArticleRevisionUndeleted', |
||
680 | [ &$this->title, $revision, $row->ar_page_id ] ); |
||
681 | } |
||
682 | |||
683 | // Now that it's safely stored, take it out of the archive |
||
684 | // Don't delete rows that we failed to restore |
||
685 | $toDeleteConds = $oldWhere; |
||
686 | $failedRevisionCount = count( $restoreFailedArIds ); |
||
687 | if ( $failedRevisionCount > 0 ) { |
||
688 | $toDeleteConds[] = 'ar_id NOT IN ( ' . $dbw->makeList( $restoreFailedArIds ) . ' )'; |
||
689 | } |
||
690 | |||
691 | $dbw->delete( 'archive', |
||
692 | $toDeleteConds, |
||
693 | __METHOD__ ); |
||
694 | } |
||
695 | |||
696 | $status = Status::newGood( $restored ); |
||
697 | |||
698 | if ( $failedRevisionCount > 0 ) { |
||
699 | $status->warning( |
||
700 | wfMessage( 'undeleterevision-duplicate-revid', $failedRevisionCount ) ); |
||
701 | } |
||
702 | |||
703 | // Was anything restored at all? |
||
704 | if ( $restored ) { |
||
705 | $created = (bool)$newid; |
||
706 | // Attach the latest revision to the page... |
||
707 | $wasnew = $article->updateIfNewerOn( $dbw, $revision ); |
||
0 ignored issues
–
show
It seems like
$dbw defined by wfGetDB(DB_MASTER) on line 460 can be null ; however, WikiPage::updateIfNewerOn() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
708 | if ( $created || $wasnew ) { |
||
709 | // Update site stats, link tables, etc |
||
710 | $article->doEditUpdates( |
||
711 | $revision, |
||
712 | User::newFromName( $revision->getUserText( Revision::RAW ), false ), |
||
0 ignored issues
–
show
It seems like
$revision->getUserText(\Revision::RAW) targeting Revision::getUserText() can also be of type boolean ; however, User::newFromName() does only seem to accept string , maybe add an additional type check?
This check looks at variables that are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble. ![]() |
|||
713 | [ |
||
714 | 'created' => $created, |
||
715 | 'oldcountable' => $oldcountable, |
||
716 | 'restored' => true |
||
717 | ] |
||
718 | ); |
||
719 | } |
||
720 | |||
721 | Hooks::run( 'ArticleUndelete', [ &$this->title, $created, $comment, $oldPageId ] ); |
||
722 | if ( $this->title->getNamespace() == NS_FILE ) { |
||
723 | DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->title, 'imagelinks' ) ); |
||
724 | } |
||
725 | } |
||
726 | |||
727 | $dbw->endAtomic( __METHOD__ ); |
||
728 | |||
729 | return $status; |
||
730 | } |
||
731 | |||
732 | /** |
||
733 | * @return Status |
||
734 | */ |
||
735 | function getFileStatus() { |
||
736 | return $this->fileStatus; |
||
737 | } |
||
738 | |||
739 | /** |
||
740 | * @return Status |
||
741 | */ |
||
742 | function getRevisionStatus() { |
||
743 | return $this->revisionStatus; |
||
744 | } |
||
745 | } |
||
746 | |||
747 | /** |
||
748 | * Special page allowing users with the appropriate permissions to view |
||
749 | * and restore deleted content. |
||
750 | * |
||
751 | * @ingroup SpecialPage |
||
752 | */ |
||
753 | class SpecialUndelete extends SpecialPage { |
||
754 | private $mAction; |
||
755 | private $mTarget; |
||
756 | private $mTimestamp; |
||
757 | private $mRestore; |
||
758 | private $mRevdel; |
||
759 | private $mInvert; |
||
760 | private $mFilename; |
||
761 | private $mTargetTimestamp; |
||
762 | private $mAllowed; |
||
763 | private $mCanView; |
||
764 | private $mComment; |
||
765 | private $mToken; |
||
766 | |||
767 | /** @var Title */ |
||
768 | private $mTargetObj; |
||
769 | |||
770 | function __construct() { |
||
771 | parent::__construct( 'Undelete', 'deletedhistory' ); |
||
772 | } |
||
773 | |||
774 | public function doesWrites() { |
||
775 | return true; |
||
776 | } |
||
777 | |||
778 | function loadRequest( $par ) { |
||
779 | $request = $this->getRequest(); |
||
780 | $user = $this->getUser(); |
||
781 | |||
782 | $this->mAction = $request->getVal( 'action' ); |
||
783 | View Code Duplication | if ( $par !== null && $par !== '' ) { |
|
784 | $this->mTarget = $par; |
||
785 | } else { |
||
786 | $this->mTarget = $request->getVal( 'target' ); |
||
787 | } |
||
788 | |||
789 | $this->mTargetObj = null; |
||
790 | |||
791 | if ( $this->mTarget !== null && $this->mTarget !== '' ) { |
||
792 | $this->mTargetObj = Title::newFromText( $this->mTarget ); |
||
793 | } |
||
794 | |||
795 | $this->mSearchPrefix = $request->getText( 'prefix' ); |
||
0 ignored issues
–
show
The property
mSearchPrefix does not exist. Did you maybe forget to declare it?
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code: class MyClass { }
$x = new MyClass();
$x->foo = true;
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: class MyClass {
public $foo;
}
$x = new MyClass();
$x->foo = true;
![]() |
|||
796 | $time = $request->getVal( 'timestamp' ); |
||
797 | $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : ''; |
||
798 | $this->mFilename = $request->getVal( 'file' ); |
||
799 | |||
800 | $posted = $request->wasPosted() && |
||
801 | $user->matchEditToken( $request->getVal( 'wpEditToken' ) ); |
||
802 | $this->mRestore = $request->getCheck( 'restore' ) && $posted; |
||
803 | $this->mRevdel = $request->getCheck( 'revdel' ) && $posted; |
||
804 | $this->mInvert = $request->getCheck( 'invert' ) && $posted; |
||
805 | $this->mPreview = $request->getCheck( 'preview' ) && $posted; |
||
0 ignored issues
–
show
The property
mPreview does not exist. Did you maybe forget to declare it?
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code: class MyClass { }
$x = new MyClass();
$x->foo = true;
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: class MyClass {
public $foo;
}
$x = new MyClass();
$x->foo = true;
![]() |
|||
806 | $this->mDiff = $request->getCheck( 'diff' ); |
||
0 ignored issues
–
show
The property
mDiff does not exist. Did you maybe forget to declare it?
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code: class MyClass { }
$x = new MyClass();
$x->foo = true;
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: class MyClass {
public $foo;
}
$x = new MyClass();
$x->foo = true;
![]() |
|||
807 | $this->mDiffOnly = $request->getBool( 'diffonly', $this->getUser()->getOption( 'diffonly' ) ); |
||
0 ignored issues
–
show
The property
mDiffOnly does not exist. Did you maybe forget to declare it?
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code: class MyClass { }
$x = new MyClass();
$x->foo = true;
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: class MyClass {
public $foo;
}
$x = new MyClass();
$x->foo = true;
![]() |
|||
808 | $this->mComment = $request->getText( 'wpComment' ); |
||
809 | $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $user->isAllowed( 'suppressrevision' ); |
||
0 ignored issues
–
show
The property
mUnsuppress does not exist. Did you maybe forget to declare it?
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code: class MyClass { }
$x = new MyClass();
$x->foo = true;
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: class MyClass {
public $foo;
}
$x = new MyClass();
$x->foo = true;
![]() The expression
$request->getVal('wpUnsuppress') of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
810 | $this->mToken = $request->getVal( 'token' ); |
||
811 | |||
812 | if ( $this->isAllowed( 'undelete' ) && !$user->isBlocked() ) { |
||
813 | $this->mAllowed = true; // user can restore |
||
814 | $this->mCanView = true; // user can view content |
||
815 | } elseif ( $this->isAllowed( 'deletedtext' ) ) { |
||
816 | $this->mAllowed = false; // user cannot restore |
||
817 | $this->mCanView = true; // user can view content |
||
818 | $this->mRestore = false; |
||
819 | } else { // user can only view the list of revisions |
||
820 | $this->mAllowed = false; |
||
821 | $this->mCanView = false; |
||
822 | $this->mTimestamp = ''; |
||
823 | $this->mRestore = false; |
||
824 | } |
||
825 | |||
826 | if ( $this->mRestore || $this->mInvert ) { |
||
827 | $timestamps = []; |
||
828 | $this->mFileVersions = []; |
||
0 ignored issues
–
show
The property
mFileVersions does not exist. Did you maybe forget to declare it?
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code: class MyClass { }
$x = new MyClass();
$x->foo = true;
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: class MyClass {
public $foo;
}
$x = new MyClass();
$x->foo = true;
![]() |
|||
829 | foreach ( $request->getValues() as $key => $val ) { |
||
830 | $matches = []; |
||
831 | if ( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) { |
||
832 | array_push( $timestamps, $matches[1] ); |
||
833 | } |
||
834 | |||
835 | if ( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) { |
||
836 | $this->mFileVersions[] = intval( $matches[1] ); |
||
837 | } |
||
838 | } |
||
839 | rsort( $timestamps ); |
||
840 | $this->mTargetTimestamp = $timestamps; |
||
841 | } |
||
842 | } |
||
843 | |||
844 | /** |
||
845 | * Checks whether a user is allowed the permission for the |
||
846 | * specific title if one is set. |
||
847 | * |
||
848 | * @param string $permission |
||
849 | * @param User $user |
||
850 | * @return bool |
||
851 | */ |
||
852 | protected function isAllowed( $permission, User $user = null ) { |
||
853 | $user = $user ?: $this->getUser(); |
||
854 | if ( $this->mTargetObj !== null ) { |
||
855 | return $this->mTargetObj->userCan( $permission, $user ); |
||
856 | } else { |
||
857 | return $user->isAllowed( $permission ); |
||
858 | } |
||
859 | } |
||
860 | |||
861 | function userCanExecute( User $user ) { |
||
862 | return $this->isAllowed( $this->mRestriction, $user ); |
||
863 | } |
||
864 | |||
865 | function execute( $par ) { |
||
866 | $this->useTransactionalTimeLimit(); |
||
867 | |||
868 | $user = $this->getUser(); |
||
869 | |||
870 | $this->setHeaders(); |
||
871 | $this->outputHeader(); |
||
872 | |||
873 | $this->loadRequest( $par ); |
||
874 | $this->checkPermissions(); // Needs to be after mTargetObj is set |
||
875 | |||
876 | $out = $this->getOutput(); |
||
877 | |||
878 | if ( is_null( $this->mTargetObj ) ) { |
||
879 | $out->addWikiMsg( 'undelete-header' ); |
||
880 | |||
881 | # Not all users can just browse every deleted page from the list |
||
882 | if ( $user->isAllowed( 'browsearchive' ) ) { |
||
883 | $this->showSearchForm(); |
||
884 | } |
||
885 | |||
886 | return; |
||
887 | } |
||
888 | |||
889 | $this->addHelpLink( 'Help:Undelete' ); |
||
890 | if ( $this->mAllowed ) { |
||
891 | $out->setPageTitle( $this->msg( 'undeletepage' ) ); |
||
892 | } else { |
||
893 | $out->setPageTitle( $this->msg( 'viewdeletedpage' ) ); |
||
894 | } |
||
895 | |||
896 | $this->getSkin()->setRelevantTitle( $this->mTargetObj ); |
||
897 | |||
898 | if ( $this->mTimestamp !== '' ) { |
||
899 | $this->showRevision( $this->mTimestamp ); |
||
900 | } elseif ( $this->mFilename !== null && $this->mTargetObj->inNamespace( NS_FILE ) ) { |
||
901 | $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename ); |
||
902 | // Check if user is allowed to see this file |
||
903 | if ( !$file->exists() ) { |
||
904 | $out->addWikiMsg( 'filedelete-nofile', $this->mFilename ); |
||
905 | View Code Duplication | } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) { |
|
906 | if ( $file->isDeleted( File::DELETED_RESTRICTED ) ) { |
||
907 | throw new PermissionsError( 'suppressrevision' ); |
||
908 | } else { |
||
909 | throw new PermissionsError( 'deletedtext' ); |
||
910 | } |
||
911 | } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) { |
||
912 | $this->showFileConfirmationForm( $this->mFilename ); |
||
913 | } else { |
||
914 | $this->showFile( $this->mFilename ); |
||
915 | } |
||
916 | } elseif ( $this->mAction === "submit" ) { |
||
917 | if ( $this->mRestore ) { |
||
918 | $this->undelete(); |
||
919 | } elseif ( $this->mRevdel ) { |
||
920 | $this->redirectToRevDel(); |
||
921 | } |
||
922 | |||
923 | } else { |
||
924 | $this->showHistory(); |
||
925 | } |
||
926 | } |
||
927 | |||
928 | /** |
||
929 | * Convert submitted form data to format expected by RevisionDelete and |
||
930 | * redirect the request |
||
931 | */ |
||
932 | private function redirectToRevDel() { |
||
933 | $archive = new PageArchive( $this->mTargetObj ); |
||
934 | |||
935 | $revisions = []; |
||
936 | |||
937 | foreach ( $this->getRequest()->getValues() as $key => $val ) { |
||
938 | $matches = []; |
||
939 | if ( preg_match( "/^ts(\d{14})$/", $key, $matches ) ) { |
||
940 | $revisions[ $archive->getRevision( $matches[1] )->getId() ] = 1; |
||
941 | } |
||
942 | } |
||
943 | $query = [ |
||
944 | "type" => "revision", |
||
945 | "ids" => $revisions, |
||
946 | "target" => $this->mTargetObj->getPrefixedText() |
||
947 | ]; |
||
948 | $url = SpecialPage::getTitleFor( 'Revisiondelete' )->getFullURL( $query ); |
||
949 | $this->getOutput()->redirect( $url ); |
||
950 | } |
||
951 | |||
952 | function showSearchForm() { |
||
953 | $out = $this->getOutput(); |
||
954 | $out->setPageTitle( $this->msg( 'undelete-search-title' ) ); |
||
955 | $out->addHTML( |
||
956 | Xml::openElement( 'form', [ 'method' => 'get', 'action' => wfScript() ] ) . |
||
957 | Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) . |
||
958 | Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . |
||
959 | Html::rawElement( |
||
960 | 'label', |
||
961 | [ 'for' => 'prefix' ], |
||
962 | $this->msg( 'undelete-search-prefix' )->parse() |
||
963 | ) . |
||
964 | Xml::input( |
||
965 | 'prefix', |
||
966 | 20, |
||
967 | $this->mSearchPrefix, |
||
968 | [ 'id' => 'prefix', 'autofocus' => '' ] |
||
969 | ) . ' ' . |
||
970 | Xml::submitButton( $this->msg( 'undelete-search-submit' )->text() ) . |
||
971 | Xml::closeElement( 'fieldset' ) . |
||
972 | Xml::closeElement( 'form' ) |
||
973 | ); |
||
974 | |||
975 | # List undeletable articles |
||
976 | if ( $this->mSearchPrefix ) { |
||
977 | $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix ); |
||
978 | $this->showList( $result ); |
||
979 | } |
||
980 | } |
||
981 | |||
982 | /** |
||
983 | * Generic list of deleted pages |
||
984 | * |
||
985 | * @param ResultWrapper $result |
||
986 | * @return bool |
||
987 | */ |
||
988 | private function showList( $result ) { |
||
989 | $out = $this->getOutput(); |
||
990 | |||
991 | if ( $result->numRows() == 0 ) { |
||
992 | $out->addWikiMsg( 'undelete-no-results' ); |
||
993 | |||
994 | return false; |
||
995 | } |
||
996 | |||
997 | $out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) ); |
||
998 | |||
999 | $undelete = $this->getPageTitle(); |
||
1000 | $out->addHTML( "<ul>\n" ); |
||
1001 | foreach ( $result as $row ) { |
||
1002 | $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title ); |
||
1003 | if ( $title !== null ) { |
||
1004 | $item = Linker::linkKnown( |
||
1005 | $undelete, |
||
1006 | htmlspecialchars( $title->getPrefixedText() ), |
||
1007 | [], |
||
1008 | [ 'target' => $title->getPrefixedText() ] |
||
1009 | ); |
||
1010 | } else { |
||
1011 | // The title is no longer valid, show as text |
||
1012 | $item = Html::element( |
||
1013 | 'span', |
||
1014 | [ 'class' => 'mw-invalidtitle' ], |
||
1015 | Linker::getInvalidTitleDescription( |
||
1016 | $this->getContext(), |
||
1017 | $row->ar_namespace, |
||
1018 | $row->ar_title |
||
1019 | ) |
||
1020 | ); |
||
1021 | } |
||
1022 | $revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse(); |
||
1023 | $out->addHTML( "<li>{$item} ({$revs})</li>\n" ); |
||
1024 | } |
||
1025 | $result->free(); |
||
1026 | $out->addHTML( "</ul>\n" ); |
||
1027 | |||
1028 | return true; |
||
1029 | } |
||
1030 | |||
1031 | private function showRevision( $timestamp ) { |
||
1032 | if ( !preg_match( '/[0-9]{14}/', $timestamp ) ) { |
||
1033 | return; |
||
1034 | } |
||
1035 | |||
1036 | $archive = new PageArchive( $this->mTargetObj, $this->getConfig() ); |
||
1037 | if ( !Hooks::run( 'UndeleteForm::showRevision', [ &$archive, $this->mTargetObj ] ) ) { |
||
1038 | return; |
||
1039 | } |
||
1040 | $rev = $archive->getRevision( $timestamp ); |
||
1041 | |||
1042 | $out = $this->getOutput(); |
||
1043 | $user = $this->getUser(); |
||
1044 | |||
1045 | if ( !$rev ) { |
||
1046 | $out->addWikiMsg( 'undeleterevision-missing' ); |
||
1047 | |||
1048 | return; |
||
1049 | } |
||
1050 | |||
1051 | if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { |
||
1052 | View Code Duplication | if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { |
|
1053 | $out->wrapWikiMsg( |
||
1054 | "<div class='mw-warning plainlinks'>\n$1\n</div>\n", |
||
1055 | $rev->isDeleted( Revision::DELETED_RESTRICTED ) ? |
||
1056 | 'rev-suppressed-text-permission' : 'rev-deleted-text-permission' |
||
1057 | ); |
||
1058 | |||
1059 | return; |
||
1060 | } |
||
1061 | |||
1062 | $out->wrapWikiMsg( |
||
1063 | "<div class='mw-warning plainlinks'>\n$1\n</div>\n", |
||
1064 | $rev->isDeleted( Revision::DELETED_RESTRICTED ) ? |
||
1065 | 'rev-suppressed-text-view' : 'rev-deleted-text-view' |
||
1066 | ); |
||
1067 | $out->addHTML( '<br />' ); |
||
1068 | // and we are allowed to see... |
||
1069 | } |
||
1070 | |||
1071 | if ( $this->mDiff ) { |
||
1072 | $previousRev = $archive->getPreviousRevision( $timestamp ); |
||
1073 | if ( $previousRev ) { |
||
1074 | $this->showDiff( $previousRev, $rev ); |
||
1075 | if ( $this->mDiffOnly ) { |
||
1076 | return; |
||
1077 | } |
||
1078 | |||
1079 | $out->addHTML( '<hr />' ); |
||
1080 | } else { |
||
1081 | $out->addWikiMsg( 'undelete-nodiff' ); |
||
1082 | } |
||
1083 | } |
||
1084 | |||
1085 | $link = Linker::linkKnown( |
||
1086 | $this->getPageTitle( $this->mTargetObj->getPrefixedDBkey() ), |
||
1087 | htmlspecialchars( $this->mTargetObj->getPrefixedText() ) |
||
1088 | ); |
||
1089 | |||
1090 | $lang = $this->getLanguage(); |
||
1091 | |||
1092 | // date and time are separate parameters to facilitate localisation. |
||
1093 | // $time is kept for backward compat reasons. |
||
1094 | $time = $lang->userTimeAndDate( $timestamp, $user ); |
||
1095 | $d = $lang->userDate( $timestamp, $user ); |
||
1096 | $t = $lang->userTime( $timestamp, $user ); |
||
1097 | $userLink = Linker::revUserTools( $rev ); |
||
1098 | |||
1099 | $content = $rev->getContent( Revision::FOR_THIS_USER, $user ); |
||
1100 | |||
1101 | $isText = ( $content instanceof TextContent ); |
||
1102 | |||
1103 | if ( $this->mPreview || $isText ) { |
||
1104 | $openDiv = '<div id="mw-undelete-revision" class="mw-warning">'; |
||
1105 | } else { |
||
1106 | $openDiv = '<div id="mw-undelete-revision">'; |
||
1107 | } |
||
1108 | $out->addHTML( $openDiv ); |
||
1109 | |||
1110 | // Revision delete links |
||
1111 | if ( !$this->mDiff ) { |
||
1112 | $revdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj ); |
||
1113 | if ( $revdel ) { |
||
1114 | $out->addHTML( "$revdel " ); |
||
1115 | } |
||
1116 | } |
||
1117 | |||
1118 | $out->addHTML( $this->msg( 'undelete-revision' )->rawParams( $link )->params( |
||
1119 | $time )->rawParams( $userLink )->params( $d, $t )->parse() . '</div>' ); |
||
1120 | |||
1121 | if ( !Hooks::run( 'UndeleteShowRevision', [ $this->mTargetObj, $rev ] ) ) { |
||
1122 | return; |
||
1123 | } |
||
1124 | |||
1125 | if ( ( $this->mPreview || !$isText ) && $content ) { |
||
1126 | // NOTE: non-text content has no source view, so always use rendered preview |
||
1127 | |||
1128 | // Hide [edit]s |
||
1129 | $popts = $out->parserOptions(); |
||
1130 | $popts->setEditSection( false ); |
||
1131 | |||
1132 | $pout = $content->getParserOutput( $this->mTargetObj, $rev->getId(), $popts, true ); |
||
1133 | $out->addParserOutput( $pout ); |
||
1134 | } |
||
1135 | |||
1136 | if ( $isText ) { |
||
1137 | // source view for textual content |
||
1138 | $sourceView = Xml::element( |
||
1139 | 'textarea', |
||
1140 | [ |
||
1141 | 'readonly' => 'readonly', |
||
1142 | 'cols' => $user->getIntOption( 'cols' ), |
||
1143 | 'rows' => $user->getIntOption( 'rows' ) |
||
1144 | ], |
||
1145 | $content->getNativeData() . "\n" |
||
1146 | ); |
||
1147 | |||
1148 | $previewButton = Xml::element( 'input', [ |
||
1149 | 'type' => 'submit', |
||
1150 | 'name' => 'preview', |
||
1151 | 'value' => $this->msg( 'showpreview' )->text() |
||
1152 | ] ); |
||
1153 | } else { |
||
1154 | $sourceView = ''; |
||
1155 | $previewButton = ''; |
||
1156 | } |
||
1157 | |||
1158 | $diffButton = Xml::element( 'input', [ |
||
1159 | 'name' => 'diff', |
||
1160 | 'type' => 'submit', |
||
1161 | 'value' => $this->msg( 'showdiff' )->text() ] ); |
||
1162 | |||
1163 | $out->addHTML( |
||
1164 | $sourceView . |
||
1165 | Xml::openElement( 'div', [ |
||
1166 | 'style' => 'clear: both' ] ) . |
||
1167 | Xml::openElement( 'form', [ |
||
1168 | 'method' => 'post', |
||
1169 | 'action' => $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] ) ] ) . |
||
1170 | Xml::element( 'input', [ |
||
1171 | 'type' => 'hidden', |
||
1172 | 'name' => 'target', |
||
1173 | 'value' => $this->mTargetObj->getPrefixedDBkey() ] ) . |
||
1174 | Xml::element( 'input', [ |
||
1175 | 'type' => 'hidden', |
||
1176 | 'name' => 'timestamp', |
||
1177 | 'value' => $timestamp ] ) . |
||
1178 | Xml::element( 'input', [ |
||
1179 | 'type' => 'hidden', |
||
1180 | 'name' => 'wpEditToken', |
||
1181 | 'value' => $user->getEditToken() ] ) . |
||
1182 | $previewButton . |
||
1183 | $diffButton . |
||
1184 | Xml::closeElement( 'form' ) . |
||
1185 | Xml::closeElement( 'div' ) |
||
1186 | ); |
||
1187 | } |
||
1188 | |||
1189 | /** |
||
1190 | * Build a diff display between this and the previous either deleted |
||
1191 | * or non-deleted edit. |
||
1192 | * |
||
1193 | * @param Revision $previousRev |
||
1194 | * @param Revision $currentRev |
||
1195 | * @return string HTML |
||
1196 | */ |
||
1197 | function showDiff( $previousRev, $currentRev ) { |
||
1198 | $diffContext = clone $this->getContext(); |
||
1199 | $diffContext->setTitle( $currentRev->getTitle() ); |
||
0 ignored issues
–
show
It seems like you code against a concrete implementation and not the interface
IContextSource as the method setTitle() does only exist in the following implementations of said interface: DerivativeContext , EditWatchlistNormalHTMLForm , HTMLForm , OOUIHTMLForm , OutputPage , PreferencesForm , RequestContext , UploadForm , VFormHTMLForm .
Let’s take a look at an example: interface User
{
/** @return string */
public function getPassword();
}
class MyUser implements User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
![]() |
|||
1200 | $diffContext->setWikiPage( WikiPage::factory( $currentRev->getTitle() ) ); |
||
0 ignored issues
–
show
It seems like
$currentRev->getTitle() can be null ; however, factory() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() It seems like you code against a concrete implementation and not the interface
IContextSource as the method setWikiPage() does only exist in the following implementations of said interface: DerivativeContext , RequestContext .
Let’s take a look at an example: interface User
{
/** @return string */
public function getPassword();
}
class MyUser implements User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
![]() |
|||
1201 | |||
1202 | $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext ); |
||
1203 | $diffEngine->showDiffStyle(); |
||
1204 | |||
1205 | $formattedDiff = $diffEngine->generateContentDiffBody( |
||
1206 | $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ), |
||
0 ignored issues
–
show
It seems like
$previousRev->getContent...USER, $this->getUser()) can be null ; however, generateContentDiffBody() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
1207 | $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) |
||
0 ignored issues
–
show
It seems like
$currentRev->getContent(...USER, $this->getUser()) can be null ; however, generateContentDiffBody() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
1208 | ); |
||
1209 | |||
1210 | $formattedDiff = $diffEngine->addHeader( |
||
1211 | $formattedDiff, |
||
1212 | $this->diffHeader( $previousRev, 'o' ), |
||
1213 | $this->diffHeader( $currentRev, 'n' ) |
||
1214 | ); |
||
1215 | |||
1216 | $this->getOutput()->addHTML( "<div>$formattedDiff</div>\n" ); |
||
1217 | } |
||
1218 | |||
1219 | /** |
||
1220 | * @param Revision $rev |
||
1221 | * @param string $prefix |
||
1222 | * @return string |
||
1223 | */ |
||
1224 | private function diffHeader( $rev, $prefix ) { |
||
1225 | $isDeleted = !( $rev->getId() && $rev->getTitle() ); |
||
0 ignored issues
–
show
The expression
$rev->getId() of type integer|null is loosely compared to true ; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For 0 == false // true
0 == null // true
123 == false // false
123 == null // false
// It is often better to use strict comparison
0 === false // false
0 === null // false
![]() |
|||
1226 | if ( $isDeleted ) { |
||
1227 | /// @todo FIXME: $rev->getTitle() is null for deleted revs...? |
||
1228 | $targetPage = $this->getPageTitle(); |
||
1229 | $targetQuery = [ |
||
1230 | 'target' => $this->mTargetObj->getPrefixedText(), |
||
1231 | 'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() ) |
||
1232 | ]; |
||
1233 | } else { |
||
1234 | /// @todo FIXME: getId() may return non-zero for deleted revs... |
||
1235 | $targetPage = $rev->getTitle(); |
||
1236 | $targetQuery = [ 'oldid' => $rev->getId() ]; |
||
1237 | } |
||
1238 | |||
1239 | // Add show/hide deletion links if available |
||
1240 | $user = $this->getUser(); |
||
1241 | $lang = $this->getLanguage(); |
||
1242 | $rdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj ); |
||
1243 | |||
1244 | if ( $rdel ) { |
||
1245 | $rdel = " $rdel"; |
||
1246 | } |
||
1247 | |||
1248 | $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : ''; |
||
1249 | |||
1250 | $tags = wfGetDB( DB_REPLICA )->selectField( |
||
1251 | 'tag_summary', |
||
1252 | 'ts_tags', |
||
1253 | [ 'ts_rev_id' => $rev->getId() ], |
||
1254 | __METHOD__ |
||
1255 | ); |
||
1256 | $tagSummary = ChangeTags::formatSummaryRow( $tags, 'deleteddiff', $this->getContext() ); |
||
1257 | |||
1258 | // FIXME This is reimplementing DifferenceEngine#getRevisionHeader |
||
1259 | // and partially #showDiffPage, but worse |
||
1260 | return '<div id="mw-diff-' . $prefix . 'title1"><strong>' . |
||
1261 | Linker::link( |
||
1262 | $targetPage, |
||
1263 | $this->msg( |
||
1264 | 'revisionasof', |
||
1265 | $lang->userTimeAndDate( $rev->getTimestamp(), $user ), |
||
1266 | $lang->userDate( $rev->getTimestamp(), $user ), |
||
1267 | $lang->userTime( $rev->getTimestamp(), $user ) |
||
1268 | )->escaped(), |
||
1269 | [], |
||
1270 | $targetQuery |
||
1271 | ) . |
||
1272 | '</strong></div>' . |
||
1273 | '<div id="mw-diff-' . $prefix . 'title2">' . |
||
1274 | Linker::revUserTools( $rev ) . '<br />' . |
||
1275 | '</div>' . |
||
1276 | '<div id="mw-diff-' . $prefix . 'title3">' . |
||
1277 | $minor . Linker::revComment( $rev ) . $rdel . '<br />' . |
||
1278 | '</div>' . |
||
1279 | '<div id="mw-diff-' . $prefix . 'title5">' . |
||
1280 | $tagSummary[0] . '<br />' . |
||
1281 | '</div>'; |
||
1282 | } |
||
1283 | |||
1284 | /** |
||
1285 | * Show a form confirming whether a tokenless user really wants to see a file |
||
1286 | * @param string $key |
||
1287 | */ |
||
1288 | private function showFileConfirmationForm( $key ) { |
||
1289 | $out = $this->getOutput(); |
||
1290 | $lang = $this->getLanguage(); |
||
1291 | $user = $this->getUser(); |
||
1292 | $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename ); |
||
1293 | $out->addWikiMsg( 'undelete-show-file-confirm', |
||
1294 | $this->mTargetObj->getText(), |
||
1295 | $lang->userDate( $file->getTimestamp(), $user ), |
||
1296 | $lang->userTime( $file->getTimestamp(), $user ) ); |
||
1297 | $out->addHTML( |
||
1298 | Xml::openElement( 'form', [ |
||
1299 | 'method' => 'POST', |
||
1300 | 'action' => $this->getPageTitle()->getLocalURL( [ |
||
1301 | 'target' => $this->mTarget, |
||
1302 | 'file' => $key, |
||
1303 | 'token' => $user->getEditToken( $key ), |
||
1304 | ] ), |
||
1305 | ] |
||
1306 | ) . |
||
1307 | Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) . |
||
1308 | '</form>' |
||
1309 | ); |
||
1310 | } |
||
1311 | |||
1312 | /** |
||
1313 | * Show a deleted file version requested by the visitor. |
||
1314 | * @param string $key |
||
1315 | */ |
||
1316 | private function showFile( $key ) { |
||
1317 | $this->getOutput()->disable(); |
||
1318 | |||
1319 | # We mustn't allow the output to be CDN cached, otherwise |
||
1320 | # if an admin previews a deleted image, and it's cached, then |
||
1321 | # a user without appropriate permissions can toddle off and |
||
1322 | # nab the image, and CDN will serve it |
||
1323 | $response = $this->getRequest()->response(); |
||
1324 | $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); |
||
1325 | $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); |
||
1326 | $response->header( 'Pragma: no-cache' ); |
||
1327 | |||
1328 | $repo = RepoGroup::singleton()->getLocalRepo(); |
||
1329 | $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key; |
||
1330 | $repo->streamFile( $path ); |
||
1331 | } |
||
1332 | |||
1333 | protected function showHistory() { |
||
1334 | $this->checkReadOnly(); |
||
1335 | |||
1336 | $out = $this->getOutput(); |
||
1337 | if ( $this->mAllowed ) { |
||
1338 | $out->addModules( 'mediawiki.special.undelete' ); |
||
1339 | } |
||
1340 | $out->wrapWikiMsg( |
||
1341 | "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n", |
||
1342 | [ 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ] |
||
1343 | ); |
||
1344 | |||
1345 | $archive = new PageArchive( $this->mTargetObj, $this->getConfig() ); |
||
1346 | Hooks::run( 'UndeleteForm::showHistory', [ &$archive, $this->mTargetObj ] ); |
||
1347 | /* |
||
1348 | $text = $archive->getLastRevisionText(); |
||
1349 | if( is_null( $text ) ) { |
||
1350 | $out->addWikiMsg( 'nohistory' ); |
||
1351 | return; |
||
1352 | } |
||
1353 | */ |
||
1354 | $out->addHTML( '<div class="mw-undelete-history">' ); |
||
1355 | if ( $this->mAllowed ) { |
||
1356 | $out->addWikiMsg( 'undeletehistory' ); |
||
1357 | $out->addWikiMsg( 'undeleterevdel' ); |
||
1358 | } else { |
||
1359 | $out->addWikiMsg( 'undeletehistorynoadmin' ); |
||
1360 | } |
||
1361 | $out->addHTML( '</div>' ); |
||
1362 | |||
1363 | # List all stored revisions |
||
1364 | $revisions = $archive->listRevisions(); |
||
1365 | $files = $archive->listFiles(); |
||
1366 | |||
1367 | $haveRevisions = $revisions && $revisions->numRows() > 0; |
||
1368 | $haveFiles = $files && $files->numRows() > 0; |
||
1369 | |||
1370 | # Batch existence check on user and talk pages |
||
1371 | View Code Duplication | if ( $haveRevisions ) { |
|
1372 | $batch = new LinkBatch(); |
||
1373 | foreach ( $revisions as $row ) { |
||
1374 | $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) ); |
||
0 ignored issues
–
show
It seems like
\Title::makeTitleSafe(NS...ER, $row->ar_user_text) can be null ; however, addObj() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
1375 | $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) ); |
||
0 ignored issues
–
show
It seems like
\Title::makeTitleSafe(NS...LK, $row->ar_user_text) can be null ; however, addObj() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
1376 | } |
||
1377 | $batch->execute(); |
||
1378 | $revisions->seek( 0 ); |
||
1379 | } |
||
1380 | View Code Duplication | if ( $haveFiles ) { |
|
1381 | $batch = new LinkBatch(); |
||
1382 | foreach ( $files as $row ) { |
||
1383 | $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) ); |
||
0 ignored issues
–
show
It seems like
\Title::makeTitleSafe(NS...ER, $row->fa_user_text) can be null ; however, addObj() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
1384 | $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) ); |
||
0 ignored issues
–
show
It seems like
\Title::makeTitleSafe(NS...LK, $row->fa_user_text) can be null ; however, addObj() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
1385 | } |
||
1386 | $batch->execute(); |
||
1387 | $files->seek( 0 ); |
||
1388 | } |
||
1389 | |||
1390 | if ( $this->mAllowed ) { |
||
1391 | $action = $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] ); |
||
1392 | # Start the form here |
||
1393 | $top = Xml::openElement( |
||
1394 | 'form', |
||
1395 | [ 'method' => 'post', 'action' => $action, 'id' => 'undelete' ] |
||
1396 | ); |
||
1397 | $out->addHTML( $top ); |
||
1398 | } |
||
1399 | |||
1400 | # Show relevant lines from the deletion log: |
||
1401 | $deleteLogPage = new LogPage( 'delete' ); |
||
1402 | $out->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) . "\n" ); |
||
1403 | LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj ); |
||
1404 | # Show relevant lines from the suppression log: |
||
1405 | $suppressLogPage = new LogPage( 'suppress' ); |
||
1406 | if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) { |
||
1407 | $out->addHTML( Xml::element( 'h2', null, $suppressLogPage->getName()->text() ) . "\n" ); |
||
1408 | LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj ); |
||
1409 | } |
||
1410 | |||
1411 | if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) { |
||
1412 | # Format the user-visible controls (comment field, submission button) |
||
1413 | # in a nice little table |
||
1414 | if ( $this->getUser()->isAllowed( 'suppressrevision' ) ) { |
||
1415 | $unsuppressBox = |
||
1416 | "<tr> |
||
1417 | <td> </td> |
||
1418 | <td class='mw-input'>" . |
||
1419 | Xml::checkLabel( $this->msg( 'revdelete-unsuppress' )->text(), |
||
1420 | 'wpUnsuppress', 'mw-undelete-unsuppress', $this->mUnsuppress ) . |
||
1421 | "</td> |
||
1422 | </tr>"; |
||
1423 | } else { |
||
1424 | $unsuppressBox = ''; |
||
1425 | } |
||
1426 | |||
1427 | $table = Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) . |
||
1428 | Xml::openElement( 'table', [ 'id' => 'mw-undelete-table' ] ) . |
||
1429 | "<tr> |
||
1430 | <td colspan='2' class='mw-undelete-extrahelp'>" . |
||
1431 | $this->msg( 'undeleteextrahelp' )->parseAsBlock() . |
||
1432 | "</td> |
||
1433 | </tr> |
||
1434 | <tr> |
||
1435 | <td class='mw-label'>" . |
||
1436 | Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) . |
||
1437 | "</td> |
||
1438 | <td class='mw-input'>" . |
||
1439 | Xml::input( |
||
1440 | 'wpComment', |
||
1441 | 50, |
||
1442 | $this->mComment, |
||
1443 | [ 'id' => 'wpComment', 'autofocus' => '' ] |
||
1444 | ) . |
||
1445 | "</td> |
||
1446 | </tr> |
||
1447 | <tr> |
||
1448 | <td> </td> |
||
1449 | <td class='mw-submit'>" . |
||
1450 | Xml::submitButton( |
||
1451 | $this->msg( 'undeletebtn' )->text(), |
||
1452 | [ 'name' => 'restore', 'id' => 'mw-undelete-submit' ] |
||
1453 | ) . ' ' . |
||
1454 | Xml::submitButton( |
||
1455 | $this->msg( 'undeleteinvert' )->text(), |
||
1456 | [ 'name' => 'invert', 'id' => 'mw-undelete-invert' ] |
||
1457 | ) . |
||
1458 | "</td> |
||
1459 | </tr>" . |
||
1460 | $unsuppressBox . |
||
1461 | Xml::closeElement( 'table' ) . |
||
1462 | Xml::closeElement( 'fieldset' ); |
||
1463 | |||
1464 | $out->addHTML( $table ); |
||
1465 | } |
||
1466 | |||
1467 | $out->addHTML( Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n" ); |
||
1468 | |||
1469 | if ( $haveRevisions ) { |
||
1470 | # Show the page's stored (deleted) history |
||
1471 | |||
1472 | if ( $this->getUser()->isAllowed( 'deleterevision' ) ) { |
||
1473 | $out->addHTML( Html::element( |
||
1474 | 'button', |
||
1475 | [ |
||
1476 | 'name' => 'revdel', |
||
1477 | 'type' => 'submit', |
||
1478 | 'class' => 'deleterevision-log-submit mw-log-deleterevision-button' |
||
1479 | ], |
||
1480 | $this->msg( 'showhideselectedversions' )->text() |
||
1481 | ) . "\n" ); |
||
1482 | } |
||
1483 | |||
1484 | $out->addHTML( '<ul>' ); |
||
1485 | $remaining = $revisions->numRows(); |
||
1486 | $earliestLiveTime = $this->mTargetObj->getEarliestRevTime(); |
||
1487 | |||
1488 | foreach ( $revisions as $row ) { |
||
1489 | $remaining--; |
||
1490 | $out->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining ) ); |
||
1491 | } |
||
1492 | $revisions->free(); |
||
1493 | $out->addHTML( '</ul>' ); |
||
1494 | } else { |
||
1495 | $out->addWikiMsg( 'nohistory' ); |
||
1496 | } |
||
1497 | |||
1498 | if ( $haveFiles ) { |
||
1499 | $out->addHTML( Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n" ); |
||
1500 | $out->addHTML( '<ul>' ); |
||
1501 | foreach ( $files as $row ) { |
||
1502 | $out->addHTML( $this->formatFileRow( $row ) ); |
||
1503 | } |
||
1504 | $files->free(); |
||
1505 | $out->addHTML( '</ul>' ); |
||
1506 | } |
||
1507 | |||
1508 | if ( $this->mAllowed ) { |
||
1509 | # Slip in the hidden controls here |
||
1510 | $misc = Html::hidden( 'target', $this->mTarget ); |
||
1511 | $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ); |
||
1512 | $misc .= Xml::closeElement( 'form' ); |
||
1513 | $out->addHTML( $misc ); |
||
1514 | } |
||
1515 | |||
1516 | return true; |
||
1517 | } |
||
1518 | |||
1519 | protected function formatRevisionRow( $row, $earliestLiveTime, $remaining ) { |
||
1520 | $rev = Revision::newFromArchiveRow( $row, |
||
1521 | [ |
||
1522 | 'title' => $this->mTargetObj |
||
1523 | ] ); |
||
1524 | |||
1525 | $revTextSize = ''; |
||
1526 | $ts = wfTimestamp( TS_MW, $row->ar_timestamp ); |
||
1527 | // Build checkboxen... |
||
1528 | if ( $this->mAllowed ) { |
||
1529 | if ( $this->mInvert ) { |
||
1530 | if ( in_array( $ts, $this->mTargetTimestamp ) ) { |
||
1531 | $checkBox = Xml::check( "ts$ts" ); |
||
1532 | } else { |
||
1533 | $checkBox = Xml::check( "ts$ts", true ); |
||
1534 | } |
||
1535 | } else { |
||
1536 | $checkBox = Xml::check( "ts$ts" ); |
||
1537 | } |
||
1538 | } else { |
||
1539 | $checkBox = ''; |
||
1540 | } |
||
1541 | |||
1542 | // Build page & diff links... |
||
1543 | $user = $this->getUser(); |
||
1544 | if ( $this->mCanView ) { |
||
1545 | $titleObj = $this->getPageTitle(); |
||
1546 | # Last link |
||
1547 | if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) { |
||
1548 | $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) ); |
||
1549 | $last = $this->msg( 'diff' )->escaped(); |
||
1550 | } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) { |
||
1551 | $pageLink = $this->getPageLink( $rev, $titleObj, $ts ); |
||
0 ignored issues
–
show
It seems like
$ts defined by wfTimestamp(TS_MW, $row->ar_timestamp) on line 1526 can also be of type false ; however, SpecialUndelete::getPageLink() does only seem to accept string , did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new ![]() |
|||
1552 | $last = Linker::linkKnown( |
||
1553 | $titleObj, |
||
1554 | $this->msg( 'diff' )->escaped(), |
||
1555 | [], |
||
1556 | [ |
||
1557 | 'target' => $this->mTargetObj->getPrefixedText(), |
||
1558 | 'timestamp' => $ts, |
||
1559 | 'diff' => 'prev' |
||
1560 | ] |
||
1561 | ); |
||
1562 | } else { |
||
1563 | $pageLink = $this->getPageLink( $rev, $titleObj, $ts ); |
||
0 ignored issues
–
show
It seems like
$ts defined by wfTimestamp(TS_MW, $row->ar_timestamp) on line 1526 can also be of type false ; however, SpecialUndelete::getPageLink() does only seem to accept string , did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new ![]() |
|||
1564 | $last = $this->msg( 'diff' )->escaped(); |
||
1565 | } |
||
1566 | } else { |
||
1567 | $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) ); |
||
1568 | $last = $this->msg( 'diff' )->escaped(); |
||
1569 | } |
||
1570 | |||
1571 | // User links |
||
1572 | $userLink = Linker::revUserTools( $rev ); |
||
1573 | |||
1574 | // Minor edit |
||
1575 | $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : ''; |
||
1576 | |||
1577 | // Revision text size |
||
1578 | $size = $row->ar_len; |
||
1579 | if ( !is_null( $size ) ) { |
||
1580 | $revTextSize = Linker::formatRevisionSize( $size ); |
||
1581 | } |
||
1582 | |||
1583 | // Edit summary |
||
1584 | $comment = Linker::revComment( $rev ); |
||
1585 | |||
1586 | // Tags |
||
1587 | $attribs = []; |
||
1588 | list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow( |
||
1589 | $row->ts_tags, |
||
1590 | 'deletedhistory', |
||
1591 | $this->getContext() |
||
1592 | ); |
||
1593 | if ( $classes ) { |
||
1594 | $attribs['class'] = implode( ' ', $classes ); |
||
1595 | } |
||
1596 | |||
1597 | $revisionRow = $this->msg( 'undelete-revision-row2' ) |
||
1598 | ->rawParams( |
||
1599 | $checkBox, |
||
1600 | $last, |
||
1601 | $pageLink, |
||
1602 | $userLink, |
||
1603 | $minor, |
||
1604 | $revTextSize, |
||
1605 | $comment, |
||
1606 | $tagSummary |
||
1607 | ) |
||
1608 | ->escaped(); |
||
1609 | |||
1610 | return Xml::tags( 'li', $attribs, $revisionRow ) . "\n"; |
||
1611 | } |
||
1612 | |||
1613 | private function formatFileRow( $row ) { |
||
1614 | $file = ArchivedFile::newFromRow( $row ); |
||
1615 | $ts = wfTimestamp( TS_MW, $row->fa_timestamp ); |
||
1616 | $user = $this->getUser(); |
||
1617 | |||
1618 | $checkBox = ''; |
||
1619 | if ( $this->mCanView && $row->fa_storage_key ) { |
||
1620 | if ( $this->mAllowed ) { |
||
1621 | $checkBox = Xml::check( 'fileid' . $row->fa_id ); |
||
1622 | } |
||
1623 | $key = urlencode( $row->fa_storage_key ); |
||
1624 | $pageLink = $this->getFileLink( $file, $this->getPageTitle(), $ts, $key ); |
||
0 ignored issues
–
show
It seems like
$ts defined by wfTimestamp(TS_MW, $row->fa_timestamp) on line 1615 can also be of type false ; however, SpecialUndelete::getFileLink() does only seem to accept string , did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new ![]() |
|||
1625 | } else { |
||
1626 | $pageLink = $this->getLanguage()->userTimeAndDate( $ts, $user ); |
||
1627 | } |
||
1628 | $userLink = $this->getFileUser( $file ); |
||
1629 | $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text(); |
||
1630 | $bytes = $this->msg( 'parentheses' ) |
||
1631 | ->rawParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() ) |
||
1632 | ->plain(); |
||
1633 | $data = htmlspecialchars( $data . ' ' . $bytes ); |
||
1634 | $comment = $this->getFileComment( $file ); |
||
1635 | |||
1636 | // Add show/hide deletion links if available |
||
1637 | $canHide = $this->isAllowed( 'deleterevision' ); |
||
1638 | if ( $canHide || ( $file->getVisibility() && $this->isAllowed( 'deletedhistory' ) ) ) { |
||
1639 | if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) { |
||
1640 | // Revision was hidden from sysops |
||
1641 | $revdlink = Linker::revDeleteLinkDisabled( $canHide ); |
||
1642 | } else { |
||
1643 | $query = [ |
||
1644 | 'type' => 'filearchive', |
||
1645 | 'target' => $this->mTargetObj->getPrefixedDBkey(), |
||
1646 | 'ids' => $row->fa_id |
||
1647 | ]; |
||
1648 | $revdlink = Linker::revDeleteLink( $query, |
||
1649 | $file->isDeleted( File::DELETED_RESTRICTED ), $canHide ); |
||
1650 | } |
||
1651 | } else { |
||
1652 | $revdlink = ''; |
||
1653 | } |
||
1654 | |||
1655 | return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n"; |
||
1656 | } |
||
1657 | |||
1658 | /** |
||
1659 | * Fetch revision text link if it's available to all users |
||
1660 | * |
||
1661 | * @param Revision $rev |
||
1662 | * @param Title $titleObj |
||
1663 | * @param string $ts Timestamp |
||
1664 | * @return string |
||
1665 | */ |
||
1666 | View Code Duplication | function getPageLink( $rev, $titleObj, $ts ) { |
|
1667 | $user = $this->getUser(); |
||
1668 | $time = $this->getLanguage()->userTimeAndDate( $ts, $user ); |
||
1669 | |||
1670 | if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { |
||
1671 | return '<span class="history-deleted">' . $time . '</span>'; |
||
1672 | } |
||
1673 | |||
1674 | $link = Linker::linkKnown( |
||
1675 | $titleObj, |
||
1676 | htmlspecialchars( $time ), |
||
1677 | [], |
||
1678 | [ |
||
1679 | 'target' => $this->mTargetObj->getPrefixedText(), |
||
1680 | 'timestamp' => $ts |
||
1681 | ] |
||
1682 | ); |
||
1683 | |||
1684 | if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { |
||
1685 | $link = '<span class="history-deleted">' . $link . '</span>'; |
||
1686 | } |
||
1687 | |||
1688 | return $link; |
||
1689 | } |
||
1690 | |||
1691 | /** |
||
1692 | * Fetch image view link if it's available to all users |
||
1693 | * |
||
1694 | * @param File|ArchivedFile $file |
||
1695 | * @param Title $titleObj |
||
1696 | * @param string $ts A timestamp |
||
1697 | * @param string $key A storage key |
||
1698 | * |
||
1699 | * @return string HTML fragment |
||
1700 | */ |
||
1701 | View Code Duplication | function getFileLink( $file, $titleObj, $ts, $key ) { |
|
1702 | $user = $this->getUser(); |
||
1703 | $time = $this->getLanguage()->userTimeAndDate( $ts, $user ); |
||
1704 | |||
1705 | if ( !$file->userCan( File::DELETED_FILE, $user ) ) { |
||
1706 | return '<span class="history-deleted">' . $time . '</span>'; |
||
1707 | } |
||
1708 | |||
1709 | $link = Linker::linkKnown( |
||
1710 | $titleObj, |
||
1711 | htmlspecialchars( $time ), |
||
1712 | [], |
||
1713 | [ |
||
1714 | 'target' => $this->mTargetObj->getPrefixedText(), |
||
1715 | 'file' => $key, |
||
1716 | 'token' => $user->getEditToken( $key ) |
||
1717 | ] |
||
1718 | ); |
||
1719 | |||
1720 | if ( $file->isDeleted( File::DELETED_FILE ) ) { |
||
1721 | $link = '<span class="history-deleted">' . $link . '</span>'; |
||
1722 | } |
||
1723 | |||
1724 | return $link; |
||
1725 | } |
||
1726 | |||
1727 | /** |
||
1728 | * Fetch file's user id if it's available to this user |
||
1729 | * |
||
1730 | * @param File|ArchivedFile $file |
||
1731 | * @return string HTML fragment |
||
1732 | */ |
||
1733 | function getFileUser( $file ) { |
||
1734 | if ( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) { |
||
1735 | return '<span class="history-deleted">' . |
||
1736 | $this->msg( 'rev-deleted-user' )->escaped() . |
||
1737 | '</span>'; |
||
1738 | } |
||
1739 | |||
1740 | $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) . |
||
0 ignored issues
–
show
The method
getRawUser does only exist in ArchivedFile , but not in File .
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
![]() The method
getRawUserText does only exist in ArchivedFile , but not in File .
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
![]() |
|||
1741 | Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() ); |
||
1742 | |||
1743 | if ( $file->isDeleted( File::DELETED_USER ) ) { |
||
1744 | $link = '<span class="history-deleted">' . $link . '</span>'; |
||
1745 | } |
||
1746 | |||
1747 | return $link; |
||
1748 | } |
||
1749 | |||
1750 | /** |
||
1751 | * Fetch file upload comment if it's available to this user |
||
1752 | * |
||
1753 | * @param File|ArchivedFile $file |
||
1754 | * @return string HTML fragment |
||
1755 | */ |
||
1756 | function getFileComment( $file ) { |
||
1757 | if ( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) { |
||
1758 | return '<span class="history-deleted"><span class="comment">' . |
||
1759 | $this->msg( 'rev-deleted-comment' )->escaped() . '</span></span>'; |
||
1760 | } |
||
1761 | |||
1762 | $link = Linker::commentBlock( $file->getRawDescription() ); |
||
0 ignored issues
–
show
The method
getRawDescription does only exist in ArchivedFile , but not in File .
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
![]() |
|||
1763 | |||
1764 | if ( $file->isDeleted( File::DELETED_COMMENT ) ) { |
||
1765 | $link = '<span class="history-deleted">' . $link . '</span>'; |
||
1766 | } |
||
1767 | |||
1768 | return $link; |
||
1769 | } |
||
1770 | |||
1771 | function undelete() { |
||
1772 | if ( $this->getConfig()->get( 'UploadMaintenance' ) |
||
1773 | && $this->mTargetObj->getNamespace() == NS_FILE |
||
1774 | ) { |
||
1775 | throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' ); |
||
1776 | } |
||
1777 | |||
1778 | $this->checkReadOnly(); |
||
1779 | |||
1780 | $out = $this->getOutput(); |
||
1781 | $archive = new PageArchive( $this->mTargetObj, $this->getConfig() ); |
||
1782 | Hooks::run( 'UndeleteForm::undelete', [ &$archive, $this->mTargetObj ] ); |
||
1783 | $ok = $archive->undelete( |
||
1784 | $this->mTargetTimestamp, |
||
1785 | $this->mComment, |
||
1786 | $this->mFileVersions, |
||
1787 | $this->mUnsuppress, |
||
1788 | $this->getUser() |
||
1789 | ); |
||
1790 | |||
1791 | if ( is_array( $ok ) ) { |
||
1792 | if ( $ok[1] ) { // Undeleted file count |
||
1793 | Hooks::run( 'FileUndeleteComplete', [ |
||
1794 | $this->mTargetObj, $this->mFileVersions, |
||
1795 | $this->getUser(), $this->mComment ] ); |
||
1796 | } |
||
1797 | |||
1798 | $link = Linker::linkKnown( $this->mTargetObj ); |
||
1799 | $out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() ); |
||
1800 | } else { |
||
1801 | $out->setPageTitle( $this->msg( 'undelete-error' ) ); |
||
1802 | } |
||
1803 | |||
1804 | // Show revision undeletion warnings and errors |
||
1805 | $status = $archive->getRevisionStatus(); |
||
1806 | if ( $status && !$status->isGood() ) { |
||
1807 | $out->addWikiText( '<div class="error">' . |
||
1808 | $status->getWikiText( |
||
1809 | 'cannotundelete', |
||
1810 | 'cannotundelete' |
||
1811 | ) . '</div>' |
||
1812 | ); |
||
1813 | } |
||
1814 | |||
1815 | // Show file undeletion warnings and errors |
||
1816 | $status = $archive->getFileStatus(); |
||
1817 | if ( $status && !$status->isGood() ) { |
||
1818 | $out->addWikiText( '<div class="error">' . |
||
1819 | $status->getWikiText( |
||
1820 | 'undelete-error-short', |
||
1821 | 'undelete-error-long' |
||
1822 | ) . '</div>' |
||
1823 | ); |
||
1824 | } |
||
1825 | } |
||
1826 | |||
1827 | /** |
||
1828 | * Return an array of subpages beginning with $search that this special page will accept. |
||
1829 | * |
||
1830 | * @param string $search Prefix to search for |
||
1831 | * @param int $limit Maximum number of results to return (usually 10) |
||
1832 | * @param int $offset Number of results to skip (usually 0) |
||
1833 | * @return string[] Matching subpages |
||
1834 | */ |
||
1835 | public function prefixSearchSubpages( $search, $limit, $offset ) { |
||
1836 | return $this->prefixSearchString( $search, $limit, $offset ); |
||
1837 | } |
||
1838 | |||
1839 | protected function getGroupName() { |
||
1840 | return 'pagetools'; |
||
1841 | } |
||
1842 | } |
||
1843 |
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: