Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.61% |
425 / 431 |
|
88.46% |
23 / 26 |
CRAP | |
0.00% |
0 / 1 |
SpecialNewLexeme | |
98.61% |
425 / 431 |
|
88.46% |
23 / 26 |
60 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
1 | |||
factory | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
1 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
100.00% |
72 / 72 |
|
100.00% |
1 / 1 |
3 | |||
getUrlParamsForConfig | |
100.00% |
31 / 31 |
|
100.00% |
1 / 1 |
8 | |||
getItemIdLabelDesc | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
extractLanguageCode | |
82.61% |
19 / 23 |
|
0.00% |
0 / 1 |
7.26 | |||
createExampleParameters | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
3 | |||
createTemplateParamsFromLexemeId | |
100.00% |
37 / 37 |
|
100.00% |
1 / 1 |
5 | |||
processInfoPanelTemplate | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
1 | |||
getLexicalCategorySuggestions | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
3 | |||
termToArrayForJs | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
createForm | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
2 | |||
createEntityFromFormData | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
createSummary | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
redirectToEntityPage | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
3 | |||
newEditEntity | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
saveEntity | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
getFormFields | |
100.00% |
60 / 60 |
|
100.00% |
1 / 1 |
2 | |||
setHeaders | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDescription | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
checkBlocked | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
checkBlockedOnNamespace | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
getCopyrightHTML | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
anonymousEditWarning | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace Wikibase\Lexeme\MediaWiki\Specials; |
5 | |
6 | use Exception; |
7 | use HTMLForm; |
8 | use Iterator; |
9 | use LanguageCode; |
10 | use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface; |
11 | use MediaWiki\Config\ConfigException; |
12 | use MediaWiki\Html\Html; |
13 | use MediaWiki\Html\TemplateParser; |
14 | use MediaWiki\Linker\LinkRenderer; |
15 | use MediaWiki\SpecialPage\SpecialPage; |
16 | use MediaWiki\Status\Status; |
17 | use MediaWiki\User\TempUser\TempUserConfig; |
18 | use Message; |
19 | use OOUI\IconWidget; |
20 | use UserBlockedError; |
21 | use Wikibase\DataModel\Entity\EntityDocument; |
22 | use Wikibase\DataModel\Entity\EntityId; |
23 | use Wikibase\DataModel\Entity\EntityIdParser; |
24 | use Wikibase\DataModel\Entity\EntityIdParsingException; |
25 | use Wikibase\DataModel\Entity\Item; |
26 | use Wikibase\DataModel\Entity\ItemId; |
27 | use Wikibase\DataModel\Entity\PropertyId; |
28 | use Wikibase\DataModel\Services\Lookup\EntityLookup; |
29 | use Wikibase\DataModel\Snak\PropertySomeValueSnak; |
30 | use Wikibase\DataModel\Snak\PropertyValueSnak; |
31 | use Wikibase\DataModel\Term\Term; |
32 | use Wikibase\DataModel\Term\TermFallback; |
33 | use Wikibase\DataModel\Term\TermList; |
34 | use Wikibase\DataModel\Term\TermTypes; |
35 | use Wikibase\Lexeme\DataAccess\ChangeOp\Validation\LemmaTermValidator; |
36 | use Wikibase\Lexeme\Domain\Model\Lexeme; |
37 | use Wikibase\Lexeme\MediaWiki\Specials\HTMLForm\LemmaLanguageField; |
38 | use Wikibase\Lib\FormatableSummary; |
39 | use Wikibase\Lib\SettingsArray; |
40 | use Wikibase\Lib\Store\EntityNamespaceLookup; |
41 | use Wikibase\Lib\Store\FallbackLabelDescriptionLookup; |
42 | use Wikibase\Lib\Store\FallbackLabelDescriptionLookupFactory; |
43 | use Wikibase\Lib\Summary; |
44 | use Wikibase\Repo\AnonymousEditWarningBuilder; |
45 | use Wikibase\Repo\CopyrightMessageBuilder; |
46 | use Wikibase\Repo\EditEntity\EditEntity; |
47 | use Wikibase\Repo\EditEntity\EditEntityStatus; |
48 | use Wikibase\Repo\EditEntity\MediaWikiEditEntityFactory; |
49 | use Wikibase\Repo\Specials\HTMLForm\HTMLItemReferenceField; |
50 | use Wikibase\Repo\Specials\HTMLForm\HTMLTrimmedTextField; |
51 | use Wikibase\Repo\Specials\SpecialPageCopyrightView; |
52 | use Wikibase\Repo\Store\EntityTitleStoreLookup; |
53 | use Wikibase\Repo\SummaryFormatter; |
54 | use Wikibase\Repo\Validators\ValidatorErrorLocalizer; |
55 | use Wikibase\View\EntityIdFormatterFactory; |
56 | use Wikimedia\Assert\Assert; |
57 | |
58 | /** |
59 | * New page for creating new Lexeme entities. |
60 | * |
61 | * @license GPL-2.0-or-later |
62 | */ |
63 | class SpecialNewLexeme extends SpecialPage { |
64 | |
65 | public const FIELD_LEXEME_LANGUAGE = 'lexeme-language'; |
66 | public const FIELD_LEXICAL_CATEGORY = 'lexicalcategory'; |
67 | public const FIELD_LEMMA = 'lemma'; |
68 | public const FIELD_LEMMA_LANGUAGE = 'lemma-language'; |
69 | |
70 | // used for the info panel and placeholders if the example lexeme is incomplete/missing |
71 | private const FALLBACK_LANGUAGE_LABEL = 'English'; |
72 | private const FALLBACK_LEXICAL_CATEGORY_LABEL = 'verb'; |
73 | |
74 | private $tags; |
75 | private $linkRenderer; |
76 | private $statsDataFactory; |
77 | private $editEntityFactory; |
78 | private $entityNamespaceLookup; |
79 | private $entityTitleLookup; |
80 | private $entityLookup; |
81 | private $entityIdParser; |
82 | private $summaryFormatter; |
83 | private $entityIdFormatterFactory; |
84 | private $labelDescriptionLookupFactory; |
85 | private $validatorErrorLocalizer; |
86 | private $lemmaTermValidator; |
87 | private $copyrightView; |
88 | private AnonymousEditWarningBuilder $anonymousEditWarningBuilder; |
89 | private TempUserConfig $tempUserConfig; |
90 | |
91 | public function __construct( |
92 | array $tags, |
93 | SpecialPageCopyrightView $copyrightView, |
94 | LinkRenderer $linkRenderer, |
95 | StatsdDataFactoryInterface $statsDataFactory, |
96 | MediaWikiEditEntityFactory $editEntityFactory, |
97 | EntityNamespaceLookup $entityNamespaceLookup, |
98 | EntityTitleStoreLookup $entityTitleLookup, |
99 | EntityLookup $entityLookup, |
100 | EntityIdParser $entityIdParser, |
101 | SummaryFormatter $summaryFormatter, |
102 | EntityIdFormatterFactory $entityIdFormatterFactory, |
103 | FallbackLabelDescriptionLookupFactory $labelDescriptionLookupFactory, |
104 | ValidatorErrorLocalizer $validatorErrorLocalizer, |
105 | LemmaTermValidator $lemmaTermValidator, |
106 | AnonymousEditWarningBuilder $anonymousEditWarningBuilder, |
107 | TempUserConfig $tempUserConfig |
108 | ) { |
109 | parent::__construct( |
110 | 'NewLexeme', |
111 | 'createpage' |
112 | ); |
113 | |
114 | $this->tags = $tags; |
115 | $this->linkRenderer = $linkRenderer; |
116 | $this->statsDataFactory = $statsDataFactory; |
117 | $this->editEntityFactory = $editEntityFactory; |
118 | $this->entityNamespaceLookup = $entityNamespaceLookup; |
119 | $this->entityTitleLookup = $entityTitleLookup; |
120 | $this->entityLookup = $entityLookup; |
121 | $this->entityIdParser = $entityIdParser; |
122 | $this->summaryFormatter = $summaryFormatter; |
123 | $this->entityIdFormatterFactory = $entityIdFormatterFactory; |
124 | $this->labelDescriptionLookupFactory = $labelDescriptionLookupFactory; |
125 | $this->validatorErrorLocalizer = $validatorErrorLocalizer; |
126 | $this->lemmaTermValidator = $lemmaTermValidator; |
127 | $this->copyrightView = $copyrightView; |
128 | $this->anonymousEditWarningBuilder = $anonymousEditWarningBuilder; |
129 | $this->tempUserConfig = $tempUserConfig; |
130 | } |
131 | |
132 | public static function factory( |
133 | LinkRenderer $linkRenderer, |
134 | StatsdDataFactoryInterface $statsDataFactory, |
135 | TempUserConfig $tempUserConfig, |
136 | AnonymousEditWarningBuilder $anonymousEditWarningBuilder, |
137 | MediaWikiEditEntityFactory $editEntityFactory, |
138 | EntityNamespaceLookup $entityNamespaceLookup, |
139 | EntityTitleStoreLookup $entityTitleLookup, |
140 | EntityLookup $entityLookup, |
141 | EntityIdParser $entityIdParser, |
142 | SettingsArray $repoSettings, |
143 | SummaryFormatter $summaryFormatter, |
144 | EntityIdFormatterFactory $entityIdFormatterFactory, |
145 | FallbackLabelDescriptionLookupFactory $labelDescriptionLookupFactory, |
146 | ValidatorErrorLocalizer $validatorErrorLocalizer, |
147 | LemmaTermValidator $lemmaTermValidator |
148 | ): self { |
149 | $copyrightView = new SpecialPageCopyrightView( |
150 | new CopyrightMessageBuilder(), |
151 | $repoSettings->getSetting( 'dataRightsUrl' ), |
152 | $repoSettings->getSetting( 'dataRightsText' ) |
153 | ); |
154 | |
155 | return new self( |
156 | $repoSettings->getSetting( 'specialPageTags' ), |
157 | $copyrightView, |
158 | $linkRenderer, |
159 | $statsDataFactory, |
160 | $editEntityFactory, |
161 | $entityNamespaceLookup, |
162 | $entityTitleLookup, |
163 | $entityLookup, |
164 | $entityIdParser, |
165 | $summaryFormatter, |
166 | $entityIdFormatterFactory, |
167 | $labelDescriptionLookupFactory, |
168 | $validatorErrorLocalizer, |
169 | $lemmaTermValidator, |
170 | $anonymousEditWarningBuilder, |
171 | $tempUserConfig |
172 | ); |
173 | } |
174 | |
175 | public function doesWrites(): bool { |
176 | return true; |
177 | } |
178 | |
179 | /** |
180 | * @param string|null $subPage |
181 | */ |
182 | public function execute( $subPage ): void { |
183 | $this->statsDataFactory->increment( 'wikibase.lexeme.special.NewLexeme.views' ); |
184 | |
185 | parent::execute( $subPage ); |
186 | |
187 | $this->checkBlocked(); |
188 | $this->checkBlockedOnNamespace(); |
189 | $this->checkReadOnly(); |
190 | |
191 | $output = $this->getOutput(); |
192 | $this->setHeaders(); |
193 | $searchUrl = SpecialPage::getTitleFor( 'Search' ) |
194 | ->getFullURL( [ |
195 | 'ns' . $this->getConfig()->get( 'LexemeNamespace' ) => '', |
196 | 'search' => $this->getRequest()->getText( self::FIELD_LEMMA ), |
197 | ] ); |
198 | $searchExisting = $this->msg( 'wikibaselexeme-newlexeme-search-existing' ) |
199 | ->params( $searchUrl ) |
200 | ->parse(); |
201 | $output->addHTML( |
202 | '<div id="wbl-snl-intro-text-wrapper">' |
203 | . '<p class="wbl-snl-search-existing-no-js">' . $searchExisting . '</p>' |
204 | . '</div>' |
205 | ); |
206 | $output->enableOOUI(); |
207 | $output->addHTML( $this->anonymousEditWarning() ); |
208 | $output->addHTML( '<div class="wbl-snl-main-content">' ); |
209 | $output->addHTML( '<div id="special-newlexeme-root"></div>' ); |
210 | $output->addModules( [ |
211 | 'wikibase.lexeme.special.NewLexeme', |
212 | 'wikibase.lexeme.special.NewLexeme.legacyBrowserFallback', |
213 | ] ); |
214 | $output->addModuleStyles( [ |
215 | 'wikibase.lexeme.special.NewLexeme.styles', |
216 | 'wikibase.alltargets', // T322687 |
217 | ] ); |
218 | |
219 | $exampleLexemeParams = $this->createExampleParameters(); |
220 | $form = $this->createForm( $exampleLexemeParams ); |
221 | $form->setSubmitText( $this->msg( 'wikibaselexeme-newlexeme-submit' ) ); |
222 | |
223 | // handle submit (submit callback may create form, see below) |
224 | // or show form (possibly with errors); status represents submit result |
225 | $status = $form->show(); |
226 | $output->addModuleStyles( [ |
227 | 'oojs-ui.styles.icons-content', // info icon |
228 | 'oojs-ui.styles.icons-alert', // alert icon |
229 | ] ); |
230 | $output->addHTML( |
231 | $this->processInfoPanelTemplate( $exampleLexemeParams ) |
232 | ); |
233 | $output->addHTML( '</div>' ); // .wbl-snl-main-content |
234 | $output->addHTML( |
235 | '<noscript>' |
236 | . '<style type="text/css">#special-newlexeme-root {display:none;}</style>' |
237 | . '</noscript>' |
238 | ); |
239 | |
240 | if ( $status instanceof Status && $status->isGood() ) { |
241 | // wrap it, in case HTMLForm turned it into a generic Status |
242 | $status = EditEntityStatus::wrap( $status ); |
243 | $this->redirectToEntityPage( $status ); |
244 | return; |
245 | } |
246 | |
247 | $output->addJsConfigVars( 'wblSpecialNewLexemeParams', |
248 | $this->getUrlParamsForConfig() |
249 | ); |
250 | $output->addJsConfigVars( |
251 | 'wblSpecialNewLexemeLexicalCategorySuggestions', |
252 | $this->getLexicalCategorySuggestions() |
253 | ); |
254 | $output->addJsConfigVars( 'wblSpecialNewLexemeTempUserEnabled', |
255 | $this->tempUserConfig->isEnabled() |
256 | ); |
257 | $output->addJSConfigVars( |
258 | 'wblSpecialNewLexemeExampleData', |
259 | [ |
260 | 'languageLabel' => $exampleLexemeParams['language_item_label'], |
261 | 'lexicalCategoryLabel' => $exampleLexemeParams['lexical_category_item_label'], |
262 | 'lemma' => $exampleLexemeParams['lemma_text'], |
263 | 'spellingVariant' => $exampleLexemeParams['lemma_language'], |
264 | ] |
265 | ); |
266 | } |
267 | |
268 | private function getUrlParamsForConfig(): array { |
269 | $params = []; |
270 | $lemma = $this->getRequest()->getText( self::FIELD_LEMMA ); |
271 | if ( $lemma ) { |
272 | $params['lemma'] = $lemma; |
273 | } |
274 | |
275 | $spellVarCode = $this->getRequest()->getText( self::FIELD_LEMMA_LANGUAGE ); |
276 | if ( $spellVarCode ) { |
277 | $params['spellVarCode'] = $spellVarCode; |
278 | } |
279 | |
280 | try { |
281 | $languageId = $this->entityIdParser->parse( |
282 | $this->getRequest()->getText( self::FIELD_LEXEME_LANGUAGE ) |
283 | ); |
284 | } catch ( EntityIdParsingException $e ) { |
285 | $languageId = null; |
286 | } |
287 | try { |
288 | $lexCatId = $this->entityIdParser->parse( |
289 | $this->getRequest()->getText( self::FIELD_LEXICAL_CATEGORY ) |
290 | ); |
291 | } catch ( EntityIdParsingException $e ) { |
292 | $lexCatId = null; |
293 | } |
294 | |
295 | $idsToPrefetch = array_filter( [ $languageId, $lexCatId ] ); |
296 | if ( !$idsToPrefetch ) { |
297 | return $params; |
298 | } |
299 | |
300 | $labelDescriptionLookup = $this->labelDescriptionLookupFactory->newLabelDescriptionLookup( |
301 | $this->getLanguage(), |
302 | $idsToPrefetch, |
303 | [ TermTypes::TYPE_LABEL, TermTypes::TYPE_DESCRIPTION ] |
304 | ); |
305 | |
306 | if ( $languageId ) { |
307 | $params['language'] = $this->getItemIdLabelDesc( $languageId, $labelDescriptionLookup ); |
308 | $params['language']['languageCode'] = $this->extractLanguageCode( $languageId ); |
309 | } |
310 | |
311 | if ( $lexCatId ) { |
312 | $params['lexicalCategory'] = $this->getItemIdLabelDesc( $lexCatId, $labelDescriptionLookup ); |
313 | } |
314 | |
315 | return $params; |
316 | } |
317 | |
318 | private function getItemIdLabelDesc( |
319 | EntityId $itemId, |
320 | FallbackLabelDescriptionLookup $labelDescriptionLookup |
321 | ): array { |
322 | $params = [ 'display' => [] ]; |
323 | $params['id'] = $itemId->getSerialization(); |
324 | $label = $labelDescriptionLookup->getLabel( $itemId ); |
325 | if ( $label !== null ) { |
326 | $params['display']['label'] = self::termToArrayForJs( $label ); |
327 | } |
328 | $description = $labelDescriptionLookup->getDescription( $itemId ); |
329 | if ( $description !== null ) { |
330 | $params['display']['description'] = self::termToArrayForJs( $description ); |
331 | } |
332 | |
333 | return $params; |
334 | } |
335 | |
336 | private function extractLanguageCode( EntityId $languageId ) { |
337 | $lexemeLanguageCodePropertyIdString = $this->getConfig()->get( 'LexemeLanguageCodePropertyId' ); |
338 | if ( !$lexemeLanguageCodePropertyIdString ) { |
339 | return null; |
340 | } |
341 | $languageItem = $this->entityLookup->getEntity( $languageId ); |
342 | if ( !( $languageItem instanceof Item ) ) { |
343 | return null; |
344 | } |
345 | $lexemeLanguageCodePropertyId = $this->entityIdParser->parse( |
346 | $lexemeLanguageCodePropertyIdString |
347 | ); |
348 | if ( !( $lexemeLanguageCodePropertyId instanceof PropertyId ) ) { |
349 | throw new ConfigException( |
350 | 'LexemeLanguageCodePropertyId must be a property ID, but isn’t: ' . $lexemeLanguageCodePropertyIdString |
351 | ); |
352 | } |
353 | $languageCodeStatements = $languageItem->getStatements()->getByPropertyId( |
354 | $lexemeLanguageCodePropertyId |
355 | )->getBestStatements(); |
356 | if ( !$languageCodeStatements->isEmpty() ) { |
357 | $firstBestSnak = $languageCodeStatements->getMainSnaks()[0]; |
358 | if ( $firstBestSnak instanceof PropertyValueSnak ) { |
359 | return $firstBestSnak->getDataValue()->getValue(); |
360 | } |
361 | if ( $firstBestSnak instanceof PropertySomeValueSnak ) { |
362 | return false; |
363 | } |
364 | } |
365 | return null; |
366 | } |
367 | |
368 | private function createExampleParameters(): array { |
369 | $exampleMessage = $this->msg( 'wikibaselexeme-newlexeme-info-panel-example-lexeme-id' ); |
370 | if ( $exampleMessage->exists() ) { |
371 | $lexemeIdString = trim( $exampleMessage->text() ); |
372 | } else { |
373 | $lexemeIdString = 'L1'; |
374 | } |
375 | try { |
376 | return $this->createTemplateParamsFromLexemeId( $lexemeIdString ); |
377 | } catch ( Exception $_ ) { |
378 | return [ |
379 | 'lexeme_id_HTML' => 'L1', |
380 | 'lemma_text' => 'speak', |
381 | 'lemma_language' => 'en', |
382 | 'language_item_id' => 'Q1', |
383 | 'language_item_label' => self::FALLBACK_LANGUAGE_LABEL, |
384 | 'language_link_HTML' => self::FALLBACK_LANGUAGE_LABEL, |
385 | 'lexical_category_item_id' => 'Q2', |
386 | 'lexical_category_item_label' => self::FALLBACK_LEXICAL_CATEGORY_LABEL, |
387 | 'lexical_category_link_HTML' => self::FALLBACK_LEXICAL_CATEGORY_LABEL, |
388 | ]; |
389 | } |
390 | } |
391 | |
392 | private function createTemplateParamsFromLexemeId( string $lexemeIdString ): array { |
393 | try { |
394 | $lexemeId = $this->entityIdParser->parse( $lexemeIdString ); |
395 | $lexeme = $this->entityLookup->getEntity( $lexemeId ); |
396 | } catch ( EntityIdParsingException $e ) { |
397 | $lexeme = null; |
398 | } |
399 | if ( !( $lexeme instanceof Lexeme ) ) { |
400 | throw new ConfigException( |
401 | 'MediaWiki:wikibaselexeme-newlexeme-info-panel-example-lexeme-id must be ' . |
402 | 'the ID of an existing lexeme, but isn’t: ' . $lexemeIdString |
403 | ); |
404 | } |
405 | |
406 | $lemma = $lexeme->getLemmas()->getIterator()->current(); |
407 | $lexemeIdLink = $this->linkRenderer->makeKnownLink( |
408 | $this->entityTitleLookup->getTitleForId( $lexemeId ), |
409 | $lexemeIdString |
410 | ); |
411 | |
412 | $labelDescriptionLookup = $this->labelDescriptionLookupFactory->newLabelDescriptionLookup( |
413 | $this->getLanguage(), |
414 | [ $lexeme->getLanguage(), $lexeme->getLexicalCategory() ], |
415 | [ TermTypes::TYPE_LABEL ] |
416 | ); |
417 | |
418 | $entityIdFormatter = $this->entityIdFormatterFactory->getEntityIdFormatter( $this->getLanguage() ); |
419 | $languageLabel = $labelDescriptionLookup->getLabel( $lexeme->getLanguage() ); |
420 | $lexicalCategoryLabel = $labelDescriptionLookup->getLabel( $lexeme->getLexicalCategory() ); |
421 | |
422 | return [ |
423 | 'lexeme_id_HTML' => $lexemeIdLink, |
424 | 'lemma_text' => $lemma->getText(), |
425 | 'lemma_language' => $lemma->getLanguageCode(), |
426 | 'language_item_id' => $lexeme->getLanguage()->getSerialization(), |
427 | 'language_item_label' => $languageLabel ? |
428 | $languageLabel->getText() : |
429 | self::FALLBACK_LANGUAGE_LABEL, |
430 | 'language_link_HTML' => $entityIdFormatter->formatEntityId( $lexeme->getLanguage() ), |
431 | 'lexical_category_item_id' => $lexeme->getLexicalCategory()->getSerialization(), |
432 | 'lexical_category_item_label' => $lexicalCategoryLabel ? |
433 | $lexicalCategoryLabel->getText() : |
434 | self::FALLBACK_LEXICAL_CATEGORY_LABEL, |
435 | 'lexical_category_link_HTML' => $entityIdFormatter->formatEntityId( $lexeme->getLexicalCategory() ), |
436 | ]; |
437 | } |
438 | |
439 | private function processInfoPanelTemplate( array $params ): string { |
440 | $staticTemplateParams = [ |
441 | 'header' => $this->msg( 'wikibaselexeme-newlexeme-info-panel-heading' )->text(), |
442 | 'lexicographical-data_HTML' => $this->msg( |
443 | 'wikibaselexeme-newlexeme-info-panel-lexicographical-data' |
444 | )->parse(), |
445 | 'no-general-data_HTML' => $this->msg( 'wikibaselexeme-newlexeme-info-panel-no-general-data' )->parse(), |
446 | 'info_icon_HTML' => ( new IconWidget( [ 'icon' => 'infoFilled' ] ) )->toString(), |
447 | 'language_label' => $this->msg( 'wikibaselexeme-field-language-label' )->text(), |
448 | 'lexical_category_label' => $this->msg( |
449 | 'wikibaselexeme-field-lexical-category-label' |
450 | )->text(), |
451 | 'colon_separator' => $this->msg( 'colon-separator' )->text(), |
452 | ]; |
453 | $params['lemma_language_HTML'] = LanguageCode::bcp47( $params['lemma_language'] ); |
454 | |
455 | return ( new TemplateParser( __DIR__ ) )->processTemplate( |
456 | 'SpecialNewLexeme-infopanel', |
457 | $staticTemplateParams + $params |
458 | ); |
459 | } |
460 | |
461 | /** |
462 | * Get the suggested lexical category items with their labels and descriptions. |
463 | * |
464 | * @return array[] |
465 | */ |
466 | private function getLexicalCategorySuggestions(): array { |
467 | $itemIds = array_map( |
468 | [ $this->entityIdParser, 'parse' ], |
469 | $this->getConfig()->get( 'LexemeLexicalCategoryItemIds' ) |
470 | ); |
471 | $labelDescriptionLookup = $this->labelDescriptionLookupFactory->newLabelDescriptionLookup( |
472 | $this->getLanguage(), |
473 | $itemIds, // prefetch labels and descriptions of all these item IDs |
474 | [ TermTypes::TYPE_LABEL, TermTypes::TYPE_DESCRIPTION ] |
475 | ); |
476 | |
477 | return array_map( static function ( EntityId $entityId ) use ( $labelDescriptionLookup ) { |
478 | $label = $labelDescriptionLookup->getLabel( $entityId ); |
479 | $description = $labelDescriptionLookup->getDescription( $entityId ); |
480 | $suggestion = [ |
481 | 'id' => $entityId->getSerialization(), |
482 | 'display' => [], |
483 | ]; |
484 | if ( $label !== null ) { |
485 | $suggestion['display']['label'] = self::termToArrayForJs( $label ); |
486 | } |
487 | if ( $description !== null ) { |
488 | $suggestion['display']['description'] = self::termToArrayForJs( $description ); |
489 | } |
490 | return $suggestion; |
491 | }, $itemIds ); |
492 | } |
493 | |
494 | private static function termToArrayForJs( TermFallback $term ): array { |
495 | return [ |
496 | 'language' => LanguageCode::bcp47( $term->getActualLanguageCode() ), |
497 | 'value' => $term->getText(), |
498 | ]; |
499 | } |
500 | |
501 | private function createForm( array $exampleLexemeParams ): HTMLForm { |
502 | return HTMLForm::factory( 'ooui', $this->getFormFields( $exampleLexemeParams ), $this->getContext() ) |
503 | ->setSubmitCallback( |
504 | function ( $data, HTMLForm $form ) { |
505 | // $data is already validated at this point (according to the field definitions) |
506 | |
507 | $entity = $this->createEntityFromFormData( $data ); |
508 | |
509 | $summary = $this->createSummary( $entity ); |
510 | |
511 | $saveStatus = $this->saveEntity( |
512 | $entity, |
513 | $summary, |
514 | $form->getRequest()->getVal( 'wpEditToken' ) |
515 | ); |
516 | |
517 | if ( !$saveStatus->isGood() ) { |
518 | return $saveStatus; |
519 | } |
520 | |
521 | $this->statsDataFactory->increment( 'wikibase.lexeme.special.NewLexeme.nojs.create' ); |
522 | |
523 | return $saveStatus; |
524 | } |
525 | )->addPreHtml( '<noscript>' ) |
526 | ->addPostHtml( '</noscript>' ); |
527 | } |
528 | |
529 | private function createEntityFromFormData( array $formData ): Lexeme { |
530 | $entity = new Lexeme(); |
531 | $lemmaLanguage = $formData[self::FIELD_LEMMA_LANGUAGE]; |
532 | |
533 | $lemmas = new TermList( [ new Term( $lemmaLanguage, $formData[self::FIELD_LEMMA] ) ] ); |
534 | $entity->setLemmas( $lemmas ); |
535 | |
536 | $entity->setLexicalCategory( new ItemId( $formData[self::FIELD_LEXICAL_CATEGORY] ) ); |
537 | |
538 | $entity->setLanguage( new ItemId( $formData[self::FIELD_LEXEME_LANGUAGE] ) ); |
539 | |
540 | return $entity; |
541 | } |
542 | |
543 | private function createSummary( Lexeme $lexeme ): Summary { |
544 | $uiLanguageCode = $this->getLanguage()->getCode(); |
545 | |
546 | $summary = new Summary( 'wbeditentity', 'create' ); |
547 | $summary->setLanguage( $uiLanguageCode ); |
548 | |
549 | $lemmaIterator = $lexeme->getLemmas()->getIterator(); |
550 | // As getIterator can also in theory return a Traversable, guard against that |
551 | Assert::invariant( |
552 | $lemmaIterator instanceof Iterator, |
553 | 'TermList::getIterator did not return an instance of Iterator' |
554 | ); |
555 | /** @var Term|null $lemmaTerm */ |
556 | $lemmaTerm = $lemmaIterator->current(); |
557 | $summary->addAutoSummaryArgs( $lemmaTerm->getText() ); |
558 | |
559 | return $summary; |
560 | } |
561 | |
562 | private function redirectToEntityPage( EditEntityStatus $status ) { |
563 | $entity = $status->getRevision()->getEntity(); |
564 | $title = $this->entityTitleLookup->getTitleForId( $entity->getId() ); |
565 | $savedTempUser = $status->getSavedTempUser(); |
566 | $redirectUrl = ''; |
567 | if ( $savedTempUser !== null ) { |
568 | $this->getHookRunner()->onTempUserCreatedRedirect( |
569 | $this->getRequest()->getSession(), |
570 | $savedTempUser, |
571 | $title->getPrefixedDBkey(), |
572 | '', |
573 | '', |
574 | $redirectUrl |
575 | ); |
576 | } |
577 | if ( !$redirectUrl ) { |
578 | $redirectUrl = $title->getFullURL(); |
579 | } |
580 | $this->getOutput()->redirect( $redirectUrl ); |
581 | } |
582 | |
583 | private function newEditEntity(): EditEntity { |
584 | return $this->editEntityFactory->newEditEntity( |
585 | $this->getContext(), |
586 | null, |
587 | 0, |
588 | $this->getRequest()->wasPosted() |
589 | ); |
590 | } |
591 | |
592 | private function saveEntity( |
593 | EntityDocument $entity, |
594 | FormatableSummary $summary, |
595 | string $token |
596 | ): EditEntityStatus { |
597 | return $this->newEditEntity()->attemptSave( |
598 | $entity, |
599 | $this->summaryFormatter->formatSummary( $summary ), |
600 | EDIT_NEW, |
601 | $token, |
602 | null, |
603 | $this->tags |
604 | ); |
605 | } |
606 | |
607 | private function getFormFields( array $exampleLexemeParams ): array { |
608 | return [ |
609 | self::FIELD_LEMMA => [ |
610 | 'name' => self::FIELD_LEMMA, |
611 | 'class' => HTMLTrimmedTextField::class, |
612 | 'id' => 'wb-newlexeme-lemma', |
613 | 'required' => true, |
614 | 'placeholder-message' => [ |
615 | 'wikibaselexeme-newlexeme-lemma-placeholder-with-example', |
616 | Message::plaintextParam( $exampleLexemeParams['lemma_text'] ), |
617 | ], |
618 | 'label-message' => 'wikibaselexeme-newlexeme-lemma', |
619 | 'validation-callback' => function ( string $lemma ) { |
620 | $result = $this->lemmaTermValidator->validate( $lemma ); |
621 | return $result->isValid() ?: |
622 | $this->validatorErrorLocalizer->getErrorMessage( $result->getErrors()[0] ); |
623 | }, |
624 | ], |
625 | self::FIELD_LEMMA_LANGUAGE => [ |
626 | 'name' => self::FIELD_LEMMA_LANGUAGE, |
627 | 'class' => LemmaLanguageField::class, |
628 | 'cssclass' => 'lemma-language', |
629 | 'id' => 'wb-newlexeme-lemma-language', |
630 | 'label-message' => 'wikibaselexeme-newlexeme-lemma-language', |
631 | 'placeholder-message' => [ |
632 | 'wikibaselexeme-newlexeme-lemma-language-placeholder-with-example', |
633 | Message::plaintextParam( $exampleLexemeParams['lemma_language'] ), |
634 | ], |
635 | ], |
636 | self::FIELD_LEXEME_LANGUAGE => [ |
637 | 'name' => self::FIELD_LEXEME_LANGUAGE, |
638 | 'labelFieldName' => self::FIELD_LEXEME_LANGUAGE . '-label', |
639 | 'class' => HTMLItemReferenceField::class, |
640 | 'id' => 'wb-newlexeme-lexeme-language', |
641 | 'label-message' => 'wikibaselexeme-newlexeme-language', |
642 | 'required' => true, |
643 | 'placeholder-message' => [ |
644 | 'wikibaselexeme-newlexeme-language-placeholder-with-example', |
645 | Message::plaintextParam( $exampleLexemeParams['language_item_id'] ), |
646 | ], |
647 | ], |
648 | self::FIELD_LEXICAL_CATEGORY => [ |
649 | 'name' => self::FIELD_LEXICAL_CATEGORY, |
650 | 'labelFieldName' => self::FIELD_LEXICAL_CATEGORY . '-label', |
651 | 'class' => HTMLItemReferenceField::class, |
652 | 'id' => 'wb-newlexeme-lexicalCategory', |
653 | 'label-message' => 'wikibaselexeme-newlexeme-lexicalcategory', |
654 | 'required' => true, |
655 | 'placeholder-message' => [ |
656 | 'wikibaselexeme-newlexeme-lexicalcategory-placeholder-with-example', |
657 | Message::plaintextParam( $exampleLexemeParams['lexical_category_item_id'] ), |
658 | ], |
659 | ], |
660 | 'copyright-message' => [ |
661 | 'name' => 'copyright-message', |
662 | 'type' => 'info', |
663 | 'raw' => true, |
664 | 'id' => 'wb-newlexeme-copyright', |
665 | 'default' => $this->getCopyrightHTML(), |
666 | ], |
667 | ]; |
668 | } |
669 | |
670 | public function setHeaders(): void { |
671 | $out = $this->getOutput(); |
672 | $out->setPageTitleMsg( $this->getDescription() ); |
673 | } |
674 | |
675 | /** @see \Wikibase\Repo\Specials\SpecialWikibasePage::getGroupName() */ |
676 | protected function getGroupName(): string { |
677 | return 'wikibase'; |
678 | } |
679 | |
680 | public function getDescription(): Message { |
681 | return $this->msg( 'special-newlexeme' ); |
682 | } |
683 | |
684 | /** |
685 | * @throws UserBlockedError |
686 | */ |
687 | private function checkBlocked(): void { |
688 | $block = $this->getUser()->getBlock(); |
689 | if ( $block && $block->isSitewide() ) { |
690 | throw new UserBlockedError( $block ); |
691 | } |
692 | } |
693 | |
694 | /** |
695 | * @throws UserBlockedError |
696 | */ |
697 | private function checkBlockedOnNamespace(): void { |
698 | $namespace = $this->entityNamespaceLookup->getEntityNamespace( Lexeme::ENTITY_TYPE ); |
699 | $block = $this->getUser()->getBlock(); |
700 | if ( $block && $block->appliesToNamespace( $namespace ) ) { |
701 | throw new UserBlockedError( $block ); |
702 | } |
703 | } |
704 | |
705 | /** |
706 | * @return string HTML |
707 | */ |
708 | private function getCopyrightHTML() { |
709 | return $this->copyrightView->getHtml( |
710 | $this->getLanguage(), |
711 | 'wikibaselexeme-newlexeme-submit' |
712 | ); |
713 | } |
714 | |
715 | /** |
716 | * @return string HTML |
717 | */ |
718 | private function anonymousEditWarning() { |
719 | $warningIconHtml = ( new IconWidget( [ 'icon' => 'alert' ] ) )->toString(); |
720 | |
721 | if ( !$this->getUser()->isRegistered() ) { |
722 | $fullTitle = $this->getPageTitle(); |
723 | $messageSpan = Html::rawElement( |
724 | 'span', |
725 | [ 'class' => 'warning' ], |
726 | $this->anonymousEditWarningBuilder->buildAnonymousEditWarningHTML( $fullTitle->getPrefixedText() ) |
727 | ); |
728 | return '<noscript> |
729 | <div class="wbl-snl-anonymous-edit-warning-no-js wbl-snl-message-warning">' |
730 | . $warningIconHtml |
731 | . $messageSpan |
732 | . '</div></noscript>'; |
733 | } |
734 | |
735 | return ''; |
736 | } |
737 | } |