1<?php 2/** 3 * Special page which uses an HTMLForm to handle processing. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @ingroup SpecialPage 22 */ 23 24/** 25 * Special page which uses an HTMLForm to handle processing. This is mostly a 26 * clone of FormAction. More special pages should be built this way; maybe this could be 27 * a new structure for SpecialPages. 28 * 29 * @ingroup SpecialPage 30 */ 31abstract class FormSpecialPage extends SpecialPage { 32 /** 33 * The sub-page of the special page. 34 * @var string|null 35 */ 36 protected $par = null; 37 38 /** 39 * @var array|null POST data preserved across re-authentication 40 * @since 1.32 41 */ 42 protected $reauthPostData = null; 43 44 /** 45 * Get an HTMLForm descriptor array 46 * @return array 47 */ 48 abstract protected function getFormFields(); 49 50 /** 51 * Add pre-text to the form 52 * @return string HTML which will be sent to $form->addPreText() 53 */ 54 protected function preText() { 55 return ''; 56 } 57 58 /** 59 * Add post-text to the form 60 * @return string HTML which will be sent to $form->addPostText() 61 */ 62 protected function postText() { 63 return ''; 64 } 65 66 /** 67 * Play with the HTMLForm if you need to more substantially 68 * @param HTMLForm $form 69 */ 70 protected function alterForm( HTMLForm $form ) { 71 } 72 73 /** 74 * Get message prefix for HTMLForm 75 * 76 * @since 1.21 77 * @return string 78 */ 79 protected function getMessagePrefix() { 80 return strtolower( $this->getName() ); 81 } 82 83 /** 84 * Get display format for the form. See HTMLForm documentation for available values. 85 * 86 * @since 1.25 87 * @return string 88 */ 89 protected function getDisplayFormat() { 90 return 'table'; 91 } 92 93 /** 94 * Get the HTMLForm to control behavior 95 * @return HTMLForm|null 96 */ 97 protected function getForm() { 98 $context = $this->getContext(); 99 $onSubmit = [ $this, 'onSubmit' ]; 100 101 if ( $this->reauthPostData ) { 102 // Restore POST data 103 $context = new DerivativeContext( $context ); 104 $oldRequest = $this->getRequest(); 105 $context->setRequest( new DerivativeRequest( 106 $oldRequest, $this->reauthPostData + $oldRequest->getQueryValues(), true 107 ) ); 108 109 // But don't treat it as a "real" submission just in case of some 110 // crazy kind of CSRF. 111 $onSubmit = static function () { 112 return false; 113 }; 114 } 115 116 $form = HTMLForm::factory( 117 $this->getDisplayFormat(), 118 $this->getFormFields(), 119 $context, 120 $this->getMessagePrefix() 121 ); 122 $form->setSubmitCallback( $onSubmit ); 123 if ( $this->getDisplayFormat() !== 'ooui' ) { 124 // No legend and wrapper by default in OOUI forms, but can be set manually 125 // from alterForm() 126 $form->setWrapperLegendMsg( $this->getMessagePrefix() . '-legend' ); 127 } 128 129 $headerMsg = $this->msg( $this->getMessagePrefix() . '-text' ); 130 if ( !$headerMsg->isDisabled() ) { 131 $form->addHeaderText( $headerMsg->parseAsBlock() ); 132 } 133 134 $form->addPreText( $this->preText() ); 135 $form->addPostText( $this->postText() ); 136 $this->alterForm( $form ); 137 if ( $form->getMethod() == 'post' ) { 138 // Retain query parameters (uselang etc) on POST requests 139 $params = array_diff_key( 140 $this->getRequest()->getQueryValues(), [ 'title' => null ] ); 141 $form->addHiddenField( 'redirectparams', wfArrayToCgi( $params ) ); 142 } 143 144 // Give hooks a chance to alter the form, adding extra fields or text etc 145 $this->getHookRunner()->onSpecialPageBeforeFormDisplay( $this->getName(), $form ); 146 147 return $form; 148 } 149 150 /** 151 * Process the form on POST submission. 152 * @phpcs:disable MediaWiki.Commenting.FunctionComment.ExtraParamComment 153 * @param array $data 154 * @param HTMLForm|null $form 155 * @suppress PhanCommentParamWithoutRealParam Many implementations don't have $form 156 * @return bool|string|array|Status As documented for HTMLForm::trySubmit. 157 * @phpcs:enable MediaWiki.Commenting.FunctionComment.ExtraParamComment 158 */ 159 abstract public function onSubmit( array $data /* HTMLForm $form = null */ ); 160 161 /** 162 * Do something exciting on successful processing of the form, most likely to show a 163 * confirmation message 164 * @since 1.22 Default is to do nothing 165 */ 166 public function onSuccess() { 167 } 168 169 /** 170 * Basic SpecialPage workflow: get a form, send it to the user; get some data back, 171 * 172 * @param string|null $par Subpage string if one was specified 173 */ 174 public function execute( $par ) { 175 $this->setParameter( $par ); 176 $this->setHeaders(); 177 178 // This will throw exceptions if there's a problem 179 $this->checkExecutePermissions( $this->getUser() ); 180 181 $securityLevel = $this->getLoginSecurityLevel(); 182 if ( $securityLevel !== false && !$this->checkLoginSecurityLevel( $securityLevel ) ) { 183 return; 184 } 185 186 $form = $this->getForm(); 187 if ( $form->show() ) { 188 $this->onSuccess(); 189 } 190 } 191 192 /** 193 * Maybe do something interesting with the subpage parameter 194 * @param string|null $par 195 */ 196 protected function setParameter( $par ) { 197 $this->par = $par; 198 } 199 200 /** 201 * Called from execute() to check if the given user can perform this action. 202 * Failures here must throw subclasses of ErrorPageError. 203 * @param User $user 204 * @throws UserBlockedError 205 */ 206 protected function checkExecutePermissions( User $user ) { 207 $this->checkPermissions(); 208 209 if ( $this->requiresUnblock() ) { 210 $block = $user->getBlock(); 211 if ( $block && $block->isSitewide() ) { 212 throw new UserBlockedError( 213 $block, 214 $user, 215 $this->getLanguage(), 216 $this->getRequest()->getIP() 217 ); 218 } 219 } 220 221 if ( $this->requiresWrite() ) { 222 $this->checkReadOnly(); 223 } 224 } 225 226 /** 227 * Whether this action requires the wiki not to be locked 228 * @return bool 229 */ 230 public function requiresWrite() { 231 return true; 232 } 233 234 /** 235 * Whether this action cannot be executed by a blocked user 236 * @return bool 237 */ 238 public function requiresUnblock() { 239 return true; 240 } 241 242 /** 243 * Preserve POST data across reauthentication 244 * 245 * @since 1.32 246 * @param array $data 247 */ 248 protected function setReauthPostData( array $data ) { 249 $this->reauthPostData = $data; 250 } 251} 252