Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
15.58% covered (danger)
15.58%
12 / 77
6.25% covered (danger)
6.25%
1 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
FormSpecialPage
15.79% covered (danger)
15.79%
12 / 76
6.25% covered (danger)
6.25%
1 / 16
724.33
0.00% covered (danger)
0.00%
0 / 1
 getFormFields
n/a
0 / 0
n/a
0 / 0
0
 preHtml
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 postHtml
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 alterForm
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMessagePrefix
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDisplayFormat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getForm
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
72
 onSubmit
n/a
0 / 0
n/a
0 / 0
0
 onSuccess
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
72
 getShowAlways
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setParameter
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSubpageField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 checkExecutePermissions
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 requiresPost
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 requiresWrite
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 requiresUnblock
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setReauthPostData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Special page which uses an HTMLForm to handle processing.
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 * @ingroup SpecialPage
8 */
9
10namespace MediaWiki\SpecialPage;
11
12use MediaWiki\Context\DerivativeContext;
13use MediaWiki\Exception\UserBlockedError;
14use MediaWiki\HTMLForm\HTMLForm;
15use MediaWiki\Request\DerivativeRequest;
16use MediaWiki\Status\Status;
17use MediaWiki\User\User;
18
19/**
20 * Special page which uses an HTMLForm to handle processing.  This is mostly a
21 * clone of FormAction.  More special pages should be built this way; maybe this could be
22 * a new structure for SpecialPages.
23 *
24 * @ingroup SpecialPage
25 */
26abstract class FormSpecialPage extends SpecialPage {
27    /**
28     * The subpage of the special page.
29     * @var string|null
30     */
31    protected $par = null;
32
33    /**
34     * @var array|null POST data preserved across re-authentication
35     * @since 1.32
36     */
37    protected $reauthPostData = null;
38
39    /**
40     * Get an HTMLForm descriptor array
41     * @return array
42     */
43    abstract protected function getFormFields();
44
45    /**
46     * Add pre-HTML to the form
47     * @return string HTML which will be sent to $form->addPreHtml()
48     * @since 1.38
49     */
50    protected function preHtml() {
51        return '';
52    }
53
54    /**
55     * Add post-HTML to the form
56     * @return string HTML which will be sent to $form->addPostHtml()
57     * @since 1.38
58     */
59    protected function postHtml() {
60        return '';
61    }
62
63    /**
64     * Play with the HTMLForm if you need to more substantially
65     */
66    protected function alterForm( HTMLForm $form ) {
67    }
68
69    /**
70     * Get message prefix for HTMLForm
71     *
72     * @since 1.21
73     * @return string
74     */
75    protected function getMessagePrefix() {
76        return strtolower( $this->getName() );
77    }
78
79    /**
80     * Get display format for the form. See HTMLForm documentation for available values.
81     *
82     * @since 1.25
83     * @return string
84     */
85    protected function getDisplayFormat() {
86        return 'table';
87    }
88
89    /**
90     * Get the HTMLForm to control behavior
91     * @return HTMLForm|null
92     */
93    protected function getForm() {
94        $context = $this->getContext();
95        $onSubmit = $this->onSubmit( ... );
96
97        if ( $this->reauthPostData ) {
98            // Restore POST data
99            $context = new DerivativeContext( $context );
100            $oldRequest = $this->getRequest();
101            $context->setRequest( new DerivativeRequest(
102                $oldRequest, $this->reauthPostData + $oldRequest->getQueryValues(), true
103            ) );
104
105            // But don't treat it as a "real" submission just in case of some
106            // crazy kind of CSRF.
107            $onSubmit = static function () {
108                return false;
109            };
110        }
111
112        $form = HTMLForm::factory(
113            $this->getDisplayFormat(),
114            $this->getFormFields(),
115            $context,
116            $this->getMessagePrefix()
117        );
118        if ( !$this->requiresPost() ) {
119            $form->setMethod( 'get' );
120        }
121        $form->setSubmitCallback( $onSubmit );
122        if ( $this->getDisplayFormat() !== 'ooui' ) {
123            // No legend and wrapper by default in OOUI forms, but can be set manually
124            // from alterForm()
125            $form->setWrapperLegendMsg( $this->getMessagePrefix() . '-legend' );
126        }
127
128        $headerMsg = $this->msg( $this->getMessagePrefix() . '-text' );
129        if ( !$headerMsg->isDisabled() ) {
130            $form->addHeaderHtml( $headerMsg->parseAsBlock() );
131        }
132
133        $form->addPreHtml( $this->preHtml() );
134        $form->addPostHtml( $this->postHtml() );
135
136        // Give precedence to subpage syntax
137        $field = $this->getSubpageField();
138        // cast to string so that "0" is not thrown away
139        if ( strval( $this->par ) !== '' && $field ) {
140            $this->getRequest()->setVal( $form->getField( $field )->getName(), $this->par );
141            $form->setTitle( $this->getPageTitle() );
142        }
143        $this->alterForm( $form );
144        if ( $form->getMethod() == 'post' ) {
145            // Retain query parameters (uselang etc) on POST requests
146            $params = array_diff_key(
147                $this->getRequest()->getQueryValues(), [ 'title' => null ] );
148            $form->addHiddenField( 'redirectparams', wfArrayToCgi( $params ) );
149        }
150
151        // Give hooks a chance to alter the form, adding extra fields or text etc
152        $this->getHookRunner()->onSpecialPageBeforeFormDisplay( $this->getName(), $form );
153
154        return $form;
155    }
156
157    /**
158     * Process the form on submission.
159     * @phpcs:disable MediaWiki.Commenting.FunctionComment.ExtraParamComment
160     * @param array $data
161     * @param HTMLForm|null $form
162     * @suppress PhanCommentParamWithoutRealParam Many implementations don't have $form
163     * @return bool|string|array|Status As documented for HTMLForm::trySubmit.
164     * @phpcs:enable MediaWiki.Commenting.FunctionComment.ExtraParamComment
165     */
166    abstract public function onSubmit( array $data /* HTMLForm $form = null */ );
167
168    /**
169     * Do something exciting on successful processing of the form, most likely to show a
170     * confirmation message
171     * @since 1.22 Default is to do nothing
172     */
173    public function onSuccess() {
174    }
175
176    /**
177     * Basic SpecialPage workflow: get a form, send it to the user; get some data back,
178     *
179     * @param string|null $par Subpage string if one was specified
180     */
181    public function execute( $par ) {
182        $this->setParameter( $par );
183        $this->setHeaders();
184        $this->outputHeader();
185
186        // This will throw exceptions if there's a problem
187        $this->checkExecutePermissions( $this->getUser() );
188
189        $securityLevel = $this->getLoginSecurityLevel();
190        if ( $securityLevel !== false && !$this->checkLoginSecurityLevel( $securityLevel ) ) {
191            return;
192        }
193
194        $form = $this->getForm();
195        // GET forms can be set as includable
196        if ( !$this->including() ) {
197            $result = $this->getShowAlways() ? $form->showAlways() : $form->show();
198        } else {
199            $result = $form->prepareForm()->tryAuthorizedSubmit();
200        }
201        if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
202            $this->onSuccess();
203        }
204    }
205
206    /**
207     * Whether the form should always be shown despite the success of submission.
208     * @since 1.40
209     * @return bool
210     */
211    protected function getShowAlways() {
212        return false;
213    }
214
215    /**
216     * Maybe do something interesting with the subpage parameter
217     * @param string|null $par
218     */
219    protected function setParameter( $par ) {
220        $this->par = $par;
221    }
222
223    /**
224     * Override this function to set the field name used in the subpage syntax.
225     * @since 1.40
226     * @return false|string
227     */
228    protected function getSubpageField() {
229        return false;
230    }
231
232    /**
233     * Called from execute() to check if the given user can perform this action.
234     * Failures here must throw subclasses of ErrorPageError.
235     * @param User $user
236     * @throws UserBlockedError
237     */
238    protected function checkExecutePermissions( User $user ) {
239        $this->checkPermissions();
240
241        if ( $this->requiresUnblock() ) {
242            $block = $user->getBlock();
243            if ( $block && $block->isSitewide() ) {
244                throw new UserBlockedError(
245                    $block,
246                    $user,
247                    $this->getLanguage(),
248                    $this->getRequest()->getIP()
249                );
250            }
251        }
252
253        if ( $this->requiresWrite() ) {
254            $this->checkReadOnly();
255        }
256    }
257
258    /**
259     * Whether this action should using POST method to submit, default to true
260     * @since 1.40
261     * @return bool
262     */
263    public function requiresPost() {
264        return true;
265    }
266
267    /**
268     * Whether this action requires the wiki not to be locked, default to requiresPost()
269     * @return bool
270     */
271    public function requiresWrite() {
272        return $this->requiresPost();
273    }
274
275    /**
276     * Whether this action cannot be executed by a blocked user, default to requiresPost()
277     * @return bool
278     */
279    public function requiresUnblock() {
280        return $this->requiresPost();
281    }
282
283    /**
284     * Preserve POST data across reauthentication
285     *
286     * @since 1.32
287     * @param array $data
288     */
289    protected function setReauthPostData( array $data ) {
290        $this->reauthPostData = $data;
291    }
292}
293
294/** @deprecated class alias since 1.41 */
295class_alias( FormSpecialPage::class, 'FormSpecialPage' );