SpecialSetSiteLink   C
last analyzed

Complexity

Total Complexity 56

Size/Duplication

Total Lines 493
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 18

Importance

Changes 0
Metric Value
wmc 56
lcom 1
cbo 18
dl 0
loc 493
rs 5.5199
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 27 1
A doesWrites() 0 3 1
B processArguments() 0 44 9
A validateInput() 0 15 4
A modifyEntity() 0 15 3
A isValidSiteId() 0 4 2
C getForm() 0 76 12
A getMultiSelectForBadges() 0 37 4
A getSiteLink() 0 7 3
A getBadges() 0 12 3
B parseBadges() 0 28 6
B setSiteLink() 0 41 7
A factory() 0 31 1

How to fix   Complexity   

Complex Class

Complex classes like SpecialSetSiteLink often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SpecialSetSiteLink, and based on these observations, apply Extract Interface, too.

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
Documentation Bug introduced by
It seems like array_intersect(array_ke...ray('badges', array())) of type array<integer,integer> is incompatible with the declared type array<integer,string> of property $badges.

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..

Loading history...
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
Documentation introduced by
$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);
Loading history...
518
		$this->applyChangeOp( $changeOp, $item, $summary );
519
520
		return $status;
521
	}
522
523
}
524