SpecialSetLabelDescriptionAliases::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.584
c 0
b 0
f 0
cc 1
nc 1
nop 7
1
<?php
2
3
namespace Wikibase\Repo\Specials;
4
5
use Html;
6
use HTMLForm;
7
use InvalidArgumentException;
8
use Language;
9
use MediaWiki\Logger\LoggerFactory;
10
use Wikibase\DataModel\Entity\EntityDocument;
11
use Wikibase\DataModel\Term\Fingerprint;
12
use Wikibase\DataModel\Term\FingerprintProvider;
13
use Wikibase\Lib\ContentLanguages;
14
use Wikibase\Lib\Store\EntityTitleLookup;
15
use Wikibase\Lib\Summary;
16
use Wikibase\Lib\UserInputException;
17
use Wikibase\Repo\ChangeOp\ChangeOp;
18
use Wikibase\Repo\ChangeOp\ChangeOpException;
19
use Wikibase\Repo\ChangeOp\ChangeOps;
20
use Wikibase\Repo\ChangeOp\FingerprintChangeOpFactory;
21
use Wikibase\Repo\CopyrightMessageBuilder;
22
use Wikibase\Repo\EditEntity\MediawikiEditEntityFactory;
23
use Wikibase\Repo\Store\EntityPermissionChecker;
24
use Wikibase\Repo\SummaryFormatter;
25
use Wikibase\Repo\WikibaseRepo;
26
27
/**
28
 * Special page for setting label, description and aliases of a Wikibase entity that features
29
 * labels, descriptions and aliases.
30
 *
31
 * @license GPL-2.0-or-later
32
 * @author Thiemo Kreuz
33
 */
34
class SpecialSetLabelDescriptionAliases extends SpecialModifyEntity {
0 ignored issues
show
Bug introduced by
There is one abstract method getName in this class; you could implement it, or declare this class as abstract.
Loading history...
35
36
	use ParameterizedDescriptionTrait;
37
38
	/**
39
	 * @var FingerprintChangeOpFactory
40
	 */
41
	private $changeOpFactory;
42
43
	/**
44
	 * @var ContentLanguages
45
	 */
46
	private $termsLanguages;
47
48
	/**
49
	 * @var EntityPermissionChecker
50
	 */
51
	private $permissionChecker;
52
53
	/**
54
	 * @var string
55
	 */
56
	private $languageCode;
57
58
	/**
59
	 * @var string
60
	 */
61
	private $label = '';
62
63
	/**
64
	 * @var string
65
	 */
66
	private $description = '';
67
68
	/**
69
	 * @var string[]
70
	 */
71
	private $aliases = [];
72
73
	public function __construct(
74
		SpecialPageCopyrightView $copyrightView,
75
		SummaryFormatter $summaryFormatter,
76
		EntityTitleLookup $entityTitleLookup,
77
		MediawikiEditEntityFactory $editEntityFactory,
78
		FingerprintChangeOpFactory $changeOpFactory,
79
		ContentLanguages $termsLanguages,
80
		EntityPermissionChecker $permissionChecker
81
	) {
82
		parent::__construct(
83
			'SetLabelDescriptionAliases',
84
			$copyrightView,
85
			$summaryFormatter,
86
			$entityTitleLookup,
87
			$editEntityFactory
88
		);
89
90
		$this->changeOpFactory = $changeOpFactory;
91
		$this->termsLanguages = $termsLanguages;
92
		$this->permissionChecker = $permissionChecker;
93
	}
94
95
	public static function factory(): self {
96
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
97
98
		$settings = $wikibaseRepo->getSettings();
99
		$copyrightView = new SpecialPageCopyrightView(
100
			new CopyrightMessageBuilder(),
101
			$settings->getSetting( 'dataRightsUrl' ),
102
			$settings->getSetting( 'dataRightsText' )
103
		);
104
105
		return new self(
106
			$copyrightView,
107
			$wikibaseRepo->getSummaryFormatter(),
108
			$wikibaseRepo->getEntityTitleLookup(),
109
			$wikibaseRepo->newEditEntityFactory(),
110
			$wikibaseRepo->getChangeOpFactoryProvider()->getFingerprintChangeOpFactory(),
111
			$wikibaseRepo->getTermsLanguages(),
112
			$wikibaseRepo->getEntityPermissionChecker()
113
		);
114
	}
115
116
	public function doesWrites() {
117
		return true;
118
	}
119
120
	/**
121
	 * @see SpecialModifyEntity::validateInput
122
	 *
123
	 * @return bool
124
	 */
125
	protected function validateInput() {
126
		return parent::validateInput()
127
			&& $this->getBaseRevision()->getEntity() instanceof FingerprintProvider
128
			&& $this->isValidLanguageCode( $this->languageCode )
129
			&& $this->wasPostedWithLabelDescriptionOrAliases()
130
			&& $this->isAllowedToChangeTerms( $this->getBaseRevision()->getEntity() );
131
	}
132
133
	/**
134
	 * @return bool
135
	 */
136
	private function wasPostedWithLabelDescriptionOrAliases() {
137
		$request = $this->getRequest();
138
139
		return $request->wasPosted() && (
140
			$request->getCheck( 'label' )
141
			|| $request->getCheck( 'description' )
142
			|| $request->getCheck( 'aliases' )
143
		);
144
	}
145
146
	/**
147
	 * @param EntityDocument $entity
148
	 *
149
	 * @return bool
150
	 */
151
	private function isAllowedToChangeTerms( EntityDocument $entity ) {
152
		$status = $this->permissionChecker->getPermissionStatusForEntity(
153
			$this->getUser(),
154
			EntityPermissionChecker::ACTION_EDIT_TERMS,
155
			$entity
156
		);
157
158
		if ( !$status->isOK() ) {
159
			$this->showErrorHTML( $this->msg( 'permissionserrors' )->parse() );
160
			return false;
161
		}
162
163
		return true;
164
	}
165
166
	/**
167
	 * @see SpecialModifyEntity::getForm
168
	 *
169
	 * @param EntityDocument|null $entity
170
	 *
171
	 * @return HTMLForm
172
	 */
173
	protected function getForm( EntityDocument $entity = null ) {
174
		if ( $entity !== null && $this->languageCode !== null ) {
175
176
			$languageName = Language::fetchLanguageName(
177
				$this->languageCode, $this->getLanguage()->getCode()
178
			);
179
			$intro = $this->msg(
180
				'wikibase-setlabeldescriptionaliases-introfull',
181
				$this->getEntityTitle( $entity->getId() )->getPrefixedText(),
182
				$languageName
183
			)->parse();
184
185
			$formDescriptor = [
186
				'id' => [
187
					'name' => 'id',
188
					'type' => 'hidden',
189
					'default' => $entity->getId()->getSerialization()
190
				],
191
				'language' => [
192
					'name' => 'language',
193
					'type' => 'hidden',
194
					'default' => $this->languageCode
195
				],
196
				'revid' => [
197
					'name' => 'revid',
198
					'type' => 'hidden',
199
					'default' => $this->getBaseRevision()->getRevisionId(),
200
				]
201
			];
202
			$formDescriptor = array_merge(
203
				$formDescriptor,
204
				$this->getLabeledInputField( 'label', $this->label ),
205
				$this->getLabeledInputField( 'description', $this->description ),
206
				$this->getLabeledInputField( 'aliases', implode( '|', $this->aliases ) )
207
			);
208
		} else {
209
			$intro = $this->msg( 'wikibase-setlabeldescriptionaliases-intro' )->parse();
210
			$fieldId = 'wikibase-setlabeldescriptionaliases-language';
211
			$languageCode = $this->languageCode ?: $this->getLanguage()->getCode();
212
213
			$formDescriptor = $this->getFormElements( $entity );
214
			$formDescriptor['language'] = [
215
				'name' => 'language',
216
				'default' => $languageCode,
217
				'type' => 'text',
218
				'id' => $fieldId,
219
				'label-message' => 'wikibase-modifyterm-language'
220
			];
221
		}
222
223
		return HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
224
			->setHeaderText( Html::rawElement( 'p', [], $intro ) );
225
	}
226
227
	/**
228
	 * Returns an HTML label and text input element for a specific term.
229
	 *
230
	 * @param string $termType Either 'label', 'description' or 'aliases'.
231
	 * @param string $value Text to fill the input element with
232
	 *
233
	 * @return array[]
234
	 */
235
	private function getLabeledInputField( $termType, $value ) {
236
		$fieldId = 'wikibase-setlabeldescriptionaliases-' . $termType;
237
238
		// Messages:
239
		// wikibase-setlabeldescriptionaliases-label-label
240
		// wikibase-setlabeldescriptionaliases-description-label
241
		// wikibase-setlabeldescriptionaliases-aliases-label
242
		return [
243
			$termType => [
244
				'name' => $termType,
245
				'default' => $value,
246
				'type' => 'text',
247
				'id' => $fieldId,
248
				'placeholder' => $value,
249
				'label-message' => $fieldId . '-label'
250
			]
251
		];
252
	}
253
254
	/**
255
	 * @see SpecialModifyEntity::processArguments
256
	 *
257
	 * @param string|null $subPage
258
	 */
259
	protected function processArguments( $subPage ) {
260
		$this->extractInput( $subPage );
261
262
		// Parse the 'id' parameter and throw an exception if the entity cannot be loaded
263
		parent::processArguments( $subPage );
264
265
		if ( $this->languageCode === '' ) {
266
			$this->languageCode = $this->getLanguage()->getCode();
267
		} elseif ( !$this->isValidLanguageCode( $this->languageCode ) ) {
268
			$msg = $this->msg( 'wikibase-wikibaserepopage-invalid-langcode' )
269
				->plaintextParams( $this->languageCode );
270
271
			$this->showErrorHTML( $msg->parse() );
272
			$this->languageCode = null;
273
		}
274
275
		$entity = $this->getEntityForDisplay();
276
		if ( $this->languageCode !== null && $entity ) {
277
			if ( $entity instanceof FingerprintProvider ) {
278
				$this->setFingerprintFields( $entity->getFingerprint() );
279
			}
280
		}
281
	}
282
283
	/**
284
	 * @param string|null $subPage
285
	 */
286
	private function extractInput( $subPage ) {
287
		$request = $this->getRequest();
288
289
		$parts = $subPage === '' ? [] : explode( '/', $subPage, 2 );
290
		$this->languageCode = $request->getRawVal( 'language', $parts[1] ?? '' );
291
292
		$label = $request->getVal( 'label', '' );
293
		$this->label = $this->stringNormalizer->trimToNFC( $label );
294
295
		$description = $request->getVal( 'description', '' );
296
		$this->description = $this->stringNormalizer->trimToNFC( $description );
297
298
		$aliases = $request->getVal( 'aliases', '' );
299
		$aliases = $this->stringNormalizer->trimToNFC( $aliases );
300
		$this->aliases = $aliases === '' ? [] : explode( '|', $aliases );
301
		foreach ( $this->aliases as &$alias ) {
302
			$alias = $this->stringNormalizer->trimToNFC( $alias );
303
		}
304
	}
305
306
	private function setFingerprintFields( Fingerprint $fingerprint ) {
307
		if ( !$this->getRequest()->getCheck( 'label' )
308
			&& $fingerprint->hasLabel( $this->languageCode )
309
		) {
310
			$this->label = $fingerprint->getLabel( $this->languageCode )->getText();
311
		}
312
313
		if ( !$this->getRequest()->getCheck( 'description' )
314
			&& $fingerprint->hasDescription( $this->languageCode )
315
		) {
316
			$this->description = $fingerprint->getDescription( $this->languageCode )->getText();
317
		}
318
319
		if ( !$this->getRequest()->getCheck( 'aliases' )
320
			&& $fingerprint->hasAliasGroup( $this->languageCode )
321
		) {
322
			$this->aliases = $fingerprint->getAliasGroup( $this->languageCode )->getAliases();
323
		}
324
	}
325
326
	/**
327
	 * @param string|null $languageCode
328
	 *
329
	 * @return bool
330
	 */
331
	private function isValidLanguageCode( $languageCode ) {
332
		return $languageCode !== null && $this->termsLanguages->hasLanguage( $languageCode );
333
	}
334
335
	/**
336
	 * @see SpecialModifyEntity::modifyEntity
337
	 *
338
	 * @param EntityDocument $entity
339
	 *
340
	 * @throws InvalidArgumentException
341
	 * @return Summary|bool
342
	 */
343
	protected function modifyEntity( EntityDocument $entity ) {
344
		if ( !( $entity instanceof FingerprintProvider ) ) {
345
			throw new InvalidArgumentException( '$entity must be a FingerprintProvider' );
346
		}
347
348
		if ( $this->assertNoPipeCharacterInAliases( $entity->getFingerprint() ) ) {
349
			$logger = LoggerFactory::getInstance( 'Wikibase' );
350
			$logger->error( 'Special:SpecialSetLabelDescriptionAliases attempt to save pipes in aliases' );
351
			$this->showErrorHTML( $this->msg( 'wikibase-wikibaserepopage-pipe-in-alias' )->parse() );
352
			return false;
353
		}
354
		$changeOps = $this->getChangeOps( $entity->getFingerprint() );
355
356
		if ( empty( $changeOps ) ) {
357
			return false;
358
		}
359
360
		try {
361
			// @phan-suppress-next-line PhanTypeMismatchArgument
362
			return $this->applyChangeOpList( $changeOps, $entity );
363
		} catch ( ChangeOpException $ex ) {
364
			$this->showErrorHTML( $ex->getMessage() );
365
			return false;
366
		}
367
	}
368
369
	/**
370
	 * @param Fingerprint $fingerprint
371
	 *
372
	 * @throws UserInputException
373
	 * @return bool
374
	 */
375
	private function assertNoPipeCharacterInAliases( Fingerprint $fingerprint ) {
376
		if ( !empty( $this->aliases ) ) {
377
			if ( $fingerprint->hasAliasGroup( $this->languageCode )
378
			) {
379
				$aliasesInLang = $fingerprint->getAliasGroup( $this->languageCode )->getAliases();
380
				foreach ( $aliasesInLang as $alias ) {
381
					if ( strpos( $alias, '|' ) !== false ) {
382
						return true;
383
384
					}
385
				}
386
			}
387
		}
388
389
		return false;
390
	}
391
392
	/**
393
	 * @throws ChangeOpException
394
	 */
395
	private function applyChangeOpList( array $changeOps, EntityDocument $entity ): Summary {
396
		$changeOp = $this->changeOpFactory->newFingerprintChangeOp( new ChangeOps( $changeOps ) );
397
		/**
398
		 * XXX: The $changeOps array is still used below as it is indexed with the
399
		 * module name to pass to the Summary object.
400
		 */
401
		if ( count( $changeOps ) === 1 ) {
402
			$module = key( $changeOps );
403
			$summary = new Summary( $module );
404
			$this->applyChangeOp( $changeOp, $entity, $summary );
405
			return $summary;
406
		} else {
407
			$this->applyChangeOp( $changeOp, $entity, new Summary() );
408
			return $this->getSummaryForLabelDescriptionAliases();
409
		}
410
	}
411
412
	/**
413
	 * @param Fingerprint $fingerprint
414
	 *
415
	 * @return ChangeOp[]
416
	 */
417
	private function getChangeOps( Fingerprint $fingerprint ) {
418
		$changeOpFactory = $this->changeOpFactory;
419
		$changeOps = [];
420
421
		if ( $this->label !== '' ) {
422
			if ( !$fingerprint->hasLabel( $this->languageCode )
423
				|| $fingerprint->getLabel( $this->languageCode )->getText() !== $this->label
424
			) {
425
				$changeOps['wbsetlabel'] = $changeOpFactory->newSetLabelOp(
426
					$this->languageCode,
427
					$this->label
428
				);
429
			}
430
		} elseif ( $fingerprint->hasLabel( $this->languageCode ) ) {
431
			$changeOps['wbsetlabel'] = $changeOpFactory->newRemoveLabelOp(
432
				$this->languageCode
433
			);
434
		}
435
436
		if ( $this->description !== '' ) {
437
			if ( !$fingerprint->hasDescription( $this->languageCode )
438
				|| $fingerprint->getDescription( $this->languageCode )->getText() !== $this->description
439
			) {
440
				$changeOps['wbsetdescription'] = $changeOpFactory->newSetDescriptionOp(
441
					$this->languageCode,
442
					$this->description
443
				);
444
			}
445
		} elseif ( $fingerprint->hasDescription( $this->languageCode ) ) {
446
			$changeOps['wbsetdescription'] = $changeOpFactory->newRemoveDescriptionOp(
447
				$this->languageCode
448
			);
449
		}
450
451
		if ( !empty( $this->aliases ) ) {
452
			if ( !$fingerprint->hasAliasGroup( $this->languageCode )
453
				|| $fingerprint->getAliasGroup( $this->languageCode )->getAliases() !== $this->aliases
454
			) {
455
				$changeOps['wbsetaliases'] = $changeOpFactory->newSetAliasesOp(
456
					$this->languageCode,
457
					$this->aliases
458
				);
459
			}
460
		} elseif ( $fingerprint->hasAliasGroup( $this->languageCode ) ) {
461
			$changeOps['wbsetaliases'] = $changeOpFactory->newRemoveAliasesOp(
462
				$this->languageCode,
463
				$fingerprint->getAliasGroup( $this->languageCode )->getAliases()
464
			);
465
		}
466
467
		return $changeOps;
468
	}
469
470
	/**
471
	 * @return Summary
472
	 */
473
	private function getSummaryForLabelDescriptionAliases() {
474
		// FIXME: Introduce more specific messages if only 2 of the 3 fields changed.
475
		$summary = new Summary( 'wbsetlabeldescriptionaliases' );
476
		$summary->addAutoSummaryArgs( $this->label, $this->description, $this->aliases );
477
478
		$summary->setLanguage( $this->languageCode );
479
		return $summary;
480
	}
481
482
}
483