Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.50% covered (success)
94.50%
103 / 109
70.00% covered (warning)
70.00%
7 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
EntitySchemaEditAction
94.50% covered (success)
94.50%
103 / 109
70.00% covered (warning)
70.00%
7 / 10
20.07
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 show
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 onSubmit
84.62% covered (warning)
84.62%
22 / 26
0.00% covered (danger)
0.00%
0 / 1
6.13
 alterForm
96.15% covered (success)
96.15%
25 / 26
0.00% covered (danger)
0.00%
0 / 1
4
 getCopyrightHTML
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getFormFields
96.43% covered (success)
96.43%
27 / 28
0.00% covered (danger)
0.00%
0 / 1
2
 usesOOUI
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onSuccess
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRestriction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare( strict_types = 1 );
4
5namespace EntitySchema\MediaWiki\Actions;
6
7use EntitySchema\DataAccess\EntitySchemaStatus;
8use EntitySchema\DataAccess\MediaWikiRevisionEntitySchemaUpdater;
9use EntitySchema\Domain\Model\EntitySchemaId;
10use EntitySchema\MediaWiki\Content\EntitySchemaContent;
11use EntitySchema\MediaWiki\EntitySchemaRedirectTrait;
12use EntitySchema\Presentation\InputValidator;
13use EntitySchema\Services\Converter\EntitySchemaConverter;
14use MediaWiki\Actions\FormAction;
15use MediaWiki\Context\IContextSource;
16use MediaWiki\HTMLForm\HTMLForm;
17use MediaWiki\MediaWikiServices;
18use MediaWiki\Page\Article;
19use MediaWiki\Revision\SlotRecord;
20use MediaWiki\Status\Status;
21use MediaWiki\User\Options\UserOptionsLookup;
22use MediaWiki\User\TempUser\TempUserConfig;
23use RuntimeException;
24use Wikibase\Repo\CopyrightMessageBuilder;
25use Wikibase\Repo\Specials\SpecialPageCopyrightView;
26use Wikimedia\Assert\Assert;
27
28/**
29 * Edit a EntitySchema via the mediawiki editing action
30 *
31 * @license GPL-2.0-or-later
32 */
33class EntitySchemaEditAction extends FormAction {
34
35    use EntitySchemaRedirectTrait;
36
37    public const FIELD_SCHEMA_TEXT = 'schema-text';
38    public const FIELD_BASE_REV = 'base-rev';
39    public const FIELD_EDIT_SUMMARY = 'edit-summary';
40    public const FIELD_IGNORE_EMPTY_SUMMARY = 'ignore-blank-summary';
41
42    private InputValidator $inputValidator;
43    private string $submitMsgKey;
44    private UserOptionsLookup $userOptionsLookup;
45    private SpecialPageCopyrightView $copyrightView;
46    private TempUserConfig $tempUserConfig;
47
48    /**
49     * Used to stash the status between {@link self::onSubmit()} and {@link self::onSuccess()}
50     * (FormAction does not pass the status into onSuccess).
51     */
52    private ?EntitySchemaStatus $status = null;
53
54    public function __construct(
55        Article $article,
56        IContextSource $context,
57        InputValidator $inputValidator,
58        bool $editSubmitButtonLabelPublish,
59        UserOptionsLookup $userOptionsLookup,
60        string $dataRightsUrl,
61        string $dataRightsText,
62        TempUserConfig $tempUserConfig
63    ) {
64        parent::__construct( $article, $context );
65        $this->inputValidator = $inputValidator;
66        $this->userOptionsLookup = $userOptionsLookup;
67        $this->submitMsgKey = $editSubmitButtonLabelPublish ? 'publishchanges' : 'savechanges';
68        $this->copyrightView = new SpecialPageCopyrightView(
69            new CopyrightMessageBuilder(),
70            $dataRightsUrl,
71            $dataRightsText
72        );
73        $this->tempUserConfig = $tempUserConfig;
74    }
75
76    public function show(): void {
77        parent::show();
78
79        $output = $this->getOutput();
80        $output->clearSubtitle();
81        $output->addModules( [
82            'ext.EntitySchema.action.edit',
83        ] );
84        $output->addJsConfigVars(
85            'wgEntitySchemaSchemaTextMaxSizeBytes',
86            intval( $this->getContext()->getConfig()->get( 'EntitySchemaSchemaTextMaxSizeBytes' ) )
87        );
88    }
89
90    /**
91     * Process the form on POST submission.
92     *
93     * If you don't want to do anything with the form, just return false here.
94     *
95     * This method will be passed to the HTMLForm as a submit callback (see
96     * HTMLForm::setSubmitCallback) and must return as documented for HTMLForm::trySubmit.
97     *
98     * @see HTMLForm::setSubmitCallback()
99     * @see HTMLForm::trySubmit()
100     *
101     * @param array $data
102     *
103     * @return bool|string|array|Status Must return as documented for HTMLForm::trySubmit
104     */
105    public function onSubmit( $data ) {
106        /**
107         * @var $content EntitySchemaContent
108         */
109        $request = $this->getContext()->getRequest();
110        $output = $this->getOutput();
111        $context = $this->getContext();
112        $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
113        $currentRevision = $revisionStore->getKnownCurrentRevision( $context->getTitle() );
114        if ( !$currentRevision ) {
115            return Status::newFatal( $this->msg( 'entityschema-error-schemadeleted' ) );
116        }
117
118        $content = $currentRevision->getContent( SlotRecord::MAIN );
119        if ( !$content instanceof EntitySchemaContent ) {
120            return Status::newFatal( $this->msg( 'entityschema-error-schemadeleted' ) );
121        }
122
123        $user = $this->getUser();
124        if (
125            $data['edit-summary'] === ''
126            && $this->userOptionsLookup->getOption( $user, 'forceeditsummary' ) === '1'
127            && !$request->getBool( self::FIELD_IGNORE_EMPTY_SUMMARY )
128        ) {
129            return $output->wrapWikiMsg( "<div id='mw-missingsummary'>\n$1\n</div>",
130                [ 'missingsummary', $this->msg( $this->submitMsgKey )->text() ] );
131        }
132        $id = new EntitySchemaId( $this->getTitle()->getText() );
133        $schemaUpdater = MediaWikiRevisionEntitySchemaUpdater::newFromContext( $this->getContext() );
134
135        $this->status = $schemaUpdater->updateSchemaText(
136            $id,
137            $data[self::FIELD_SCHEMA_TEXT],
138            (int)$data[self::FIELD_BASE_REV],
139            trim( $data[self::FIELD_EDIT_SUMMARY] )
140        );
141        $this->status->replaceMessage( 'edit-conflict', 'entityschema-error-schematext-conflict' );
142        return $this->status;
143    }
144
145    protected function alterForm( HTMLForm $form ): void {
146        $form->suppressDefaultSubmit();
147        $request = $this->getContext()->getRequest();
148        if ( $request->getVal( 'wpedit-summary' ) === '' ) {
149            $form->addHiddenField( self::FIELD_IGNORE_EMPTY_SUMMARY, true );
150        }
151
152        $form->addFields( [ [
153            'type' => 'info',
154            'default' => $this->getCopyrightHTML(),
155            'raw' => true,
156        ] ] );
157        if ( $this->getUser()->isAnon() && !$this->tempUserConfig->isEnabled() ) {
158            $form->addFields( [ [
159                'type' => 'info',
160                'default' => $this->msg(
161                    'entityschema-anonymouseditwarning'
162                )->parse(),
163                'raw' => true,
164            ] ] );
165        }
166
167        $form->addButton( [
168            'name' => 'wpSave',
169            'value' => $this->msg( $this->submitMsgKey )->text(),
170            'label' => $this->msg( $this->submitMsgKey )->text(),
171            'attribs' => [ 'accessKey' => $this->msg( 'accesskey-save' )->plain() ],
172            'flags' => [ 'primary', 'progressive' ],
173            'type' => 'submit',
174        ] );
175        $form->setValidationErrorMessage( [ [ 'entityschema-error-one-more-message-available' ] ] );
176    }
177
178    /**
179     * @return string HTML
180     */
181    private function getCopyrightHTML() {
182        return $this->copyrightView
183            ->getHtml( $this->getLanguage(), $this->submitMsgKey );
184    }
185
186    protected function getFormFields(): array {
187        $context = $this->getContext();
188        $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
189        $currentRevRecord = $revisionStore->getKnownCurrentRevision( $context->getTitle() );
190        if ( !$currentRevRecord ) {
191            throw new RuntimeException( $this->msg( 'entityschema-error-schemadeleted' )->text() );
192        }
193
194        /** @var EntitySchemaContent $content */
195        $content = $currentRevRecord->getContent( SlotRecord::MAIN );
196        // @phan-suppress-next-line PhanUndeclaredMethod
197        $schemaText = ( new EntitySchemaConverter() )->getSchemaText( $content->getText() );
198        $baseRev = $this->getContext()->getRequest()->getInt(
199            'wp' . self::FIELD_BASE_REV,
200            $currentRevRecord->getId()
201        );
202
203        return [
204            self::FIELD_SCHEMA_TEXT => [
205                'type' => 'textarea',
206                'default' => $schemaText,
207                'label-message' => 'entityschema-editpage-schema-inputlabel',
208                'validation-callback' => [ $this->inputValidator, 'validateSchemaTextLength' ],
209            ],
210            self::FIELD_BASE_REV => [
211                'type' => 'hidden',
212                'default' => $baseRev,
213            ],
214            self::FIELD_EDIT_SUMMARY => [
215                'type' => 'text',
216                'default' => '',
217                'label-message' => 'entityschema-summary-generated',
218            ],
219        ];
220    }
221
222    protected function usesOOUI(): bool {
223        return true;
224    }
225
226    /**
227     * Do something exciting on successful processing of the form.  This might be to show
228     * a confirmation message (watch, rollback, etc) or to redirect somewhere else (edit,
229     * protect, etc).
230     */
231    public function onSuccess(): void {
232        Assert::precondition( $this->status !== null,
233            '$this->status must have been set by onSubmit()' );
234        $this->redirectToEntitySchema( $this->status,
235            $this->getRequest()->getVal( 'redirectparams', '' ) );
236    }
237
238    /**
239     * Return the name of the action this object responds to
240     *
241     * @return string Lowercase name
242     */
243    public function getName(): string {
244        return 'edit';
245    }
246
247    public function getRestriction(): string {
248        return $this->getName();
249    }
250
251}