Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
74.68% |
118 / 158 |
|
70.00% |
7 / 10 |
CRAP | |
0.00% |
0 / 1 |
AddSense | |
74.68% |
118 / 158 |
|
70.00% |
7 / 10 |
29.85 | |
0.00% |
0 / 1 |
factory | |
100.00% |
29 / 29 |
|
100.00% |
1 / 1 |
1 | |||
__construct | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
81.82% |
54 / 66 |
|
0.00% |
0 / 1 |
14.02 | |||
getAllowedParams | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
1 | |||
isWriteMode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isInternal | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
needsToken | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
mustBePosted | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getExamplesMessages | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
2 | |||
getSenseWithMaxId | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | declare( strict_types = 1 ); |
4 | |
5 | namespace Wikibase\Lexeme\MediaWiki\Api; |
6 | |
7 | use Deserializers\Deserializer; |
8 | use LogicException; |
9 | use MediaWiki\Api\ApiBase; |
10 | use MediaWiki\Api\ApiCreateTempUserTrait; |
11 | use MediaWiki\Api\ApiMain; |
12 | use MediaWiki\Api\ApiUsageException; |
13 | use MediaWiki\Message\Message; |
14 | use RuntimeException; |
15 | use Wikibase\DataModel\Deserializers\TermDeserializer; |
16 | use Wikibase\DataModel\Entity\EntityIdParser; |
17 | use Wikibase\DataModel\Serializers\SerializerFactory; |
18 | use Wikibase\Lexeme\DataAccess\ChangeOp\Validation\LexemeTermLanguageValidator; |
19 | use Wikibase\Lexeme\DataAccess\ChangeOp\Validation\LexemeTermSerializationValidator; |
20 | use Wikibase\Lexeme\Domain\Model\Exceptions\ConflictException; |
21 | use Wikibase\Lexeme\Domain\Model\Lexeme; |
22 | use Wikibase\Lexeme\Domain\Model\Sense; |
23 | use Wikibase\Lexeme\Domain\Model\SenseId; |
24 | use Wikibase\Lexeme\MediaWiki\Api\Error\LexemeNotFound; |
25 | use Wikibase\Lexeme\Presentation\ChangeOp\Deserialization\EditSenseChangeOpDeserializer; |
26 | use Wikibase\Lexeme\Presentation\ChangeOp\Deserialization\GlossesChangeOpDeserializer; |
27 | use Wikibase\Lexeme\Serialization\SenseSerializer; |
28 | use Wikibase\Lexeme\WikibaseLexemeServices; |
29 | use Wikibase\Lib\Store\EntityRevisionLookup; |
30 | use Wikibase\Lib\Store\LookupConstants; |
31 | use Wikibase\Lib\Store\StorageException; |
32 | use Wikibase\Lib\StringNormalizer; |
33 | use Wikibase\Lib\Summary; |
34 | use Wikibase\Repo\Api\ApiErrorReporter; |
35 | use Wikibase\Repo\Api\ApiHelperFactory; |
36 | use Wikibase\Repo\Api\ResultBuilder; |
37 | use Wikibase\Repo\ChangeOp\ChangeOpException; |
38 | use Wikibase\Repo\ChangeOp\ChangeOpFactoryProvider; |
39 | use Wikibase\Repo\ChangeOp\ChangeOpValidationException; |
40 | use Wikibase\Repo\ChangeOp\Deserialization\ClaimsChangeOpDeserializer; |
41 | use Wikibase\Repo\EditEntity\MediaWikiEditEntityFactory; |
42 | use Wikibase\Repo\Store\Store; |
43 | use Wikibase\Repo\SummaryFormatter; |
44 | use Wikimedia\ParamValidator\ParamValidator; |
45 | |
46 | /** |
47 | * @license GPL-2.0-or-later |
48 | */ |
49 | class AddSense extends ApiBase { |
50 | |
51 | use ApiCreateTempUserTrait; |
52 | |
53 | private const LATEST_REVISION = 0; |
54 | |
55 | private AddSenseRequestParser $requestParser; |
56 | |
57 | private ResultBuilder $resultBuilder; |
58 | private ApiErrorReporter $errorReporter; |
59 | private SenseSerializer $senseSerializer; |
60 | private MediaWikiEditEntityFactory $editEntityFactory; |
61 | private SummaryFormatter $summaryFormatter; |
62 | private EntityRevisionLookup $entityRevisionLookup; |
63 | |
64 | /** |
65 | * @return self |
66 | */ |
67 | public static function factory( |
68 | ApiMain $mainModule, |
69 | string $moduleName, |
70 | ApiHelperFactory $apiHelperFactory, |
71 | SerializerFactory $baseDataModelSerializerFactory, |
72 | ChangeOpFactoryProvider $changeOpFactoryProvider, |
73 | MediaWikiEditEntityFactory $editEntityFactory, |
74 | EntityIdParser $entityIdParser, |
75 | Deserializer $externalFormatStatementDeserializer, |
76 | Store $store, |
77 | StringNormalizer $stringNormalizer, |
78 | SummaryFormatter $summaryFormatter |
79 | ) { |
80 | $senseSerializer = new SenseSerializer( |
81 | $baseDataModelSerializerFactory->newTermListSerializer(), |
82 | $baseDataModelSerializerFactory->newStatementListSerializer() |
83 | ); |
84 | |
85 | return new self( |
86 | $mainModule, |
87 | $moduleName, |
88 | new AddSenseRequestParser( |
89 | $entityIdParser, |
90 | new EditSenseChangeOpDeserializer( |
91 | new GlossesChangeOpDeserializer( |
92 | new TermDeserializer(), |
93 | $stringNormalizer, |
94 | new LexemeTermSerializationValidator( |
95 | new LexemeTermLanguageValidator( WikibaseLexemeServices::getTermLanguages() ) |
96 | ) |
97 | ), |
98 | new ClaimsChangeOpDeserializer( |
99 | $externalFormatStatementDeserializer, |
100 | $changeOpFactoryProvider->getStatementChangeOpFactory() |
101 | ) |
102 | ) |
103 | ), |
104 | $senseSerializer, |
105 | $store->getEntityRevisionLookup( Store::LOOKUP_CACHING_DISABLED ), |
106 | $editEntityFactory, |
107 | $summaryFormatter, |
108 | $apiHelperFactory |
109 | ); |
110 | } |
111 | |
112 | public function __construct( |
113 | ApiMain $mainModule, |
114 | string $moduleName, |
115 | AddSenseRequestParser $requestParser, |
116 | SenseSerializer $senseSerializer, |
117 | EntityRevisionLookup $entityRevisionLookup, |
118 | MediaWikiEditEntityFactory $editEntityFactory, |
119 | SummaryFormatter $summaryFormatter, |
120 | ApiHelperFactory $apiHelperFactory |
121 | ) { |
122 | parent::__construct( $mainModule, $moduleName ); |
123 | |
124 | $this->resultBuilder = $apiHelperFactory->getResultBuilder( $this ); |
125 | $this->errorReporter = $apiHelperFactory->getErrorReporter( $this ); |
126 | $this->requestParser = $requestParser; |
127 | $this->senseSerializer = $senseSerializer; |
128 | $this->editEntityFactory = $editEntityFactory; |
129 | $this->entityRevisionLookup = $entityRevisionLookup; |
130 | $this->summaryFormatter = $summaryFormatter; |
131 | } |
132 | |
133 | /** |
134 | * @see ApiBase::execute() |
135 | * |
136 | * @throws ApiUsageException |
137 | */ |
138 | public function execute(): void { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh |
139 | /* |
140 | * { |
141 | "glosses": [ |
142 | "en-GB": { |
143 | "value": "colour", |
144 | "language": "en-gb" |
145 | }, |
146 | "en-US": { |
147 | "value": "color", |
148 | "language": "en-us" |
149 | } |
150 | ] |
151 | } |
152 | * |
153 | */ |
154 | |
155 | //TODO: Documenting response structure. Is it possible? |
156 | |
157 | $params = $this->extractRequestParams(); |
158 | $request = $this->requestParser->parse( $params ); |
159 | |
160 | try { |
161 | $lexemeId = $request->getLexemeId(); |
162 | $lexemeRevision = $this->entityRevisionLookup->getEntityRevision( |
163 | $lexemeId, |
164 | self::LATEST_REVISION, |
165 | LookupConstants::LATEST_FROM_MASTER |
166 | ); |
167 | |
168 | if ( !$lexemeRevision ) { |
169 | $error = new LexemeNotFound( $lexemeId ); |
170 | $this->dieWithError( $error->asApiMessage( AddSenseRequestParser::PARAM_LEXEME_ID, |
171 | [] ) ); |
172 | } |
173 | } catch ( StorageException $e ) { |
174 | // TODO Test it |
175 | if ( $e->getStatus() ) { |
176 | $this->dieStatus( $e->getStatus() ); |
177 | } else { |
178 | throw new LogicException( |
179 | 'StorageException caught with no status', |
180 | 0, |
181 | $e |
182 | ); |
183 | } |
184 | } |
185 | /** @var Lexeme $lexeme */ |
186 | $lexeme = $lexemeRevision->getEntity(); |
187 | $changeOp = $request->getChangeOp(); |
188 | |
189 | $summary = new Summary(); |
190 | $result = $changeOp->validate( $lexeme ); |
191 | if ( !$result->isValid() ) { |
192 | $this->errorReporter->dieException( |
193 | new ChangeOpValidationException( $result ), |
194 | 'modification-failed' |
195 | ); |
196 | } |
197 | |
198 | try { |
199 | $changeOp->apply( $lexeme, $summary ); |
200 | } catch ( ChangeOpException $exception ) { |
201 | $this->errorReporter->dieException( $exception, 'unprocessable-request' ); |
202 | } |
203 | |
204 | $baseRevId = $request->getBaseRevId() ?: $lexemeRevision->getRevisionId(); |
205 | |
206 | $editEntity = $this->editEntityFactory->newEditEntity( |
207 | $this->getContext(), |
208 | $request->getLexemeId(), |
209 | $baseRevId |
210 | ); |
211 | $summaryString = $this->summaryFormatter->formatSummary( |
212 | $summary |
213 | ); |
214 | $flags = EDIT_UPDATE; |
215 | if ( isset( $params['bot'] ) && $params['bot'] && |
216 | $this->getPermissionManager()->userHasRight( $this->getUser(), 'bot' ) |
217 | ) { |
218 | $flags |= EDIT_FORCE_BOT; |
219 | } |
220 | |
221 | $tokenThatDoesNotNeedChecking = false; |
222 | try { |
223 | $status = $editEntity->attemptSave( |
224 | $lexeme, |
225 | $summaryString, |
226 | $flags, |
227 | $tokenThatDoesNotNeedChecking, |
228 | null, |
229 | $params['tags'] ?: [] |
230 | ); |
231 | } catch ( ConflictException $exception ) { |
232 | $this->dieWithException( new RuntimeException( 'Edit conflict: ' . $exception->getMessage() ) ); |
233 | } |
234 | |
235 | if ( !$status->isGood() ) { |
236 | $this->dieStatus( $status ); |
237 | } |
238 | |
239 | $entityRevision = $status->getRevision(); |
240 | |
241 | /** @var Lexeme $editedLexeme */ |
242 | $editedLexeme = $entityRevision->getEntity(); |
243 | '@phan-var Lexeme $editedLexeme'; |
244 | $newSense = $this->getSenseWithMaxId( $editedLexeme ); |
245 | $serializedSense = $this->senseSerializer->serialize( $newSense ); |
246 | |
247 | $this->resultBuilder->addRevisionIdFromStatusToResult( $status, null ); |
248 | $this->resultBuilder->markSuccess(); |
249 | $this->resultBuilder->addTempUser( $status, fn ( $user ) => $this->getTempUserRedirectUrl( $params, $user ) ); |
250 | $this->getResult()->addValue( null, 'sense', $serializedSense ); |
251 | } |
252 | |
253 | /** @inheritDoc */ |
254 | protected function getAllowedParams(): array { |
255 | return array_merge( [ |
256 | AddSenseRequestParser::PARAM_LEXEME_ID => [ |
257 | ParamValidator::PARAM_TYPE => 'string', |
258 | ParamValidator::PARAM_REQUIRED => true, |
259 | ], |
260 | AddSenseRequestParser::PARAM_DATA => [ |
261 | ParamValidator::PARAM_TYPE => 'text', |
262 | ParamValidator::PARAM_REQUIRED => true, |
263 | ], |
264 | AddSenseRequestParser::PARAM_BASEREVID => [ |
265 | ParamValidator::PARAM_TYPE => 'integer', |
266 | ], |
267 | 'tags' => [ |
268 | ParamValidator::PARAM_TYPE => 'tags', |
269 | ParamValidator::PARAM_ISMULTI => true, |
270 | ], |
271 | 'bot' => [ |
272 | ParamValidator::PARAM_TYPE => 'boolean', |
273 | ParamValidator::PARAM_DEFAULT => false, |
274 | ], |
275 | ], $this->getCreateTempUserParams() ); |
276 | } |
277 | |
278 | public function isWriteMode(): bool { |
279 | return true; |
280 | } |
281 | |
282 | /** |
283 | * As long as this codebase is in development and APIs might change any time without notice, we |
284 | * mark all as internal. This adds an "unstable" notice, but does not hide them in any way. |
285 | */ |
286 | public function isInternal(): bool { |
287 | return true; |
288 | } |
289 | |
290 | public function needsToken(): string { |
291 | return 'csrf'; |
292 | } |
293 | |
294 | public function mustBePosted(): bool { |
295 | return true; |
296 | } |
297 | |
298 | protected function getExamplesMessages(): array { |
299 | $lexemeId = 'L12'; |
300 | $exampleData = [ |
301 | 'glosses' => [ |
302 | 'en-us' => [ 'value' => 'Some text value', 'language' => 'en-us' ], |
303 | 'en-gb' => [ 'value' => 'Another text value', 'language' => 'en-gb' ], |
304 | ], |
305 | ]; |
306 | |
307 | $query = http_build_query( [ |
308 | 'action' => $this->getModuleName(), |
309 | AddSenseRequestParser::PARAM_LEXEME_ID => $lexemeId, |
310 | AddSenseRequestParser::PARAM_DATA => json_encode( $exampleData ), |
311 | ] ); |
312 | |
313 | $languages = array_column( $exampleData['glosses'], 'language' ); |
314 | $glosses = array_column( $exampleData['glosses'], 'value' ); |
315 | |
316 | $glossesText = $this->getLanguage()->commaList( $glosses ); |
317 | $languagesText = $this->getLanguage()->commaList( $languages ); |
318 | |
319 | $exampleMessage = new Message( |
320 | 'apihelp-wbladdsense-example-1', |
321 | [ |
322 | $lexemeId, |
323 | $glossesText, |
324 | $languagesText, |
325 | ] |
326 | ); |
327 | |
328 | return [ |
329 | urldecode( $query ) => $exampleMessage, |
330 | ]; |
331 | } |
332 | |
333 | private function getSenseWithMaxId( Lexeme $lexeme ): Sense { |
334 | // TODO: This is all rather nasty |
335 | $maxIdNumber = $lexeme->getSenses()->maxSenseIdNumber(); |
336 | // TODO: Use some service to get the ID object! |
337 | $senseId = new SenseId( $lexeme->getId() . '-S' . $maxIdNumber ); |
338 | return $lexeme->getSense( $senseId ); |
339 | } |
340 | |
341 | } |