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