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 */
20
21namespace MediaWiki\Specials;
22
23use ErrorPageError;
24use MediaWiki\HTMLForm\HTMLForm;
25use MediaWiki\MainConfigNames;
26use MediaWiki\Preferences\MultiUsernameFilter;
27use MediaWiki\SpecialPage\FormSpecialPage;
28use MediaWiki\User\CentralId\CentralIdLookup;
29use MediaWiki\User\Options\UserOptionsManager;
30use MediaWiki\User\User;
31use MediaWiki\User\UserIdentity;
32use MediaWiki\User\UserIdentityLookup;
33use MediaWiki\User\UserIdentityUtils;
34
35/**
36 * Modify your own notification preferences
37 *
38 * @ingroup SpecialPage
39 */
40class SpecialMute extends FormSpecialPage {
41
42    private const PAGE_NAME = 'Mute';
43
44    /** @var UserIdentity|null */
45    private $target;
46
47    /** @var int */
48    private $targetCentralId;
49
50    private CentralIdLookup $centralIdLookup;
51    private UserOptionsManager $userOptionsManager;
52    private UserIdentityLookup $userIdentityLookup;
53    private UserIdentityUtils $userIdentityUtils;
54
55    /**
56     * @param CentralIdLookup $centralIdLookup
57     * @param UserOptionsManager $userOptionsManager
58     * @param UserIdentityLookup $userIdentityLookup
59     * @param UserIdentityUtils $userIdentityUtils
60     */
61    public function __construct(
62        CentralIdLookup $centralIdLookup,
63        UserOptionsManager $userOptionsManager,
64        UserIdentityLookup $userIdentityLookup,
65        UserIdentityUtils $userIdentityUtils
66    ) {
67        parent::__construct( self::PAGE_NAME, '', false );
68        $this->centralIdLookup = $centralIdLookup;
69        $this->userOptionsManager = $userOptionsManager;
70        $this->userIdentityLookup = $userIdentityLookup;
71        $this->userIdentityUtils = $userIdentityUtils;
72    }
73
74    /**
75     * Entry point for special pages
76     *
77     * @param string|null $par
78     */
79    public function execute( $par ) {
80        $this->addHelpLink(
81            'https://meta.wikimedia.org/wiki/Community_health_initiative/User_Mute_features',
82            true
83        );
84        $this->requireNamedUser( 'specialmute-login-required' );
85        $this->loadTarget( $par );
86
87        parent::execute( $par );
88
89        $out = $this->getOutput();
90        $out->addModules( 'mediawiki.misc-authed-ooui' );
91    }
92
93    /**
94     * @inheritDoc
95     */
96    public function requiresUnblock() {
97        return false;
98    }
99
100    /**
101     * @inheritDoc
102     */
103    protected function getDisplayFormat() {
104        return 'ooui';
105    }
106
107    /**
108     * @inheritDoc
109     */
110    public function onSuccess() {
111        $out = $this->getOutput();
112        $out->addWikiMsg( 'specialmute-success' );
113    }
114
115    /**
116     * @param array $data
117     * @param HTMLForm|null $form
118     * @return bool
119     */
120    public function onSubmit( array $data, ?HTMLForm $form = null ) {
121        foreach ( $data as $userOption => $value ) {
122            if ( $value ) {
123                $this->muteTarget( $userOption );
124            } else {
125                $this->unmuteTarget( $userOption );
126            }
127        }
128
129        return true;
130    }
131
132    /**
133     * @inheritDoc
134     */
135    public function getDescription() {
136        return $this->msg( 'specialmute' );
137    }
138
139    /**
140     * @return UserIdentity|null
141     */
142    private function getTarget(): ?UserIdentity {
143        return $this->target;
144    }
145
146    /**
147     * Un-mute target
148     *
149     * @param string $userOption up_property key that holds the list of muted users
150     */
151    private function unmuteTarget( $userOption ) {
152        $muteList = $this->getMuteList( $userOption );
153
154        $key = array_search( $this->targetCentralId, $muteList );
155        if ( $key !== false ) {
156            unset( $muteList[$key] );
157            $muteList = implode( "\n", $muteList );
158
159            $user = $this->getUser();
160            $this->userOptionsManager->setOption( $user, $userOption, $muteList );
161            $user->saveSettings();
162        }
163    }
164
165    /**
166     * Mute target
167     * @param string $userOption up_property key that holds the blacklist
168     */
169    private function muteTarget( $userOption ) {
170        // avoid duplicates just in case
171        if ( !$this->isTargetMuted( $userOption ) ) {
172            $muteList = $this->getMuteList( $userOption );
173
174            $muteList[] = $this->targetCentralId;
175            $muteList = implode( "\n", $muteList );
176
177            $user = $this->getUser();
178            $this->userOptionsManager->setOption( $user, $userOption, $muteList );
179            $user->saveSettings();
180        }
181    }
182
183    /**
184     * @inheritDoc
185     */
186    protected function getForm() {
187        $target = $this->getTarget();
188        $form = parent::getForm();
189        $form->setId( 'mw-specialmute-form' );
190        $form->setHeaderHtml( $this->msg( 'specialmute-header', $target ? $target->getName() : '' )->parse() );
191        $form->setSubmitTextMsg( 'specialmute-submit' );
192        $form->setSubmitID( 'save' );
193
194        return $form;
195    }
196
197    /**
198     * @inheritDoc
199     */
200    protected function getFormFields() {
201        $config = $this->getConfig();
202        $fields = [];
203
204        if ( !$config->get( MainConfigNames::EnableUserEmail ) ) {
205            throw new ErrorPageError( 'specialmute', 'specialmute-error-email-disabled' );
206        }
207
208        if ( !$config->get( MainConfigNames::EnableUserEmailMuteList ) ) {
209            throw new ErrorPageError( 'specialmute', 'specialmute-error-mutelist-disabled' );
210        }
211
212        if ( !$this->getUser()->isEmailConfirmed() ) {
213            throw new ErrorPageError( 'specialmute', 'specialmute-error-no-email-set' );
214        }
215
216        $target = $this->getTarget();
217
218        if ( $target && $this->userIdentityUtils->isNamed( $target ) ) {
219            $fields['email-blacklist'] = [
220                'type' => 'check',
221                'label-message' => [
222                    'specialmute-label-mute-email',
223                    $target->getName()
224                ],
225                'default' => $this->isTargetMuted( 'email-blacklist' ),
226            ];
227        }
228
229        $legacyUser = $target ? User::newFromIdentity( $target ) : null;
230        $this->getHookRunner()->onSpecialMuteModifyFormFields( $legacyUser, $this->getUser(), $fields );
231
232        if ( count( $fields ) == 0 ) {
233            throw new ErrorPageError( 'specialmute', 'specialmute-error-no-options' );
234        }
235
236        return $fields;
237    }
238
239    /**
240     * @param string|null $username
241     */
242    private function loadTarget( $username ) {
243        $target = null;
244        if ( $username !== null ) {
245            $target = $this->userIdentityLookup->getUserIdentityByName( $username );
246        }
247        if ( !$target || !$target->isRegistered() ) {
248            throw new ErrorPageError( 'specialmute', 'specialmute-error-invalid-user' );
249        } else {
250            $this->target = $target;
251            $this->targetCentralId = $this->centralIdLookup->centralIdFromLocalUser( $target );
252        }
253    }
254
255    /**
256     * @param string $userOption
257     * @return bool
258     */
259    public function isTargetMuted( $userOption ) {
260        $muteList = $this->getMuteList( $userOption );
261        return in_array( $this->targetCentralId, $muteList, true );
262    }
263
264    /**
265     * @param string $userOption
266     * @return array
267     */
268    private function getMuteList( $userOption ) {
269        $muteList = $this->userOptionsManager->getOption( $this->getUser(), $userOption );
270        if ( !$muteList ) {
271            return [];
272        }
273
274        return MultiUsernameFilter::splitIds( $muteList );
275    }
276}
277
278/**
279 * Retain the old class name for backwards compatibility.
280 * @deprecated since 1.41
281 */
282class_alias( SpecialMute::class, 'SpecialMute' );