Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.48% covered (warning)
77.48%
86 / 111
66.67% covered (warning)
66.67%
6 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
RemoveSense
77.48% covered (warning)
77.48%
86 / 111
66.67% covered (warning)
66.67%
6 / 9
24.57
0.00% covered (danger)
0.00%
0 / 1
 factory
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 execute
80.00% covered (warning)
80.00%
48 / 60
0.00% covered (danger)
0.00%
0 / 1
13.15
 getAllowedParams
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
1
 isWriteMode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isInternal
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 needsToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 mustBePosted
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare( strict_types = 1 );
4
5namespace Wikibase\Lexeme\MediaWiki\Api;
6
7use LogicException;
8use MediaWiki\Api\ApiBase;
9use MediaWiki\Api\ApiCreateTempUserTrait;
10use MediaWiki\Api\ApiMain;
11use MediaWiki\Api\ApiUsageException;
12use MediaWiki\Message\Message;
13use Wikibase\DataModel\Entity\EntityIdParser;
14use Wikibase\Lexeme\Domain\Model\Lexeme;
15use Wikibase\Lexeme\MediaWiki\Api\Error\LexemeNotFound;
16use Wikibase\Lexeme\MediaWiki\Api\Error\SenseNotFound;
17use Wikibase\Lexeme\Presentation\ChangeOp\Deserialization\SenseIdDeserializer;
18use Wikibase\Lib\Store\EntityRevisionLookup;
19use Wikibase\Lib\Store\LookupConstants;
20use Wikibase\Lib\Store\StorageException;
21use Wikibase\Lib\Summary;
22use Wikibase\Repo\Api\ApiErrorReporter;
23use Wikibase\Repo\Api\ApiHelperFactory;
24use Wikibase\Repo\Api\ResultBuilder;
25use Wikibase\Repo\ChangeOp\ChangeOpValidationException;
26use Wikibase\Repo\EditEntity\MediaWikiEditEntityFactory;
27use Wikibase\Repo\Store\Store;
28use Wikibase\Repo\SummaryFormatter;
29use Wikimedia\ParamValidator\ParamValidator;
30
31/**
32 * @license GPL-2.0-or-later
33 */
34class RemoveSense extends ApiBase {
35
36    use ApiCreateTempUserTrait;
37
38    private const LATEST_REVISION = 0;
39
40    private RemoveSenseRequestParser $requestParser;
41    private ResultBuilder $resultBuilder;
42    private ApiErrorReporter $errorReporter;
43    private MediaWikiEditEntityFactory $editEntityFactory;
44    private SummaryFormatter $summaryFormatter;
45    private EntityRevisionLookup $entityRevisionLookup;
46
47    public static function factory(
48        ApiMain $mainModule,
49        string $moduleName,
50        ApiHelperFactory $apiHelperFactory,
51        MediaWikiEditEntityFactory $editEntityFactory,
52        EntityIdParser $entityIdParser,
53        Store $store,
54        SummaryFormatter $summaryFormatter
55    ): self {
56        return new self(
57            $mainModule,
58            $moduleName,
59            new RemoveSenseRequestParser(
60                new SenseIdDeserializer( $entityIdParser )
61            ),
62            $store->getEntityRevisionLookup( Store::LOOKUP_CACHING_DISABLED ),
63            $editEntityFactory,
64            $summaryFormatter,
65            $apiHelperFactory
66        );
67    }
68
69    public function __construct(
70        ApiMain $mainModule,
71        string $moduleName,
72        RemoveSenseRequestParser $requestParser,
73        EntityRevisionLookup $entityRevisionLookup,
74        MediaWikiEditEntityFactory $editEntityFactory,
75        SummaryFormatter $summaryFormatter,
76        ApiHelperFactory $apiHelperFactory
77    ) {
78        parent::__construct( $mainModule, $moduleName );
79
80        $this->resultBuilder = $apiHelperFactory->getResultBuilder( $this );
81        $this->errorReporter = $apiHelperFactory->getErrorReporter( $this );
82        $this->requestParser = $requestParser;
83        $this->editEntityFactory = $editEntityFactory;
84        $this->entityRevisionLookup = $entityRevisionLookup;
85        $this->summaryFormatter = $summaryFormatter;
86    }
87
88    /**
89     * @see ApiBase::execute()
90     *
91     * @throws ApiUsageException
92     */
93    public function execute(): void {
94        $params = $this->extractRequestParams();
95        $request = $this->requestParser->parse( $params );
96        if ( $request->getBaseRevId() ) {
97            $baseRevId = $request->getBaseRevId();
98        } else {
99            $baseRevId = self::LATEST_REVISION;
100        }
101
102        try {
103            $senseId = $request->getSenseId();
104            $lexemeId = $senseId->getLexemeId();
105
106            $lexemeRevision = $this->entityRevisionLookup->getEntityRevision(
107                $lexemeId,
108                $baseRevId,
109                LookupConstants::LATEST_FROM_MASTER
110            );
111
112            if ( !$lexemeRevision ) {
113                $error = new LexemeNotFound( $lexemeId );
114                $this->dieWithError( $error->asApiMessage( RemoveSenseRequestParser::PARAM_SENSE_ID, [] ) );
115            }
116
117            $baseRevId = $lexemeRevision->getRevisionId();
118            /** @var Lexeme $lexeme */
119            $lexeme = $lexemeRevision->getEntity();
120            '@phan-var Lexeme $lexeme';
121
122            if ( $lexeme->getSenses()->getById( $senseId ) === null ) {
123                $error = new SenseNotFound( $senseId );
124                $this->dieWithError( $error->asApiMessage( RemoveSenseRequestParser::PARAM_SENSE_ID, [] ) );
125            }
126        } catch ( StorageException $e ) {
127            // TODO Test it
128            if ( $e->getStatus() ) {
129                $this->dieStatus( $e->getStatus() );
130            } else {
131                throw new LogicException(
132                    'StorageException caught with no status',
133                    0,
134                    $e
135                );
136            }
137        }
138
139        $summary = new Summary();
140        $changeOp = $request->getChangeOp();
141
142        $result = $changeOp->validate( $lexeme );
143        if ( !$result->isValid() ) {
144            $this->errorReporter->dieException(
145                new ChangeOpValidationException( $result ),
146                'modification-failed'
147            );
148        }
149
150        $changeOp->apply( $lexeme, $summary );
151
152        $editEntity = $this->editEntityFactory->newEditEntity(
153            $this->getContext(),
154            $lexemeId,
155            $baseRevId
156        );
157        $flags = EDIT_UPDATE;
158        if ( isset( $params['bot'] ) && $params['bot'] &&
159            $this->getPermissionManager()->userHasRight( $this->getUser(), 'bot' )
160        ) {
161            $flags |= EDIT_FORCE_BOT;
162        }
163
164        $tokenThatDoesNotNeedChecking = false;
165        // FIXME: Handle failure
166        $status = $editEntity->attemptSave(
167            $lexeme,
168            $this->summaryFormatter->formatSummary( $summary ),
169            $flags,
170            $tokenThatDoesNotNeedChecking,
171            null,
172            $params['tags'] ?: []
173        );
174
175        if ( !$status->isOK() ) {
176            $this->dieStatus( $status );
177        }
178
179        $this->resultBuilder->addRevisionIdFromStatusToResult( $status, null );
180        $this->resultBuilder->markSuccess();
181        $this->resultBuilder->addTempUser( $status, fn ( $user ) => $this->getTempUserRedirectUrl( $params, $user ) );
182    }
183
184    protected function getAllowedParams(): array {
185        return array_merge( [
186            RemoveSenseRequestParser::PARAM_SENSE_ID => [
187                ParamValidator::PARAM_TYPE => 'string',
188                ParamValidator::PARAM_REQUIRED => true,
189            ],
190            RemoveSenseRequestParser::PARAM_BASEREVID => [
191                ParamValidator::PARAM_TYPE => 'integer',
192            ],
193            'tags' => [
194                ParamValidator::PARAM_TYPE => 'tags',
195                ParamValidator::PARAM_ISMULTI => true,
196            ],
197            'bot' => [
198                ParamValidator::PARAM_TYPE => 'boolean',
199                ParamValidator::PARAM_DEFAULT => false,
200            ],
201        ], $this->getCreateTempUserParams() );
202    }
203
204    public function isWriteMode(): bool {
205        return true;
206    }
207
208    /**
209     * As long as this codebase is in development and APIs might change any time without notice, we
210     * mark all as internal. This adds an "unstable" notice, but does not hide them in any way.
211     */
212    public function isInternal(): bool {
213        return true;
214    }
215
216    public function needsToken(): string {
217        return 'csrf';
218    }
219
220    public function mustBePosted(): bool {
221        return true;
222    }
223
224    protected function getExamplesMessages(): array {
225        $senseId = 'L10-S20';
226
227        $query = http_build_query( [
228            'action' => $this->getModuleName(),
229            RemoveSenseRequestParser::PARAM_SENSE_ID => $senseId,
230        ] );
231
232        $exampleMessage = new Message(
233            'apihelp-wblremovesense-example-1',
234            [ $senseId ]
235        );
236
237        return [
238            urldecode( $query ) => $exampleMessage,
239        ];
240    }
241
242}