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