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
RemoveForm
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\FormNotFound;
16use Wikibase\Lexeme\MediaWiki\Api\Error\LexemeNotFound;
17use Wikibase\Lexeme\Presentation\ChangeOp\Deserialization\FormIdDeserializer;
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 RemoveForm extends ApiBase {
35
36    use ApiCreateTempUserTrait;
37
38    private const LATEST_REVISION = 0;
39
40    private RemoveFormRequestParser $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 RemoveFormRequestParser(
60                new FormIdDeserializer( $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        RemoveFormRequestParser $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            $formId = $request->getFormId();
104            $lexemeId = $formId->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( RemoveFormRequestParser::PARAM_FORM_ID, [] ) );
115            }
116
117            $baseRevId = $lexemeRevision->getRevisionId();
118            /** @var Lexeme $lexeme */
119            $lexeme = $lexemeRevision->getEntity();
120            '@phan-var Lexeme $lexeme';
121
122            if ( $lexeme->getForms()->getById( $formId ) === null ) {
123                $error = new FormNotFound( $formId );
124                $this->dieWithError( $error->asApiMessage( RemoveFormRequestParser::PARAM_FORM_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        $status = $editEntity->attemptSave(
166            $lexeme,
167            $this->summaryFormatter->formatSummary( $summary ),
168            $flags,
169            $tokenThatDoesNotNeedChecking,
170            null,
171            $params['tags'] ?: []
172        );
173
174        if ( !$status->isOK() ) {
175            $this->dieStatus( $status );
176        }
177
178        $this->resultBuilder->addRevisionIdFromStatusToResult( $status, null );
179        $this->resultBuilder->markSuccess();
180        $this->resultBuilder->addTempUser( $status, fn ( $user ) => $this->getTempUserRedirectUrl( $params, $user ) );
181    }
182
183    protected function getAllowedParams(): array {
184        return array_merge( [
185            RemoveFormRequestParser::PARAM_FORM_ID => [
186                ParamValidator::PARAM_TYPE => 'string',
187                ParamValidator::PARAM_REQUIRED => true,
188            ],
189            'tags' => [
190                ParamValidator::PARAM_TYPE => 'tags',
191                ParamValidator::PARAM_ISMULTI => true,
192            ],
193            'bot' => [
194                ParamValidator::PARAM_TYPE => 'boolean',
195                ParamValidator::PARAM_DEFAULT => false,
196            ],
197            RemoveFormRequestParser::PARAM_BASEREVID => [
198                ParamValidator::PARAM_TYPE => 'integer',
199            ],
200        ], $this->getCreateTempUserParams() );
201    }
202
203    public function isWriteMode(): bool {
204        return true;
205    }
206
207    /**
208     * As long as this codebase is in development and APIs might change any time without notice, we
209     * mark all as internal. This adds an "unstable" notice, but does not hide them in any way.
210     */
211    public function isInternal(): bool {
212        return true;
213    }
214
215    public function needsToken(): string {
216        return 'csrf';
217    }
218
219    public function mustBePosted(): bool {
220        return true;
221    }
222
223    protected function getExamplesMessages(): array {
224        $formId = 'L10-F20';
225
226        $query = http_build_query( [
227            'action' => $this->getModuleName(),
228            RemoveFormRequestParser::PARAM_FORM_ID => $formId,
229        ] );
230
231        $exampleMessage = new Message(
232            'apihelp-wblremoveform-example-1',
233            [ $formId ]
234        );
235
236        return [
237            urldecode( $query ) => $exampleMessage,
238        ];
239    }
240
241}