Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
99.33% covered (success)
99.33%
149 / 150
90.91% covered (success)
90.91%
10 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
NewEntitySchema
99.33% covered (success)
99.33%
149 / 150
90.91% covered (success)
90.91%
10 / 11
19
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
4
 submitCallback
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
 getDescription
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFormFields
100.00% covered (success)
100.00%
64 / 64
100.00% covered (success)
100.00%
1 / 1
1
 displayBeforeForm
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getCopyrightHTML
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getWarnings
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 addJavaScript
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 checkPermissionsWithSubpage
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare( strict_types = 1 );
4
5namespace EntitySchema\MediaWiki\Specials;
6
7use EntitySchema\DataAccess\EntitySchemaStatus;
8use EntitySchema\DataAccess\MediaWikiPageUpdaterFactory;
9use EntitySchema\DataAccess\MediaWikiRevisionEntitySchemaInserter;
10use EntitySchema\Domain\Storage\IdGenerator;
11use EntitySchema\MediaWiki\EntitySchemaRedirectTrait;
12use EntitySchema\MediaWiki\EntitySchemaServices;
13use EntitySchema\Presentation\InputValidator;
14use MediaWiki\Exception\PermissionsError;
15use MediaWiki\Html\Html;
16use MediaWiki\HTMLForm\HTMLForm;
17use MediaWiki\MediaWikiServices;
18use MediaWiki\Message\Message;
19use MediaWiki\Output\OutputPage;
20use MediaWiki\SpecialPage\SpecialPage;
21use MediaWiki\Status\Status;
22use MediaWiki\User\TempUser\TempUserConfig;
23use Wikibase\Lib\SettingsArray;
24use Wikibase\Repo\CopyrightMessageBuilder;
25use Wikibase\Repo\Specials\SpecialPageCopyrightView;
26
27/**
28 * Page for creating a new EntitySchema.
29 *
30 * @license GPL-2.0-or-later
31 */
32class NewEntitySchema extends SpecialPage {
33
34    use EntitySchemaRedirectTrait;
35
36    public const FIELD_DESCRIPTION = 'description';
37
38    public const FIELD_LABEL = 'label';
39
40    public const FIELD_ALIASES = 'aliases';
41
42    public const FIELD_SCHEMA_TEXT = 'schema-text';
43
44    public const FIELD_LANGUAGE = 'languagecode';
45
46    private IdGenerator $idGenerator;
47
48    private SpecialPageCopyrightView $copyrightView;
49
50    private TempUserConfig $tempUserConfig;
51
52    private MediaWikiPageUpdaterFactory $pageUpdaterFactory;
53
54    public function __construct(
55        TempUserConfig $tempUserConfig,
56        SettingsArray $repoSettings,
57        IdGenerator $idGenerator,
58        MediaWikiPageUpdaterFactory $pageUpdaterFactory
59    ) {
60        parent::__construct(
61            'NewEntitySchema',
62            'createpage'
63        );
64        $this->idGenerator = $idGenerator;
65        $this->copyrightView = new SpecialPageCopyrightView(
66            new CopyrightMessageBuilder(),
67            $repoSettings->getSetting( 'dataRightsUrl' ),
68            $repoSettings->getSetting( 'dataRightsText' )
69        );
70        $this->tempUserConfig = $tempUserConfig;
71        $this->pageUpdaterFactory = $pageUpdaterFactory;
72    }
73
74    /** @inheritDoc */
75    public function execute( $subPage ): void {
76        parent::execute( $subPage );
77
78        $this->checkPermissionsWithSubpage( $subPage );
79        $this->checkReadOnly();
80
81        $form = HTMLForm::factory( 'ooui', $this->getFormFields(), $this->getContext() )
82            ->setSubmitName( 'submit' )
83            ->setSubmitID( 'entityschema-newschema-submit' )
84            ->setSubmitTextMsg( 'entityschema-newschema-submit' )
85            ->setValidationErrorMessage( [ [
86                'entityschema-error-possibly-multiple-messages-available',
87            ] ] )
88            ->setSubmitCallback( [ $this, 'submitCallback' ] );
89        $form->prepareForm();
90
91        /** @var Status|false $submitStatus `false` if form was not submitted */
92        $submitStatus = $form->tryAuthorizedSubmit();
93
94        if ( $submitStatus && $submitStatus->isGood() ) {
95            // wrap it, in case HTMLForm turned it into a generic Status
96            $submitStatus = EntitySchemaStatus::wrap( $submitStatus );
97            $this->redirectToEntitySchema( $submitStatus );
98            return;
99        }
100
101        $this->addJavaScript();
102        $this->displayBeforeForm( $this->getOutput() );
103
104        $form->displayForm( $submitStatus ?: Status::newGood() );
105    }
106
107    public function submitCallback( array $data, HTMLForm $form ): Status {
108        // TODO: no form data validation??
109
110        $services = MediaWikiServices::getInstance();
111        $schemaInserter = new MediaWikiRevisionEntitySchemaInserter(
112            $this->pageUpdaterFactory,
113            EntitySchemaServices::getWatchlistUpdater( $services ),
114            $this->idGenerator,
115            $this->getContext(),
116            $services->getLanguageFactory(),
117            EntitySchemaServices::getHookRunner( $services )
118        );
119        return $schemaInserter->insertSchema(
120            $data[self::FIELD_LANGUAGE],
121            $data[self::FIELD_LABEL],
122            $data[self::FIELD_DESCRIPTION],
123            array_filter( array_map( 'trim', explode( '|', $data[self::FIELD_ALIASES] ) ) ),
124            $data[self::FIELD_SCHEMA_TEXT]
125        );
126    }
127
128    public function getDescription(): Message {
129        return $this->msg( 'special-newschema' );
130    }
131
132    protected function getGroupName(): string {
133        return 'wikibase';
134    }
135
136    private function getFormFields(): array {
137        $langCode = $this->getLanguage()->getCode();
138        $langName = MediaWikiServices::getInstance()->getLanguageNameUtils()
139            ->getLanguageName( $langCode, $langCode );
140        $inputValidator = InputValidator::newFromGlobalState();
141        return [
142            self::FIELD_LABEL => [
143                'name' => self::FIELD_LABEL,
144                'type' => 'text',
145                'id' => 'entityschema-newschema-label',
146                'required' => true,
147                'default' => '',
148                'placeholder-message' => $this->msg( 'entityschema-label-edit-placeholder' )
149                    ->params( $langName ),
150                'label-message' => 'entityschema-newschema-label',
151                'validation-callback' => [
152                    $inputValidator,
153                    'validateStringInputLength',
154                ],
155            ],
156            self::FIELD_DESCRIPTION => [
157                'name' => self::FIELD_DESCRIPTION,
158                'type' => 'text',
159                'default' => '',
160                'id' => 'entityschema-newschema-description',
161                'placeholder-message' => $this->msg( 'entityschema-description-edit-placeholder' )
162                    ->params( $langName ),
163                'label-message' => 'entityschema-newschema-description',
164                'validation-callback' => [
165                    $inputValidator,
166                    'validateStringInputLength',
167                ],
168            ],
169            self::FIELD_ALIASES => [
170                'name' => self::FIELD_ALIASES,
171                'type' => 'text',
172                'default' => '',
173                'id' => 'entityschema-newschema-aliases',
174                'placeholder-message' => $this->msg( 'entityschema-aliases-edit-placeholder' )
175                    ->params( $langName ),
176                'label-message' => 'entityschema-newschema-aliases',
177                'validation-callback' => [
178                    $inputValidator,
179                    'validateAliasesLength',
180                ],
181            ],
182            self::FIELD_SCHEMA_TEXT => [
183                'name' => self::FIELD_SCHEMA_TEXT,
184                'type' => 'textarea',
185                'default' => '',
186                'id' => 'entityschema-newschema-schema-text',
187                'placeholder' => "<human> {\n  wdt:P31 [wd:Q5]\n}",
188                'label-message' => 'entityschema-newschema-schema-shexc',
189                'validation-callback' => [
190                    $inputValidator,
191                    'validateSchemaTextLength',
192                ],
193                'useeditfont' => true,
194            ],
195            self::FIELD_LANGUAGE => [
196                'name' => self::FIELD_LANGUAGE,
197                'type' => 'hidden',
198                'default' => $langCode,
199            ],
200        ];
201    }
202
203    private function displayBeforeForm( OutputPage $output ): void {
204        $output->addHTML( $this->getCopyrightHTML() );
205
206        foreach ( $this->getWarnings() as $warning ) {
207            $output->addHTML( Html::rawElement( 'div', [ 'class' => 'warning' ], $warning ) );
208        }
209    }
210
211    /**
212     * @return string HTML
213     */
214    private function getCopyrightHTML() {
215        return $this->copyrightView
216            ->getHtml( $this->getLanguage(), 'entityschema-newschema-submit' );
217    }
218
219    private function getWarnings(): array {
220        if ( $this->getUser()->isAnon() && !$this->tempUserConfig->isEnabled() ) {
221            return [
222                $this->msg(
223                    'entityschema-anonymouseditwarning'
224                )->parse(),
225            ];
226        }
227
228        return [];
229    }
230
231    private function addJavaScript(): void {
232        $output = $this->getOutput();
233        $output->addModules( [
234            'ext.EntitySchema.special.newEntitySchema',
235        ] );
236        $output->addJsConfigVars( [
237            'wgEntitySchemaSchemaTextMaxSizeBytes' =>
238                intval( $this->getConfig()->get( 'EntitySchemaSchemaTextMaxSizeBytes' ) ),
239            'wgEntitySchemaNameBadgeMaxSizeChars' =>
240                intval( $this->getConfig()->get( 'EntitySchemaNameBadgeMaxSizeChars' ) ),
241        ] );
242    }
243
244    /**
245     * Checks if the user has permissions to perform this page’s action,
246     * and throws a {@link PermissionsError} if they don’t.
247     *
248     * @throws PermissionsError
249     */
250    protected function checkPermissionsWithSubpage( ?string $subPage ): void {
251        $pm = MediaWikiServices::getInstance()->getPermissionManager();
252        $checkReplica = !$this->getRequest()->wasPosted();
253        $permissionErrors = $pm->getPermissionErrors(
254            $this->getRestriction(),
255            $this->getUser(),
256            $this->getPageTitle( $subPage ),
257            $checkReplica ? $pm::RIGOR_FULL : $pm::RIGOR_SECURE,
258            [
259                'ns-specialprotected', // ignore “special pages cannot be edited”
260            ]
261        );
262        if ( $permissionErrors !== [] ) {
263            // reindex $permissionErrors:
264            // the ignoreErrors param (ns-specialprotected) may have left holes,
265            // but PermissionsError expects $errors[0] to exist
266            $permissionErrors = array_values( $permissionErrors );
267            throw new PermissionsError( $this->getRestriction(), $permissionErrors );
268        }
269    }
270
271}