Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.24% covered (success)
95.24%
80 / 84
86.67% covered (warning)
86.67%
13 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialMute
96.39% covered (success)
96.39%
80 / 83
86.67% covered (warning)
86.67%
13 / 15
31
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 requiresUnblock
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDisplayFormat
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onSuccess
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 onSubmit
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getDescription
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTarget
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 unmuteTarget
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 muteTarget
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 getForm
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 getFormFields
91.30% covered (success)
91.30%
21 / 23
0.00% covered (danger)
0.00%
0 / 1
8.04
 loadTarget
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 isTargetMuted
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getMuteList
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
1<?php
2/*
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 * @ingroup SpecialPage
20 */
21
22namespace MediaWiki\Specials;
23
24use ErrorPageError;
25use MediaWiki\HTMLForm\HTMLForm;
26use MediaWiki\MainConfigNames;
27use MediaWiki\Preferences\MultiUsernameFilter;
28use MediaWiki\SpecialPage\FormSpecialPage;
29use MediaWiki\User\CentralId\CentralIdLookup;
30use MediaWiki\User\Options\UserOptionsManager;
31use MediaWiki\User\User;
32use MediaWiki\User\UserIdentity;
33use MediaWiki\User\UserIdentityLookup;
34use MediaWiki\User\UserIdentityUtils;
35
36/**
37 * A special page that allows users to modify their notification
38 * preferences
39 *
40 * @ingroup SpecialPage
41 */
42class SpecialMute extends FormSpecialPage {
43
44    private const PAGE_NAME = 'Mute';
45
46    /** @var UserIdentity|null */
47    private $target;
48
49    /** @var int */
50    private $targetCentralId;
51
52    private CentralIdLookup $centralIdLookup;
53    private UserOptionsManager $userOptionsManager;
54    private UserIdentityLookup $userIdentityLookup;
55    private UserIdentityUtils $userIdentityUtils;
56
57    /**
58     * @param CentralIdLookup $centralIdLookup
59     * @param UserOptionsManager $userOptionsManager
60     * @param UserIdentityLookup $userIdentityLookup
61     * @param UserIdentityUtils $userIdentityUtils
62     */
63    public function __construct(
64        CentralIdLookup $centralIdLookup,
65        UserOptionsManager $userOptionsManager,
66        UserIdentityLookup $userIdentityLookup,
67        UserIdentityUtils $userIdentityUtils
68    ) {
69        parent::__construct( self::PAGE_NAME, '', false );
70        $this->centralIdLookup = $centralIdLookup;
71        $this->userOptionsManager = $userOptionsManager;
72        $this->userIdentityLookup = $userIdentityLookup;
73        $this->userIdentityUtils = $userIdentityUtils;
74    }
75
76    /**
77     * Entry point for special pages
78     *
79     * @param string|null $par
80     */
81    public function execute( $par ) {
82        $this->addHelpLink(
83            'https://meta.wikimedia.org/wiki/Community_health_initiative/User_Mute_features',
84            true
85        );
86        $this->requireNamedUser( 'specialmute-login-required' );
87        $this->loadTarget( $par );
88
89        parent::execute( $par );
90
91        $out = $this->getOutput();
92        $out->addModules( 'mediawiki.misc-authed-ooui' );
93    }
94
95    /**
96     * @inheritDoc
97     */
98    public function requiresUnblock() {
99        return false;
100    }
101
102    /**
103     * @inheritDoc
104     */
105    protected function getDisplayFormat() {
106        return 'ooui';
107    }
108
109    /**
110     * @inheritDoc
111     */
112    public function onSuccess() {
113        $out = $this->getOutput();
114        $out->addWikiMsg( 'specialmute-success' );
115    }
116
117    /**
118     * @param array $data
119     * @param HTMLForm|null $form
120     * @return bool
121     */
122    public function onSubmit( array $data, HTMLForm $form = null ) {
123        foreach ( $data as $userOption => $value ) {
124            if ( $value ) {
125                $this->muteTarget( $userOption );
126            } else {
127                $this->unmuteTarget( $userOption );
128            }
129        }
130
131        return true;
132    }
133
134    /**
135     * @inheritDoc
136     */
137    public function getDescription() {
138        return $this->msg( 'specialmute' );
139    }
140
141    /**
142     * @return UserIdentity|null
143     */
144    private function getTarget(): ?UserIdentity {
145        return $this->target;
146    }
147
148    /**
149     * Un-mute target
150     *
151     * @param string $userOption up_property key that holds the list of muted users
152     */
153    private function unmuteTarget( $userOption ) {
154        $muteList = $this->getMuteList( $userOption );
155
156        $key = array_search( $this->targetCentralId, $muteList );
157        if ( $key !== false ) {
158            unset( $muteList[$key] );
159            $muteList = implode( "\n", $muteList );
160
161            $user = $this->getUser();
162            $this->userOptionsManager->setOption( $user, $userOption, $muteList );
163            $user->saveSettings();
164        }
165    }
166
167    /**
168     * Mute target
169     * @param string $userOption up_property key that holds the blacklist
170     */
171    private function muteTarget( $userOption ) {
172        // avoid duplicates just in case
173        if ( !$this->isTargetMuted( $userOption ) ) {
174            $muteList = $this->getMuteList( $userOption );
175
176            $muteList[] = $this->targetCentralId;
177            $muteList = implode( "\n", $muteList );
178
179            $user = $this->getUser();
180            $this->userOptionsManager->setOption( $user, $userOption, $muteList );
181            $user->saveSettings();
182        }
183    }
184
185    /**
186     * @inheritDoc
187     */
188    protected function getForm() {
189        $target = $this->getTarget();
190        $form = parent::getForm();
191        $form->setId( 'mw-specialmute-form' );
192        $form->setHeaderHtml( $this->msg( 'specialmute-header', $target ? $target->getName() : '' )->parse() );
193        $form->setSubmitTextMsg( 'specialmute-submit' );
194        $form->setSubmitID( 'save' );
195
196        return $form;
197    }
198
199    /**
200     * @inheritDoc
201     */
202    protected function getFormFields() {
203        $config = $this->getConfig();
204        $fields = [];
205
206        if ( !$config->get( MainConfigNames::EnableUserEmail ) ) {
207            throw new ErrorPageError( 'specialmute', 'specialmute-error-email-disabled' );
208        }
209
210        if ( !$config->get( MainConfigNames::EnableUserEmailMuteList ) ) {
211            throw new ErrorPageError( 'specialmute', 'specialmute-error-mutelist-disabled' );
212        }
213
214        if ( !$this->getUser()->isEmailConfirmed() ) {
215            throw new ErrorPageError( 'specialmute', 'specialmute-error-no-email-set' );
216        }
217
218        $target = $this->getTarget();
219
220        if ( $target && $this->userIdentityUtils->isNamed( $target ) ) {
221            $fields['email-blacklist'] = [
222                'type' => 'check',
223                'label-message' => [
224                    'specialmute-label-mute-email',
225                    $target->getName()
226                ],
227                'default' => $this->isTargetMuted( 'email-blacklist' ),
228            ];
229        }
230
231        $legacyUser = $target ? User::newFromIdentity( $target ) : null;
232        $this->getHookRunner()->onSpecialMuteModifyFormFields( $legacyUser, $this->getUser(), $fields );
233
234        if ( count( $fields ) == 0 ) {
235            throw new ErrorPageError( 'specialmute', 'specialmute-error-no-options' );
236        }
237
238        return $fields;
239    }
240
241    /**
242     * @param string|null $username
243     */
244    private function loadTarget( $username ) {
245        $target = null;
246        if ( $username !== null ) {
247            $target = $this->userIdentityLookup->getUserIdentityByName( $username );
248        }
249        if ( !$target || !$target->isRegistered() ) {
250            throw new ErrorPageError( 'specialmute', 'specialmute-error-invalid-user' );
251        } else {
252            $this->target = $target;
253            $this->targetCentralId = $this->centralIdLookup->centralIdFromLocalUser( $target );
254        }
255    }
256
257    /**
258     * @param string $userOption
259     * @return bool
260     */
261    public function isTargetMuted( $userOption ) {
262        $muteList = $this->getMuteList( $userOption );
263        return in_array( $this->targetCentralId, $muteList, true );
264    }
265
266    /**
267     * @param string $userOption
268     * @return array
269     */
270    private function getMuteList( $userOption ) {
271        $muteList = $this->userOptionsManager->getOption( $this->getUser(), $userOption );
272        if ( !$muteList ) {
273            return [];
274        }
275
276        return MultiUsernameFilter::splitIds( $muteList );
277    }
278}
279
280/**
281 * Retain the old class name for backwards compatibility.
282 * @deprecated since 1.41
283 */
284class_alias( SpecialMute::class, 'SpecialMute' );