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