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 | namespace Wikibase\Repo\Specials; |
||
4 | |||
5 | use Html; |
||
6 | use HTMLForm; |
||
7 | use InvalidArgumentException; |
||
8 | use OutOfBoundsException; |
||
9 | use SiteLookup; |
||
10 | use Status; |
||
11 | use Wikibase\DataModel\Entity\EntityDocument; |
||
12 | use Wikibase\DataModel\Entity\Item; |
||
13 | use Wikibase\DataModel\Entity\ItemId; |
||
14 | use Wikibase\Lib\Store\EntityTitleLookup; |
||
15 | use Wikibase\Lib\Store\LanguageFallbackLabelDescriptionLookupFactory; |
||
16 | use Wikibase\Lib\Summary; |
||
17 | use Wikibase\Repo\ChangeOp\ChangeOpException; |
||
18 | use Wikibase\Repo\ChangeOp\SiteLinkChangeOpFactory; |
||
19 | use Wikibase\Repo\CopyrightMessageBuilder; |
||
20 | use Wikibase\Repo\EditEntity\MediawikiEditEntityFactory; |
||
21 | use Wikibase\Repo\SiteLinkTargetProvider; |
||
22 | use Wikibase\Repo\SummaryFormatter; |
||
23 | use Wikibase\Repo\WikibaseRepo; |
||
24 | |||
25 | /** |
||
26 | * Special page for setting the sitepage of a Wikibase entity. |
||
27 | * |
||
28 | * @license GPL-2.0-or-later |
||
29 | * @author Bene* < [email protected] > |
||
30 | */ |
||
31 | class SpecialSetSiteLink extends SpecialModifyEntity { |
||
32 | |||
33 | /** |
||
34 | * @var SiteLookup |
||
35 | */ |
||
36 | private $siteLookup; |
||
37 | |||
38 | /** |
||
39 | * @var SiteLinkTargetProvider |
||
40 | */ |
||
41 | private $siteLinkTargetProvider; |
||
42 | |||
43 | /** |
||
44 | * @var string[] |
||
45 | */ |
||
46 | private $siteLinkGroups; |
||
47 | |||
48 | /** |
||
49 | * @var string[] |
||
50 | */ |
||
51 | private $badgeItems; |
||
52 | |||
53 | /** |
||
54 | * @var LanguageFallbackLabelDescriptionLookupFactory |
||
55 | */ |
||
56 | private $labelDescriptionLookupFactory; |
||
57 | |||
58 | /** |
||
59 | * @var SiteLinkChangeOpFactory |
||
60 | */ |
||
61 | private $siteLinkChangeOpFactory; |
||
62 | |||
63 | /** |
||
64 | * The site of the site link. |
||
65 | * |
||
66 | * @var string|null |
||
67 | */ |
||
68 | private $site; |
||
69 | |||
70 | /** |
||
71 | * The page of the site link. |
||
72 | * |
||
73 | * @var string |
||
74 | */ |
||
75 | private $page; |
||
76 | |||
77 | /** |
||
78 | * The badges of the site link. |
||
79 | * |
||
80 | * @var string[] |
||
81 | */ |
||
82 | private $badges; |
||
83 | |||
84 | /** |
||
85 | * @param SpecialPageCopyrightView $copyrightView |
||
86 | * @param SummaryFormatter $summaryFormatter |
||
87 | * @param EntityTitleLookup $entityTitleLookup |
||
88 | * @param MediawikiEditEntityFactory $editEntityFactory |
||
89 | * @param SiteLookup $siteLookup |
||
90 | * @param SiteLinkTargetProvider $siteLinkTargetProvider |
||
91 | * @param string[] $siteLinkGroups |
||
92 | * @param string[] $badgeItems |
||
93 | * @param LanguageFallbackLabelDescriptionLookupFactory $labelDescriptionLookupFactory |
||
94 | * @param SiteLinkChangeOpFactory $siteLinkChangeOpFactory |
||
95 | */ |
||
96 | public function __construct( |
||
97 | SpecialPageCopyrightView $copyrightView, |
||
98 | SummaryFormatter $summaryFormatter, |
||
99 | EntityTitleLookup $entityTitleLookup, |
||
100 | MediawikiEditEntityFactory $editEntityFactory, |
||
101 | SiteLookup $siteLookup, |
||
102 | SiteLinkTargetProvider $siteLinkTargetProvider, |
||
103 | array $siteLinkGroups, |
||
104 | array $badgeItems, |
||
105 | LanguageFallbackLabelDescriptionLookupFactory $labelDescriptionLookupFactory, |
||
106 | SiteLinkChangeOpFactory $siteLinkChangeOpFactory |
||
107 | ) { |
||
108 | parent::__construct( |
||
109 | 'SetSiteLink', |
||
110 | $copyrightView, |
||
111 | $summaryFormatter, |
||
112 | $entityTitleLookup, |
||
113 | $editEntityFactory |
||
114 | ); |
||
115 | |||
116 | $this->siteLookup = $siteLookup; |
||
117 | $this->siteLinkTargetProvider = $siteLinkTargetProvider; |
||
118 | $this->siteLinkGroups = $siteLinkGroups; |
||
119 | $this->badgeItems = $badgeItems; |
||
120 | $this->labelDescriptionLookupFactory = $labelDescriptionLookupFactory; |
||
121 | $this->siteLinkChangeOpFactory = $siteLinkChangeOpFactory; |
||
122 | } |
||
123 | |||
124 | public static function factory(): self { |
||
125 | $wikibaseRepo = WikibaseRepo::getDefaultInstance(); |
||
126 | $siteLookup = $wikibaseRepo->getSiteLookup(); |
||
127 | $settings = $wikibaseRepo->getSettings(); |
||
128 | |||
129 | $siteLinkChangeOpFactory = $wikibaseRepo->getChangeOpFactoryProvider()->getSiteLinkChangeOpFactory(); |
||
130 | $siteLinkTargetProvider = new SiteLinkTargetProvider( |
||
131 | $siteLookup, |
||
132 | $settings->getSetting( 'specialSiteLinkGroups' ) |
||
133 | ); |
||
134 | |||
135 | $copyrightView = new SpecialPageCopyrightView( |
||
136 | new CopyrightMessageBuilder(), |
||
137 | $settings->getSetting( 'dataRightsUrl' ), |
||
138 | $settings->getSetting( 'dataRightsText' ) |
||
139 | ); |
||
140 | |||
141 | $labelDescriptionLookupFactory = $wikibaseRepo->getLanguageFallbackLabelDescriptionLookupFactory(); |
||
142 | return new self( |
||
143 | $copyrightView, |
||
144 | $wikibaseRepo->getSummaryFormatter(), |
||
145 | $wikibaseRepo->getEntityTitleLookup(), |
||
146 | $wikibaseRepo->newEditEntityFactory(), |
||
147 | $siteLookup, |
||
148 | $siteLinkTargetProvider, |
||
149 | $settings->getSetting( 'siteLinkGroups' ), |
||
150 | $settings->getSetting( 'badgeItems' ), |
||
151 | $labelDescriptionLookupFactory, |
||
152 | $siteLinkChangeOpFactory |
||
153 | ); |
||
154 | } |
||
155 | |||
156 | public function doesWrites() { |
||
157 | return true; |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * @see SpecialModifyEntity::processArguments() |
||
162 | * |
||
163 | * @param string|null $subPage |
||
164 | */ |
||
165 | protected function processArguments( $subPage ) { |
||
166 | parent::processArguments( $subPage ); |
||
167 | |||
168 | $request = $this->getRequest(); |
||
169 | // explode the sub page from the format Special:SetSitelink/q123/enwiki |
||
170 | $parts = ( $subPage === '' ) ? [] : explode( '/', $subPage, 2 ); |
||
171 | |||
172 | $entityId = $this->getEntityId(); |
||
173 | |||
174 | // check if id belongs to an item |
||
175 | if ( $entityId !== null |
||
176 | && $entityId->getEntityType() !== Item::ENTITY_TYPE |
||
177 | ) { |
||
178 | $msg = $this->msg( 'wikibase-setsitelink-not-item', $entityId->getSerialization() ); |
||
179 | $this->showErrorHTML( $msg->parse() ); |
||
180 | } |
||
181 | |||
182 | $this->site = trim( $request->getVal( 'site', $parts[1] ?? '' ) ); |
||
183 | |||
184 | if ( $this->site === '' ) { |
||
185 | $this->site = null; |
||
186 | } |
||
187 | |||
188 | if ( $this->site !== null && !$this->isValidSiteId( $this->site ) ) { |
||
189 | $this->showErrorHTML( $this->msg( |
||
190 | 'wikibase-setsitelink-invalid-site', |
||
191 | wfEscapeWikiText( $this->site ) |
||
192 | )->parse() ); |
||
193 | } |
||
194 | |||
195 | $this->page = $request->getVal( 'page' ); |
||
196 | |||
197 | // If the user just enters an item id and a site, dont remove the site link. |
||
198 | // The user can remove the site link in the second form where it has to be |
||
199 | // actually removed. This prevents users from removing site links accidentally. |
||
200 | if ( !$request->getCheck( 'remove' ) && $this->page === '' ) { |
||
201 | $this->page = null; |
||
202 | } |
||
203 | |||
204 | $this->badges = array_intersect( |
||
0 ignored issues
–
show
|
|||
205 | array_keys( $this->badgeItems ), |
||
206 | $request->getArray( 'badges', [] ) |
||
207 | ); |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * @see SpecialModifyEntity::validateInput() |
||
212 | * |
||
213 | * @return bool |
||
214 | */ |
||
215 | protected function validateInput() { |
||
216 | if ( !$this->isValidSiteId( $this->site ) ) { |
||
217 | return false; |
||
218 | } |
||
219 | |||
220 | if ( $this->page === null ) { |
||
221 | return false; |
||
222 | } |
||
223 | |||
224 | if ( !parent::validateInput() ) { |
||
225 | return false; |
||
226 | } |
||
227 | |||
228 | return true; |
||
229 | } |
||
230 | |||
231 | /** |
||
232 | * @see SpecialModifyEntity::modifyEntity() |
||
233 | * |
||
234 | * @param EntityDocument $entity |
||
235 | * |
||
236 | * @return Summary|bool The summary or false |
||
237 | */ |
||
238 | protected function modifyEntity( EntityDocument $entity ) { |
||
239 | try { |
||
240 | $status = $this->setSiteLink( $entity, $this->site, $this->page, $this->badges, $summary ); |
||
241 | } catch ( ChangeOpException $e ) { |
||
242 | $this->showErrorHTML( $e->getMessage() ); |
||
243 | return false; |
||
244 | } |
||
245 | |||
246 | if ( !$status->isGood() ) { |
||
247 | $this->showErrorHTML( $status->getMessage()->parse() ); |
||
248 | return false; |
||
249 | } |
||
250 | |||
251 | return $summary; |
||
252 | } |
||
253 | |||
254 | /** |
||
255 | * Checks if the site id is valid. |
||
256 | * |
||
257 | * @param string $siteId the site id |
||
258 | * |
||
259 | * @return bool |
||
260 | */ |
||
261 | private function isValidSiteId( $siteId ) { |
||
262 | return $siteId !== null |
||
263 | && $this->siteLinkTargetProvider->getSiteList( $this->siteLinkGroups )->hasSite( $siteId ); |
||
264 | } |
||
265 | |||
266 | /** |
||
267 | * @see SpecialModifyEntity::getForm() |
||
268 | * |
||
269 | * @param EntityDocument|null $entity |
||
270 | * |
||
271 | * @return HTMLForm |
||
272 | */ |
||
273 | protected function getForm( EntityDocument $entity = null ) { |
||
274 | if ( $this->page === null ) { |
||
275 | $this->page = $this->site === null ? '' : $this->getSiteLink( $entity, $this->site ); |
||
276 | } |
||
277 | if ( empty( $this->badges ) ) { |
||
278 | $this->badges = $this->site === null ? [] : $this->getBadges( $entity, $this->site ); |
||
279 | } |
||
280 | $pageinput = [ |
||
281 | 'page' => [ |
||
282 | 'name' => 'page', |
||
283 | 'label-message' => 'wikibase-setsitelink-label', |
||
284 | 'type' => 'text', |
||
285 | 'default' => $this->getRequest()->getVal( 'page' ) ?: $this->page, |
||
286 | 'nodata' => true, |
||
287 | 'cssclass' => 'wb-input wb-input-text', |
||
288 | 'id' => 'wb-setsitelink-page' |
||
289 | ] |
||
290 | ]; |
||
291 | |||
292 | if ( !empty( $this->badgeItems ) ) { |
||
293 | $pageinput['badges'] = $this->getMultiSelectForBadges(); |
||
294 | } |
||
295 | |||
296 | $site = $this->siteLookup->getSite( $this->site ); |
||
297 | |||
298 | if ( $entity !== null && $this->site !== null && $site !== null ) { |
||
299 | // show the detailed form which also allows users to remove site links |
||
300 | $intro = $this->msg( |
||
301 | 'wikibase-setsitelink-introfull', |
||
302 | $this->getEntityTitle( $entity->getId() )->getPrefixedText(), |
||
303 | '[' . $site->getPageUrl( '' ) . ' ' . $this->site . ']' |
||
304 | )->parse(); |
||
305 | $formDescriptor = [ |
||
306 | 'site' => [ |
||
307 | 'name' => 'site', |
||
308 | 'type' => 'hidden', |
||
309 | 'default' => $this->site |
||
310 | ], |
||
311 | 'id' => [ |
||
312 | 'name' => 'id', |
||
313 | 'type' => 'hidden', |
||
314 | 'default' => $this->getEntityId()->getSerialization() |
||
315 | ], |
||
316 | 'remove' => [ |
||
317 | 'name' => 'remove', |
||
318 | 'type' => 'hidden', |
||
319 | 'default' => 'remove' |
||
320 | ], |
||
321 | 'revid' => [ |
||
322 | 'name' => 'revid', |
||
323 | 'type' => 'hidden', |
||
324 | 'default' => $this->getBaseRevision()->getRevisionId(), |
||
325 | ] |
||
326 | ]; |
||
327 | } else { |
||
328 | $intro = $this->msg( 'wikibase-setsitelink-intro' )->text(); |
||
329 | |||
330 | if ( !empty( $this->badgeItems ) ) { |
||
331 | $intro .= $this->msg( 'word-separator' )->text() . $this->msg( 'wikibase-setsitelink-intro-badges' )->text(); |
||
332 | } |
||
333 | |||
334 | $formDescriptor = $this->getFormElements( $entity ); |
||
335 | $formDescriptor['site'] = [ |
||
336 | 'name' => 'site', |
||
337 | 'label-message' => 'wikibase-setsitelink-site', |
||
338 | 'type' => 'text', |
||
339 | 'default' => $this->getRequest()->getVal( 'site' ) ?: $this->site, |
||
340 | 'cssclass' => 'wb-input', |
||
341 | 'id' => 'wb-setsitelink-site' |
||
342 | ]; |
||
343 | } |
||
344 | $formDescriptor = array_merge( $formDescriptor, $pageinput ); |
||
345 | |||
346 | return HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() ) |
||
347 | ->setHeaderText( Html::rawElement( 'p', [], $intro ) ); |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * Returns an array for generating a checkbox for each badge. |
||
352 | * |
||
353 | * @return array |
||
354 | */ |
||
355 | private function getMultiSelectForBadges() { |
||
356 | $options = []; |
||
357 | $default = []; |
||
358 | |||
359 | /** @var ItemId[] $badgeItemIds */ |
||
360 | $badgeItemIds = array_map( |
||
361 | function( $badgeId ) { |
||
362 | return new ItemId( $badgeId ); |
||
363 | }, |
||
364 | array_keys( $this->badgeItems ) |
||
365 | ); |
||
366 | |||
367 | $labelLookup = $this->labelDescriptionLookupFactory->newLabelDescriptionLookup( |
||
368 | $this->getLanguage(), |
||
369 | $badgeItemIds |
||
370 | ); |
||
371 | |||
372 | foreach ( $badgeItemIds as $badgeId ) { |
||
373 | $idSerialization = $badgeId->getSerialization(); |
||
374 | |||
375 | $label = $labelLookup->getLabel( $badgeId ); |
||
376 | $label = $label === null ? $idSerialization : $label->getText(); |
||
377 | |||
378 | $options[$label] = $idSerialization; |
||
379 | if ( in_array( $idSerialization, $this->badges ) ) { |
||
380 | $default[] = $idSerialization; |
||
381 | } |
||
382 | } |
||
383 | |||
384 | return [ |
||
385 | 'name' => 'badges', |
||
386 | 'type' => 'multiselect', |
||
387 | 'label-message' => 'wikibase-setsitelink-badges', |
||
388 | 'options' => $options, |
||
389 | 'default' => $default |
||
390 | ]; |
||
391 | } |
||
392 | |||
393 | /** |
||
394 | * Returning the site page of the entity. |
||
395 | * |
||
396 | * @param Item|null $item |
||
397 | * @param string $siteId |
||
398 | * |
||
399 | * @throws OutOfBoundsException |
||
400 | * @return string |
||
401 | */ |
||
402 | private function getSiteLink( ?Item $item, $siteId ) { |
||
403 | if ( $item === null || !$item->hasLinkToSite( $siteId ) ) { |
||
404 | return ''; |
||
405 | } |
||
406 | |||
407 | return $item->getSiteLink( $siteId )->getPageName(); |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * Returning the badges of the entity. |
||
412 | * |
||
413 | * @param Item|null $item |
||
414 | * @param string $siteId |
||
415 | * |
||
416 | * @throws OutOfBoundsException |
||
417 | * @return string[] |
||
418 | */ |
||
419 | private function getBadges( ?Item $item, $siteId ) { |
||
420 | if ( $item === null || !$item->getSiteLinkList()->hasLinkWithSiteId( $siteId ) ) { |
||
421 | return []; |
||
422 | } |
||
423 | |||
424 | return array_map( |
||
425 | function( ItemId $badge ) { |
||
426 | return $badge->getSerialization(); |
||
427 | }, |
||
428 | $item->getSiteLinkList()->getBySiteId( $siteId )->getBadges() |
||
429 | ); |
||
430 | } |
||
431 | |||
432 | /** |
||
433 | * Validates badges from params and turns them into an array of ItemIds. |
||
434 | * |
||
435 | * @param string[] $badges |
||
436 | * @param Status $status |
||
437 | * |
||
438 | * @return ItemId[]|boolean |
||
439 | */ |
||
440 | private function parseBadges( array $badges, Status $status ) { |
||
441 | $badgesObjects = []; |
||
442 | |||
443 | foreach ( $badges as $badge ) { |
||
444 | try { |
||
445 | $badgeId = new ItemId( $badge ); |
||
446 | } catch ( InvalidArgumentException $ex ) { |
||
447 | $status->fatal( 'wikibase-wikibaserepopage-not-itemid', $badge ); |
||
448 | return false; |
||
449 | } |
||
450 | |||
451 | if ( !array_key_exists( $badgeId->getSerialization(), $this->badgeItems ) ) { |
||
452 | $status->fatal( 'wikibase-setsitelink-not-badge', $badgeId->getSerialization() ); |
||
453 | return false; |
||
454 | } |
||
455 | |||
456 | $itemTitle = $this->getEntityTitle( $badgeId ); |
||
457 | |||
458 | if ( $itemTitle === null || !$itemTitle->exists() ) { |
||
459 | $status->fatal( 'wikibase-wikibaserepopage-invalid-id', $badgeId ); |
||
460 | return false; |
||
461 | } |
||
462 | |||
463 | $badgesObjects[] = $badgeId; |
||
464 | } |
||
465 | |||
466 | return $badgesObjects; |
||
467 | } |
||
468 | |||
469 | /** |
||
470 | * Setting the sitepage of the entity. |
||
471 | * |
||
472 | * @param EntityDocument $item |
||
473 | * @param string $siteId |
||
474 | * @param string $pageName |
||
475 | * @param string[] $badgeIds |
||
476 | * @param Summary|null &$summary The summary for this edit will be saved here. |
||
477 | * |
||
478 | * @throws InvalidArgumentException |
||
479 | * @return Status |
||
480 | */ |
||
481 | private function setSiteLink( EntityDocument $item, $siteId, $pageName, array $badgeIds, Summary &$summary = null ) { |
||
482 | if ( !( $item instanceof Item ) ) { |
||
483 | throw new InvalidArgumentException( '$entity must be an Item' ); |
||
484 | } |
||
485 | |||
486 | $status = Status::newGood(); |
||
487 | $site = $this->siteLookup->getSite( $siteId ); |
||
488 | |||
489 | if ( $site === null ) { |
||
490 | $status->fatal( 'wikibase-setsitelink-invalid-site', $siteId ); |
||
491 | return $status; |
||
492 | } |
||
493 | |||
494 | $summary = new Summary( 'wbsetsitelink' ); |
||
495 | |||
496 | // when $pageName is an empty string, we want to remove the site link |
||
497 | if ( $pageName === '' ) { |
||
498 | if ( !$item->hasLinkToSite( $siteId ) ) { |
||
499 | $status->fatal( 'wikibase-setsitelink-remove-failed' ); |
||
500 | return $status; |
||
501 | } |
||
502 | } else { |
||
503 | $pageName = $site->normalizePageName( $pageName ); |
||
504 | |||
505 | if ( $pageName === false ) { |
||
506 | $status->fatal( 'wikibase-error-ui-no-external-page', $siteId, $this->page ); |
||
507 | return $status; |
||
508 | } |
||
509 | } |
||
510 | |||
511 | $badges = $this->parseBadges( $badgeIds, $status ); |
||
512 | |||
513 | if ( !$status->isGood() ) { |
||
514 | return $status; |
||
515 | } |
||
516 | |||
517 | $changeOp = $this->siteLinkChangeOpFactory->newSetSiteLinkOp( $siteId, $pageName, $badges ); |
||
0 ignored issues
–
show
$badges is of type false|array , but the function expects a null|array<integer,objec...taModel\Entity\ItemId>> .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() |
|||
518 | $this->applyChangeOp( $changeOp, $item, $summary ); |
||
519 | |||
520 | return $status; |
||
521 | } |
||
522 | |||
523 | } |
||
524 |
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..