Merge "Update docs/hooks.txt for ShowSearchHitTitle"
[mediawiki.git] / includes / specials / SpecialBotPasswords.php
blob1dd78d7cfcbd0f9fec6f4abd900517b243cb0a4e
1 <?php
2 /**
3 * Implements Special:BotPasswords
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.
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.
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
20 * @file
21 * @ingroup SpecialPage
24 /**
25 * Let users manage bot passwords
27 * @ingroup SpecialPage
29 class SpecialBotPasswords extends FormSpecialPage {
31 /** @var int Central user ID */
32 private $userId = 0;
34 /** @var BotPassword|null Bot password being edited, if any */
35 private $botPassword = null;
37 /** @var string Operation being performed: create, update, delete */
38 private $operation = null;
40 /** @var string New password set, for communication between onSubmit() and onSuccess() */
41 private $password = null;
43 public function __construct() {
44 parent::__construct( 'BotPasswords', 'editmyprivateinfo' );
47 /**
48 * @return bool
50 public function isListed() {
51 return $this->getConfig()->get( 'EnableBotPasswords' );
54 /**
55 * Main execution point
56 * @param string|null $par
58 function execute( $par ) {
59 $this->getOutput()->disallowUserJs();
60 $this->requireLogin();
62 $par = trim( $par );
63 if ( strlen( $par ) === 0 ) {
64 $par = null;
65 } elseif ( strlen( $par ) > BotPassword::APPID_MAXLENGTH ) {
66 throw new ErrorPageError( 'botpasswords', 'botpasswords-bad-appid',
67 [ htmlspecialchars( $par ) ] );
70 parent::execute( $par );
73 protected function checkExecutePermissions( User $user ) {
74 parent::checkExecutePermissions( $user );
76 if ( !$this->getConfig()->get( 'EnableBotPasswords' ) ) {
77 throw new ErrorPageError( 'botpasswords', 'botpasswords-disabled' );
80 $this->userId = CentralIdLookup::factory()->centralIdFromLocalUser( $this->getUser() );
81 if ( !$this->userId ) {
82 throw new ErrorPageError( 'botpasswords', 'botpasswords-no-central-id' );
86 protected function getFormFields() {
87 $fields = [];
89 if ( $this->par !== null ) {
90 $this->botPassword = BotPassword::newFromCentralId( $this->userId, $this->par );
91 if ( !$this->botPassword ) {
92 $this->botPassword = BotPassword::newUnsaved( [
93 'centralId' => $this->userId,
94 'appId' => $this->par,
95 ] );
98 $sep = BotPassword::getSeparator();
99 $fields[] = [
100 'type' => 'info',
101 'label-message' => 'username',
102 'default' => $this->getUser()->getName() . $sep . $this->par
105 if ( $this->botPassword->isSaved() ) {
106 $fields['resetPassword'] = [
107 'type' => 'check',
108 'label-message' => 'botpasswords-label-resetpassword',
112 $lang = $this->getLanguage();
113 $showGrants = MWGrants::getValidGrants();
114 $fields['grants'] = [
115 'type' => 'checkmatrix',
116 'label-message' => 'botpasswords-label-grants',
117 'help-message' => 'botpasswords-help-grants',
118 'columns' => [
119 $this->msg( 'botpasswords-label-grants-column' )->escaped() => 'grant'
121 'rows' => array_combine(
122 array_map( 'MWGrants::getGrantsLink', $showGrants ),
123 $showGrants
125 'default' => array_map(
126 function( $g ) {
127 return "grant-$g";
129 $this->botPassword->getGrants()
131 'tooltips' => array_combine(
132 array_map( 'MWGrants::getGrantsLink', $showGrants ),
133 array_map(
134 function( $rights ) use ( $lang ) {
135 return $lang->semicolonList( array_map( 'User::getRightDescription', $rights ) );
137 array_intersect_key( MWGrants::getRightsByGrant(), array_flip( $showGrants ) )
140 'force-options-on' => array_map(
141 function( $g ) {
142 return "grant-$g";
144 MWGrants::getHiddenGrants()
148 $fields['restrictions'] = [
149 'class' => 'HTMLRestrictionsField',
150 'required' => true,
151 'default' => $this->botPassword->getRestrictions(),
154 } else {
155 $linkRenderer = $this->getLinkRenderer();
156 $dbr = BotPassword::getDB( DB_REPLICA );
157 $res = $dbr->select(
158 'bot_passwords',
159 [ 'bp_app_id' ],
160 [ 'bp_user' => $this->userId ],
161 __METHOD__
163 foreach ( $res as $row ) {
164 $fields[] = [
165 'section' => 'existing',
166 'type' => 'info',
167 'raw' => true,
168 'default' => $linkRenderer->makeKnownLink(
169 $this->getPageTitle( $row->bp_app_id ),
170 $row->bp_app_id
175 $fields['appId'] = [
176 'section' => 'createnew',
177 'type' => 'textwithbutton',
178 'label-message' => 'botpasswords-label-appid',
179 'buttondefault' => $this->msg( 'botpasswords-label-create' )->text(),
180 'buttonflags' => [ 'progressive', 'primary' ],
181 'required' => true,
182 'size' => BotPassword::APPID_MAXLENGTH,
183 'maxlength' => BotPassword::APPID_MAXLENGTH,
184 'validation-callback' => function ( $v ) {
185 $v = trim( $v );
186 return $v !== '' && strlen( $v ) <= BotPassword::APPID_MAXLENGTH;
190 $fields[] = [
191 'type' => 'hidden',
192 'default' => 'new',
193 'name' => 'op',
197 return $fields;
200 protected function alterForm( HTMLForm $form ) {
201 $form->setId( 'mw-botpasswords-form' );
202 $form->setTableId( 'mw-botpasswords-table' );
203 $form->addPreText( $this->msg( 'botpasswords-summary' )->parseAsBlock() );
204 $form->suppressDefaultSubmit();
206 if ( $this->par !== null ) {
207 if ( $this->botPassword->isSaved() ) {
208 $form->setWrapperLegendMsg( 'botpasswords-editexisting' );
209 $form->addButton( [
210 'name' => 'op',
211 'value' => 'update',
212 'label-message' => 'botpasswords-label-update',
213 'flags' => [ 'primary', 'progressive' ],
214 ] );
215 $form->addButton( [
216 'name' => 'op',
217 'value' => 'delete',
218 'label-message' => 'botpasswords-label-delete',
219 'flags' => [ 'destructive' ],
220 ] );
221 } else {
222 $form->setWrapperLegendMsg( 'botpasswords-createnew' );
223 $form->addButton( [
224 'name' => 'op',
225 'value' => 'create',
226 'label-message' => 'botpasswords-label-create',
227 'flags' => [ 'primary', 'progressive' ],
228 ] );
231 $form->addButton( [
232 'name' => 'op',
233 'value' => 'cancel',
234 'label-message' => 'botpasswords-label-cancel'
235 ] );
239 public function onSubmit( array $data ) {
240 $op = $this->getRequest()->getVal( 'op', '' );
242 switch ( $op ) {
243 case 'new':
244 $this->getOutput()->redirect( $this->getPageTitle( $data['appId'] )->getFullURL() );
245 return false;
247 case 'create':
248 $this->operation = 'insert';
249 return $this->save( $data );
251 case 'update':
252 $this->operation = 'update';
253 return $this->save( $data );
255 case 'delete':
256 $this->operation = 'delete';
257 $bp = BotPassword::newFromCentralId( $this->userId, $this->par );
258 if ( $bp ) {
259 $bp->delete();
261 return Status::newGood();
263 case 'cancel':
264 $this->getOutput()->redirect( $this->getPageTitle()->getFullURL() );
265 return false;
268 return false;
271 private function save( array $data ) {
272 $bp = BotPassword::newUnsaved( [
273 'centralId' => $this->userId,
274 'appId' => $this->par,
275 'restrictions' => $data['restrictions'],
276 'grants' => array_merge(
277 MWGrants::getHiddenGrants(),
278 preg_replace( '/^grant-/', '', $data['grants'] )
280 ] );
282 if ( $this->operation === 'insert' || !empty( $data['resetPassword'] ) ) {
283 $this->password = BotPassword::generatePassword( $this->getConfig() );
284 $passwordFactory = new PasswordFactory();
285 $passwordFactory->init( RequestContext::getMain()->getConfig() );
286 $password = $passwordFactory->newFromPlaintext( $this->password );
287 } else {
288 $password = null;
291 if ( $bp->save( $this->operation, $password ) ) {
292 return Status::newGood();
293 } else {
294 // Messages: botpasswords-insert-failed, botpasswords-update-failed
295 return Status::newFatal( "botpasswords-{$this->operation}-failed", $this->par );
299 public function onSuccess() {
300 $out = $this->getOutput();
302 $username = $this->getUser()->getName();
303 switch ( $this->operation ) {
304 case 'insert':
305 $out->setPageTitle( $this->msg( 'botpasswords-created-title' )->text() );
306 $out->addWikiMsg( 'botpasswords-created-body', $this->par, $username );
307 break;
309 case 'update':
310 $out->setPageTitle( $this->msg( 'botpasswords-updated-title' )->text() );
311 $out->addWikiMsg( 'botpasswords-updated-body', $this->par, $username );
312 break;
314 case 'delete':
315 $out->setPageTitle( $this->msg( 'botpasswords-deleted-title' )->text() );
316 $out->addWikiMsg( 'botpasswords-deleted-body', $this->par, $username );
317 $this->password = null;
318 break;
321 if ( $this->password !== null ) {
322 $sep = BotPassword::getSeparator();
323 $out->addWikiMsg(
324 'botpasswords-newpassword',
325 htmlspecialchars( $username . $sep . $this->par ),
326 htmlspecialchars( $this->password ),
327 htmlspecialchars( $username ),
328 htmlspecialchars( $this->par . $sep . $this->password )
330 $this->password = null;
333 $out->addReturnTo( $this->getPageTitle() );
336 protected function getGroupName() {
337 return 'users';
340 protected function getDisplayFormat() {
341 return 'ooui';