Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.59% |
421 / 427 |
|
88.46% |
23 / 26 |
CRAP | |
0.00% |
0 / 1 |
SpecialNewLexeme | |
98.59% |
421 / 427 |
|
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% |
73 / 73 |
|
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% |
35 / 35 |
|
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% |
18 / 18 |
|
100.00% |
1 / 1 |
2 | |||
createEntityFromFormData | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
createSummary | |
100.00% |
7 / 7 |
|
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 MediaWiki\Config\ConfigException; |
8 | use MediaWiki\Exception\UserBlockedError; |
9 | use MediaWiki\Html\Html; |
10 | use MediaWiki\Html\TemplateParser; |
11 | use MediaWiki\HTMLForm\HTMLForm; |
12 | use MediaWiki\Language\LanguageCode; |
13 | use MediaWiki\Linker\LinkRenderer; |
14 | use MediaWiki\Message\Message; |
15 | use MediaWiki\SpecialPage\SpecialPage; |
16 | use MediaWiki\Status\Status; |
17 | use MediaWiki\User\TempUser\TempUserConfig; |
18 | use OOUI\IconWidget; |
19 | use Wikibase\DataModel\Entity\EntityDocument; |
20 | use Wikibase\DataModel\Entity\EntityId; |
21 | use Wikibase\DataModel\Entity\EntityIdParser; |
22 | use Wikibase\DataModel\Entity\EntityIdParsingException; |
23 | use Wikibase\DataModel\Entity\Item; |
24 | use Wikibase\DataModel\Entity\ItemId; |
25 | use Wikibase\DataModel\Entity\PropertyId; |
26 | use Wikibase\DataModel\Services\Lookup\EntityLookup; |
27 | use Wikibase\DataModel\Snak\PropertySomeValueSnak; |
28 | use Wikibase\DataModel\Snak\PropertyValueSnak; |
29 | use Wikibase\DataModel\Term\Term; |
30 | use Wikibase\DataModel\Term\TermFallback; |
31 | use Wikibase\DataModel\Term\TermList; |
32 | use Wikibase\DataModel\Term\TermTypes; |
33 | use Wikibase\Lexeme\DataAccess\ChangeOp\Validation\LemmaTermValidator; |
34 | use Wikibase\Lexeme\Domain\Model\Lexeme; |
35 | use Wikibase\Lexeme\MediaWiki\Specials\HTMLForm\LemmaLanguageField; |
36 | use Wikibase\Lib\FormatableSummary; |
37 | use Wikibase\Lib\SettingsArray; |
38 | use Wikibase\Lib\Store\EntityNamespaceLookup; |
39 | use Wikibase\Lib\Store\FallbackLabelDescriptionLookup; |
40 | use Wikibase\Lib\Store\FallbackLabelDescriptionLookupFactory; |
41 | use Wikibase\Lib\Summary; |
42 | use Wikibase\Repo\AnonymousEditWarningBuilder; |
43 | use Wikibase\Repo\CopyrightMessageBuilder; |
44 | use Wikibase\Repo\EditEntity\EditEntity; |
45 | use Wikibase\Repo\EditEntity\EditEntityStatus; |
46 | use Wikibase\Repo\EditEntity\MediaWikiEditEntityFactory; |
47 | use Wikibase\Repo\Specials\HTMLForm\HTMLItemReferenceField; |
48 | use Wikibase\Repo\Specials\HTMLForm\HTMLTrimmedTextField; |
49 | use Wikibase\Repo\Specials\SpecialPageCopyrightView; |
50 | use Wikibase\Repo\Store\EntityTitleStoreLookup; |
51 | use Wikibase\Repo\SummaryFormatter; |
52 | use Wikibase\Repo\Validators\ValidatorErrorLocalizer; |
53 | use Wikibase\View\EntityIdFormatterFactory; |
54 | use Wikimedia\Stats\StatsFactory; |
55 | |
56 | /** |
57 | * New page for creating new Lexeme entities. |
58 | * |
59 | * @license GPL-2.0-or-later |
60 | */ |
61 | class SpecialNewLexeme extends SpecialPage { |
62 | |
63 | public const FIELD_LEXEME_LANGUAGE = 'lexeme-language'; |
64 | public const FIELD_LEXICAL_CATEGORY = 'lexicalcategory'; |
65 | public const FIELD_LEMMA = 'lemma'; |
66 | public const FIELD_LEMMA_LANGUAGE = 'lemma-language'; |
67 | |
68 | // used for the info panel and placeholders if the example lexeme is incomplete/missing |
69 | private const FALLBACK_LANGUAGE_LABEL = 'English'; |
70 | private const FALLBACK_LEXICAL_CATEGORY_LABEL = 'verb'; |
71 | |
72 | private array $tags; |
73 | private LinkRenderer $linkRenderer; |
74 | private StatsFactory $statsFactory; |
75 | private MediaWikiEditEntityFactory $editEntityFactory; |
76 | private EntityNamespaceLookup $entityNamespaceLookup; |
77 | private EntityTitleStoreLookup $entityTitleLookup; |
78 | private EntityLookup $entityLookup; |
79 | private EntityIdParser $entityIdParser; |
80 | private SummaryFormatter $summaryFormatter; |
81 | private EntityIdFormatterFactory $entityIdFormatterFactory; |
82 | private FallbackLabelDescriptionLookupFactory $labelDescriptionLookupFactory; |
83 | private ValidatorErrorLocalizer $validatorErrorLocalizer; |
84 | private LemmaTermValidator $lemmaTermValidator; |
85 | private SpecialPageCopyrightView $copyrightView; |
86 | private AnonymousEditWarningBuilder $anonymousEditWarningBuilder; |
87 | private TempUserConfig $tempUserConfig; |
88 | |
89 | public function __construct( |
90 | array $tags, |
91 | SpecialPageCopyrightView $copyrightView, |
92 | LinkRenderer $linkRenderer, |
93 | StatsFactory $statsFactory, |
94 | MediaWikiEditEntityFactory $editEntityFactory, |
95 | EntityNamespaceLookup $entityNamespaceLookup, |
96 | EntityTitleStoreLookup $entityTitleLookup, |
97 | EntityLookup $entityLookup, |
98 | EntityIdParser $entityIdParser, |
99 | SummaryFormatter $summaryFormatter, |
100 | EntityIdFormatterFactory $entityIdFormatterFactory, |
101 | FallbackLabelDescriptionLookupFactory $labelDescriptionLookupFactory, |
102 | ValidatorErrorLocalizer $validatorErrorLocalizer, |
103 | LemmaTermValidator $lemmaTermValidator, |
104 | AnonymousEditWarningBuilder $anonymousEditWarningBuilder, |
105 | TempUserConfig $tempUserConfig |
106 | ) { |
107 | parent::__construct( |
108 | 'NewLexeme', |
109 | 'createpage' |
110 | ); |
111 | |
112 | $this->tags = $tags; |
113 | $this->linkRenderer = $linkRenderer; |
114 | $this->statsFactory = $statsFactory; |
115 | $this->editEntityFactory = $editEntityFactory; |
116 | $this->entityNamespaceLookup = $entityNamespaceLookup; |
117 | $this->entityTitleLookup = $entityTitleLookup; |
118 | $this->entityLookup = $entityLookup; |
119 | $this->entityIdParser = $entityIdParser; |
120 | $this->summaryFormatter = $summaryFormatter; |
121 | $this->entityIdFormatterFactory = $entityIdFormatterFactory; |
122 | $this->labelDescriptionLookupFactory = $labelDescriptionLookupFactory; |
123 | $this->validatorErrorLocalizer = $validatorErrorLocalizer; |
124 | $this->lemmaTermValidator = $lemmaTermValidator; |
125 | $this->copyrightView = $copyrightView; |
126 | $this->anonymousEditWarningBuilder = $anonymousEditWarningBuilder; |
127 | $this->tempUserConfig = $tempUserConfig; |
128 | } |
129 | |
130 | public static function factory( |
131 | LinkRenderer $linkRenderer, |
132 | StatsFactory $statsFactory, |
133 | TempUserConfig $tempUserConfig, |
134 | AnonymousEditWarningBuilder $anonymousEditWarningBuilder, |
135 | MediaWikiEditEntityFactory $editEntityFactory, |
136 | EntityNamespaceLookup $entityNamespaceLookup, |
137 | EntityTitleStoreLookup $entityTitleLookup, |
138 | EntityLookup $entityLookup, |
139 | EntityIdParser $entityIdParser, |
140 | SettingsArray $repoSettings, |
141 | SummaryFormatter $summaryFormatter, |
142 | EntityIdFormatterFactory $entityIdFormatterFactory, |
143 | FallbackLabelDescriptionLookupFactory $labelDescriptionLookupFactory, |
144 | ValidatorErrorLocalizer $validatorErrorLocalizer, |
145 | LemmaTermValidator $lemmaTermValidator |
146 | ): self { |
147 | $copyrightView = new SpecialPageCopyrightView( |
148 | new CopyrightMessageBuilder(), |
149 | $repoSettings->getSetting( 'dataRightsUrl' ), |
150 | $repoSettings->getSetting( 'dataRightsText' ) |
151 | ); |
152 | |
153 | return new self( |
154 | $repoSettings->getSetting( 'specialPageTags' ), |
155 | $copyrightView, |
156 | $linkRenderer, |
157 | $statsFactory, |
158 | $editEntityFactory, |
159 | $entityNamespaceLookup, |
160 | $entityTitleLookup, |
161 | $entityLookup, |
162 | $entityIdParser, |
163 | $summaryFormatter, |
164 | $entityIdFormatterFactory, |
165 | $labelDescriptionLookupFactory, |
166 | $validatorErrorLocalizer, |
167 | $lemmaTermValidator, |
168 | $anonymousEditWarningBuilder, |
169 | $tempUserConfig |
170 | ); |
171 | } |
172 | |
173 | public function doesWrites(): bool { |
174 | return true; |
175 | } |
176 | |
177 | /** |
178 | * @param string|null $subPage |
179 | */ |
180 | public function execute( $subPage ): void { |
181 | $metric = $this->statsFactory->getCounter( 'special_new_lexeme_views_total' ); |
182 | $metric->copyToStatsdAt( 'wikibase.lexeme.special.NewLexeme.views' )->increment(); |
183 | |
184 | parent::execute( $subPage ); |
185 | |
186 | $this->checkBlocked(); |
187 | $this->checkBlockedOnNamespace(); |
188 | $this->checkReadOnly(); |
189 | |
190 | $output = $this->getOutput(); |
191 | $this->setHeaders(); |
192 | $searchUrl = SpecialPage::getTitleFor( 'Search' ) |
193 | ->getFullURL( [ |
194 | 'ns' . $this->getConfig()->get( 'LexemeNamespace' ) => '', |
195 | 'search' => $this->getRequest()->getText( self::FIELD_LEMMA ), |
196 | ] ); |
197 | $searchExisting = $this->msg( 'wikibaselexeme-newlexeme-search-existing' ) |
198 | ->params( $searchUrl ) |
199 | ->parse(); |
200 | $output->addHTML( |
201 | '<div id="wbl-snl-intro-text-wrapper">' |
202 | . '<p class="wbl-snl-search-existing-no-js">' . $searchExisting . '</p>' |
203 | . '</div>' |
204 | ); |
205 | $output->enableOOUI(); |
206 | $output->addHTML( $this->anonymousEditWarning() ); |
207 | $output->addHTML( '<div class="wbl-snl-main-content">' ); |
208 | $output->addHTML( '<div id="special-newlexeme-root"></div>' ); |
209 | $output->addModules( [ |
210 | 'wikibase.lexeme.special.NewLexeme', |
211 | 'wikibase.lexeme.special.NewLexeme.legacyBrowserFallback', |
212 | ] ); |
213 | $output->addModuleStyles( [ |
214 | 'wikibase.lexeme.special.NewLexeme.styles', |
215 | 'wikibase.alltargets', // T322687 |
216 | ] ); |
217 | |
218 | $exampleLexemeParams = $this->createExampleParameters(); |
219 | $form = $this->createForm( $exampleLexemeParams ); |
220 | $form->setSubmitText( $this->msg( 'wikibaselexeme-newlexeme-submit' ) ); |
221 | |
222 | // handle submit (submit callback may create form, see below) |
223 | // or show form (possibly with errors); status represents submit result |
224 | $status = $form->show(); |
225 | $output->addModuleStyles( [ |
226 | 'oojs-ui.styles.icons-content', // info icon |
227 | 'oojs-ui.styles.icons-alert', // alert icon |
228 | ] ); |
229 | $output->addHTML( |
230 | $this->processInfoPanelTemplate( $exampleLexemeParams ) |
231 | ); |
232 | $output->addHTML( '</div>' ); // .wbl-snl-main-content |
233 | $output->addHTML( |
234 | '<noscript>' |
235 | . '<style type="text/css">#special-newlexeme-root {display:none;}</style>' |
236 | . '</noscript>' |
237 | ); |
238 | |
239 | if ( $status instanceof Status && $status->isGood() ) { |
240 | // wrap it, in case HTMLForm turned it into a generic Status |
241 | $status = EditEntityStatus::wrap( $status ); |
242 | $this->redirectToEntityPage( $status ); |
243 | return; |
244 | } |
245 | |
246 | $output->addJsConfigVars( 'wblSpecialNewLexemeParams', |
247 | $this->getUrlParamsForConfig() |
248 | ); |
249 | $output->addJsConfigVars( |
250 | 'wblSpecialNewLexemeLexicalCategorySuggestions', |
251 | $this->getLexicalCategorySuggestions() |
252 | ); |
253 | $output->addJsConfigVars( 'wblSpecialNewLexemeTempUserEnabled', |
254 | $this->tempUserConfig->isEnabled() |
255 | ); |
256 | $output->addJSConfigVars( |
257 | 'wblSpecialNewLexemeExampleData', |
258 | [ |
259 | 'languageLabel' => $exampleLexemeParams['language_item_label'], |
260 | 'lexicalCategoryLabel' => $exampleLexemeParams['lexical_category_item_label'], |
261 | 'lemma' => $exampleLexemeParams['lemma_text'], |
262 | 'spellingVariant' => $exampleLexemeParams['lemma_language'], |
263 | ] |
264 | ); |
265 | } |
266 | |
267 | private function getUrlParamsForConfig(): array { |
268 | $params = []; |
269 | $lemma = $this->getRequest()->getText( self::FIELD_LEMMA ); |
270 | if ( $lemma ) { |
271 | $params['lemma'] = $lemma; |
272 | } |
273 | |
274 | $spellVarCode = $this->getRequest()->getText( self::FIELD_LEMMA_LANGUAGE ); |
275 | if ( $spellVarCode ) { |
276 | $params['spellVarCode'] = $spellVarCode; |
277 | } |
278 | |
279 | try { |
280 | $languageId = $this->entityIdParser->parse( |
281 | $this->getRequest()->getText( self::FIELD_LEXEME_LANGUAGE ) |
282 | ); |
283 | } catch ( EntityIdParsingException $e ) { |
284 | $languageId = null; |
285 | } |
286 | try { |
287 | $lexCatId = $this->entityIdParser->parse( |
288 | $this->getRequest()->getText( self::FIELD_LEXICAL_CATEGORY ) |
289 | ); |
290 | } catch ( EntityIdParsingException $e ) { |
291 | $lexCatId = null; |
292 | } |
293 | |
294 | $idsToPrefetch = array_filter( [ $languageId, $lexCatId ] ); |
295 | if ( !$idsToPrefetch ) { |
296 | return $params; |
297 | } |
298 | |
299 | $labelDescriptionLookup = $this->labelDescriptionLookupFactory->newLabelDescriptionLookup( |
300 | $this->getLanguage(), |
301 | $idsToPrefetch, |
302 | [ TermTypes::TYPE_LABEL, TermTypes::TYPE_DESCRIPTION ] |
303 | ); |
304 | |
305 | if ( $languageId ) { |
306 | $params['language'] = $this->getItemIdLabelDesc( $languageId, $labelDescriptionLookup ); |
307 | $params['language']['languageCode'] = $this->extractLanguageCode( $languageId ); |
308 | } |
309 | |
310 | if ( $lexCatId ) { |
311 | $params['lexicalCategory'] = $this->getItemIdLabelDesc( $lexCatId, $labelDescriptionLookup ); |
312 | } |
313 | |
314 | return $params; |
315 | } |
316 | |
317 | private function getItemIdLabelDesc( |
318 | EntityId $itemId, |
319 | FallbackLabelDescriptionLookup $labelDescriptionLookup |
320 | ): array { |
321 | $params = [ 'display' => [] ]; |
322 | $params['id'] = $itemId->getSerialization(); |
323 | $label = $labelDescriptionLookup->getLabel( $itemId ); |
324 | if ( $label !== null ) { |
325 | $params['display']['label'] = self::termToArrayForJs( $label ); |
326 | } |
327 | $description = $labelDescriptionLookup->getDescription( $itemId ); |
328 | if ( $description !== null ) { |
329 | $params['display']['description'] = self::termToArrayForJs( $description ); |
330 | } |
331 | |
332 | return $params; |
333 | } |
334 | |
335 | /** @return mixed|null */ |
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 | $metric = $this->statsFactory->getCounter( 'special_new_lexeme_nojs_create_total' ); |
522 | $metric->copyToStatsdAt( 'wikibase.lexeme.special.NewLexeme.nojs.create' )->increment(); |
523 | |
524 | return $saveStatus; |
525 | } |
526 | )->addPreHtml( '<noscript>' ) |
527 | ->addPostHtml( '</noscript>' ); |
528 | } |
529 | |
530 | private function createEntityFromFormData( array $formData ): Lexeme { |
531 | $entity = new Lexeme(); |
532 | $lemmaLanguage = $formData[self::FIELD_LEMMA_LANGUAGE]; |
533 | |
534 | $lemmas = new TermList( [ new Term( $lemmaLanguage, $formData[self::FIELD_LEMMA] ) ] ); |
535 | $entity->setLemmas( $lemmas ); |
536 | |
537 | $entity->setLexicalCategory( new ItemId( $formData[self::FIELD_LEXICAL_CATEGORY] ) ); |
538 | |
539 | $entity->setLanguage( new ItemId( $formData[self::FIELD_LEXEME_LANGUAGE] ) ); |
540 | |
541 | return $entity; |
542 | } |
543 | |
544 | private function createSummary( Lexeme $lexeme ): Summary { |
545 | $uiLanguageCode = $this->getLanguage()->getCode(); |
546 | |
547 | $summary = new Summary( 'wbeditentity', 'create' ); |
548 | $summary->setLanguage( $uiLanguageCode ); |
549 | |
550 | $lemmaIterator = $lexeme->getLemmas()->getIterator(); |
551 | /** @var Term|null $lemmaTerm */ |
552 | $lemmaTerm = $lemmaIterator->current(); |
553 | $summary->addAutoSummaryArgs( $lemmaTerm->getText() ); |
554 | |
555 | return $summary; |
556 | } |
557 | |
558 | private function redirectToEntityPage( EditEntityStatus $status ) { |
559 | $entity = $status->getRevision()->getEntity(); |
560 | $title = $this->entityTitleLookup->getTitleForId( $entity->getId() ); |
561 | $savedTempUser = $status->getSavedTempUser(); |
562 | $redirectUrl = ''; |
563 | if ( $savedTempUser !== null ) { |
564 | $this->getHookRunner()->onTempUserCreatedRedirect( |
565 | $this->getRequest()->getSession(), |
566 | $savedTempUser, |
567 | $title->getPrefixedDBkey(), |
568 | '', |
569 | '', |
570 | $redirectUrl |
571 | ); |
572 | } |
573 | if ( !$redirectUrl ) { |
574 | $redirectUrl = $title->getFullURL(); |
575 | } |
576 | $this->getOutput()->redirect( $redirectUrl ); |
577 | } |
578 | |
579 | private function newEditEntity(): EditEntity { |
580 | return $this->editEntityFactory->newEditEntity( |
581 | $this->getContext(), |
582 | null, |
583 | 0, |
584 | $this->getRequest()->wasPosted() |
585 | ); |
586 | } |
587 | |
588 | private function saveEntity( |
589 | EntityDocument $entity, |
590 | FormatableSummary $summary, |
591 | string $token |
592 | ): EditEntityStatus { |
593 | return $this->newEditEntity()->attemptSave( |
594 | $entity, |
595 | $this->summaryFormatter->formatSummary( $summary ), |
596 | EDIT_NEW, |
597 | $token, |
598 | null, |
599 | $this->tags |
600 | ); |
601 | } |
602 | |
603 | private function getFormFields( array $exampleLexemeParams ): array { |
604 | return [ |
605 | self::FIELD_LEMMA => [ |
606 | 'name' => self::FIELD_LEMMA, |
607 | 'class' => HTMLTrimmedTextField::class, |
608 | 'id' => 'wb-newlexeme-lemma', |
609 | 'required' => true, |
610 | 'placeholder-message' => [ |
611 | 'wikibaselexeme-newlexeme-lemma-placeholder-with-example', |
612 | Message::plaintextParam( $exampleLexemeParams['lemma_text'] ), |
613 | ], |
614 | 'label-message' => 'wikibaselexeme-newlexeme-lemma', |
615 | 'validation-callback' => function ( string $lemma ) { |
616 | $result = $this->lemmaTermValidator->validate( $lemma ); |
617 | return $result->isValid() ?: |
618 | $this->validatorErrorLocalizer->getErrorMessage( $result->getErrors()[0] ); |
619 | }, |
620 | ], |
621 | self::FIELD_LEMMA_LANGUAGE => [ |
622 | 'name' => self::FIELD_LEMMA_LANGUAGE, |
623 | 'class' => LemmaLanguageField::class, |
624 | 'cssclass' => 'lemma-language', |
625 | 'id' => 'wb-newlexeme-lemma-language', |
626 | 'label-message' => 'wikibaselexeme-newlexeme-lemma-language', |
627 | 'placeholder-message' => [ |
628 | 'wikibaselexeme-newlexeme-lemma-language-placeholder-with-example', |
629 | Message::plaintextParam( $exampleLexemeParams['lemma_language'] ), |
630 | ], |
631 | ], |
632 | self::FIELD_LEXEME_LANGUAGE => [ |
633 | 'name' => self::FIELD_LEXEME_LANGUAGE, |
634 | 'labelFieldName' => self::FIELD_LEXEME_LANGUAGE . '-label', |
635 | 'class' => HTMLItemReferenceField::class, |
636 | 'id' => 'wb-newlexeme-lexeme-language', |
637 | 'label-message' => 'wikibaselexeme-newlexeme-language', |
638 | 'required' => true, |
639 | 'placeholder-message' => [ |
640 | 'wikibaselexeme-newlexeme-language-placeholder-with-example', |
641 | Message::plaintextParam( $exampleLexemeParams['language_item_id'] ), |
642 | ], |
643 | ], |
644 | self::FIELD_LEXICAL_CATEGORY => [ |
645 | 'name' => self::FIELD_LEXICAL_CATEGORY, |
646 | 'labelFieldName' => self::FIELD_LEXICAL_CATEGORY . '-label', |
647 | 'class' => HTMLItemReferenceField::class, |
648 | 'id' => 'wb-newlexeme-lexicalCategory', |
649 | 'label-message' => 'wikibaselexeme-newlexeme-lexicalcategory', |
650 | 'required' => true, |
651 | 'placeholder-message' => [ |
652 | 'wikibaselexeme-newlexeme-lexicalcategory-placeholder-with-example', |
653 | Message::plaintextParam( $exampleLexemeParams['lexical_category_item_id'] ), |
654 | ], |
655 | ], |
656 | 'copyright-message' => [ |
657 | 'name' => 'copyright-message', |
658 | 'type' => 'info', |
659 | 'raw' => true, |
660 | 'id' => 'wb-newlexeme-copyright', |
661 | 'default' => $this->getCopyrightHTML(), |
662 | ], |
663 | ]; |
664 | } |
665 | |
666 | public function setHeaders(): void { |
667 | $out = $this->getOutput(); |
668 | $out->setPageTitleMsg( $this->getDescription() ); |
669 | } |
670 | |
671 | /** @see \Wikibase\Repo\Specials\SpecialWikibasePage::getGroupName() */ |
672 | protected function getGroupName(): string { |
673 | return 'wikibase'; |
674 | } |
675 | |
676 | public function getDescription(): Message { |
677 | return $this->msg( 'special-newlexeme' ); |
678 | } |
679 | |
680 | /** |
681 | * @throws UserBlockedError |
682 | */ |
683 | private function checkBlocked(): void { |
684 | $block = $this->getUser()->getBlock(); |
685 | if ( $block && $block->isSitewide() ) { |
686 | throw new UserBlockedError( $block ); |
687 | } |
688 | } |
689 | |
690 | /** |
691 | * @throws UserBlockedError |
692 | */ |
693 | private function checkBlockedOnNamespace(): void { |
694 | $namespace = $this->entityNamespaceLookup->getEntityNamespace( Lexeme::ENTITY_TYPE ); |
695 | $block = $this->getUser()->getBlock(); |
696 | if ( $block && $block->appliesToNamespace( $namespace ) ) { |
697 | throw new UserBlockedError( $block ); |
698 | } |
699 | } |
700 | |
701 | /** |
702 | * @return string HTML |
703 | */ |
704 | private function getCopyrightHTML() { |
705 | return $this->copyrightView->getHtml( |
706 | $this->getLanguage(), |
707 | 'wikibaselexeme-newlexeme-submit' |
708 | ); |
709 | } |
710 | |
711 | /** |
712 | * @return string HTML |
713 | */ |
714 | private function anonymousEditWarning() { |
715 | $warningIconHtml = ( new IconWidget( [ 'icon' => 'alert' ] ) )->toString(); |
716 | |
717 | if ( !$this->getUser()->isRegistered() ) { |
718 | $fullTitle = $this->getPageTitle(); |
719 | $messageSpan = Html::rawElement( |
720 | 'span', |
721 | [ 'class' => 'warning' ], |
722 | $this->anonymousEditWarningBuilder->buildAnonymousEditWarningHTML( $fullTitle->getPrefixedText() ) |
723 | ); |
724 | return '<noscript> |
725 | <div class="wbl-snl-anonymous-edit-warning-no-js wbl-snl-message-warning">' |
726 | . $warningIconHtml |
727 | . $messageSpan |
728 | . '</div></noscript>'; |
729 | } |
730 | |
731 | return ''; |
732 | } |
733 | } |