Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 103
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
PreferenceHooks
0.00% covered (danger)
0.00%
0 / 103
0.00% covered (danger)
0.00%
0 / 4
702
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 arrayRenameKey
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 onGetPreferences
0.00% covered (danger)
0.00%
0 / 89
0.00% covered (danger)
0.00%
0 / 1
380
 onLocalUserCreated
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * DiscussionTools preference hooks
4 *
5 * @file
6 * @ingroup Extensions
7 * @license MIT
8 */
9
10namespace MediaWiki\Extension\DiscussionTools\Hooks;
11
12use MediaWiki\Auth\Hook\LocalUserCreatedHook;
13use MediaWiki\Config\Config;
14use MediaWiki\Config\ConfigFactory;
15use MediaWiki\Html\Html;
16use MediaWiki\Linker\LinkRenderer;
17use MediaWiki\MediaWikiServices;
18use MediaWiki\Preferences\Hook\GetPreferencesHook;
19use MediaWiki\SpecialPage\SpecialPage;
20use MediaWiki\User\User;
21
22class PreferenceHooks implements
23    LocalUserCreatedHook,
24    GetPreferencesHook
25{
26
27    private Config $config;
28    private LinkRenderer $linkRenderer;
29
30    public function __construct(
31        ConfigFactory $configFactory,
32        LinkRenderer $linkRenderer
33    ) {
34        $this->config = $configFactory->makeConfig( 'discussiontools' );
35        $this->linkRenderer = $linkRenderer;
36    }
37
38    /**
39     * Rename a key in an array while preserving the order of associative array keys.
40     *
41     * @param array $array
42     * @param string $from
43     * @param string $to
44     * @return array Modified array
45     */
46    private static function arrayRenameKey( array $array, string $from, string $to ): array {
47        $out = [];
48        foreach ( $array as $key => $value ) {
49            if ( $key === $from ) {
50                $key = $to;
51            }
52            $out[$key] = $value;
53        }
54        return $out;
55    }
56
57    /**
58     * Handler for the GetPreferences hook, to add and hide user preferences as configured
59     *
60     * @param User $user
61     * @param array &$preferences
62     */
63    public function onGetPreferences( $user, &$preferences ) {
64        if ( HookUtils::isFeatureAvailableToUser( $user ) ) {
65            $preferences['discussiontools-summary'] = [
66                'type' => 'info',
67                'default' => wfMessage( 'discussiontools-preference-summary' )->parse(),
68                'raw' => true,
69                'section' => 'editing/discussion',
70            ];
71        }
72        foreach ( HookUtils::FEATURES as $feature ) {
73            if (
74                $feature === HookUtils::VISUALENHANCEMENTS_REPLY ||
75                $feature === HookUtils::VISUALENHANCEMENTS_PAGEFRAME
76            ) {
77                // Feature is never user-configurable
78                continue;
79            }
80            if ( HookUtils::isFeatureAvailableToUser( $user, $feature ) ) {
81                $preferences["discussiontools-$feature"] = [
82                    'type' => 'toggle',
83                    // The following messages are used here:
84                    // * discussiontools-preference-autotopicsub
85                    // * discussiontools-preference-newtopictool
86                    // * discussiontools-preference-replytool
87                    // * discussiontools-preference-sourcemodetoolbar
88                    // * discussiontools-preference-topicsubscription
89                    // * discussiontools-preference-visualenhancements
90                    'label-message' => "discussiontools-preference-$feature",
91                    // The following messages are used here:
92                    // * discussiontools-preference-autotopicsub-help
93                    // * discussiontools-preference-newtopictool-help
94                    // * discussiontools-preference-replytool-help
95                    // * discussiontools-preference-sourcemodetoolbar-help
96                    // * discussiontools-preference-topicsubscription-help
97                    // * discussiontools-preference-visualenhancements-help
98                    'help-message' => "discussiontools-preference-$feature-help",
99                    'section' => 'editing/discussion',
100                ];
101
102                // Option to enable/disable new topic tool on pages that haven't been created
103                // (it's inside this loop to place the options in a nice order)
104                if ( $feature === HookUtils::NEWTOPICTOOL ) {
105                    $preferences["discussiontools-newtopictool-createpage"] = [
106                        'type' => 'radio',
107                        'cssclass' => 'mw-htmlform-checkradio-indent',
108                        'label-message' => 'discussiontools-preference-newtopictool-createpage',
109                        'options-messages' => [
110                            'discussiontools-preference-newtopictool-createpage-newtopictool' => 1,
111                            'discussiontools-preference-newtopictool-createpage-editor' => 0,
112                        ],
113                        'disable-if' => [ '===', 'discussiontools-' . HookUtils::NEWTOPICTOOL, '' ],
114                        'section' => 'editing/discussion',
115                    ];
116                }
117
118                // Make this option unavailable when a conflicting Convenient Discussions gadget exists
119                // (we can't use 'disable-if' or 'hide-if', because they don't let us change the labels).
120                if ( HookUtils::featureConflictsWithGadget( $user, $feature ) ) {
121                    $preferences["discussiontools-$feature"]['disabled'] = true;
122                    $preferences["discussiontools-$feature"]['help-message'] =
123                        [ 'discussiontools-preference-gadget-conflict', 'Special:Preferences#mw-prefsection-gadgets' ];
124                }
125            }
126        }
127
128        if ( isset( $preferences['discussiontools-' . HookUtils::SOURCEMODETOOLBAR] ) && (
129            isset( $preferences['discussiontools-' . HookUtils::REPLYTOOL] ) ||
130            isset( $preferences['discussiontools-' . HookUtils::NEWTOPICTOOL] )
131        ) ) {
132            // Disable this option when it would have no effect
133            // (both reply tool and new topic tool are disabled)
134            $preferences['discussiontools-' . HookUtils::SOURCEMODETOOLBAR]['disable-if'] = [ 'AND' ];
135
136            if ( isset( $preferences['discussiontools-' . HookUtils::REPLYTOOL] ) &&
137                // GlobalPreferences extension would delete disabled fields, avoid referring to it.
138                !( $preferences['discussiontools-' . HookUtils::REPLYTOOL]['disabled'] ?? false )
139            ) {
140                $preferences['discussiontools-' . HookUtils::SOURCEMODETOOLBAR]['disable-if'][] = [
141                    '===', 'discussiontools-' . HookUtils::REPLYTOOL, ''
142                ];
143            }
144            if ( isset( $preferences['discussiontools-' . HookUtils::NEWTOPICTOOL] ) ) {
145                $preferences['discussiontools-' . HookUtils::SOURCEMODETOOLBAR]['disable-if'][] = [
146                    '===', 'discussiontools-' . HookUtils::NEWTOPICTOOL, ''
147                ];
148            }
149        }
150
151        if ( isset( $preferences['discussiontools-' . HookUtils::AUTOTOPICSUB] ) &&
152            isset( $preferences['discussiontools-' . HookUtils::TOPICSUBSCRIPTION] )
153        ) {
154            // Disable automatic subscriptions when subscriptions are disabled
155            $preferences['discussiontools-' . HookUtils::AUTOTOPICSUB]['disable-if'] = [
156                '===', 'discussiontools-' . HookUtils::TOPICSUBSCRIPTION, ''
157            ];
158        }
159
160        $preferences['discussiontools-showadvanced'] = [
161            'type' => 'api',
162        ];
163        $preferences['discussiontools-seenautotopicsubpopup'] = [
164            'type' => 'api',
165        ];
166
167        if ( !$this->config->get( 'DiscussionToolsBeta' ) ) {
168            // When out of beta, preserve the user preference in case we
169            // bring back the beta feature for a new sub-feature. (T272071)
170            $preferences['discussiontools-betaenable'] = [
171                'type' => 'api'
172            ];
173        }
174
175        $preferences['discussiontools-editmode'] = [
176            'type' => 'api',
177            'validation-callback' => static function ( $value ) {
178                return in_array( $value, [ '', 'source', 'visual' ], true );
179            },
180        ];
181
182        // Add a link to Special:TopicSubscriptions to the Echo preferences matrix
183        $categoryMessage = wfMessage( 'echo-category-title-dt-subscription' )->numParams( 1 )->escaped();
184        $categoryMessageExtra = $categoryMessage .
185            Html::element( 'br' ) .
186            wfMessage( 'parentheses' )->rawParams(
187                $this->linkRenderer->makeLink(
188                    SpecialPage::getTitleFor( 'TopicSubscriptions' ),
189                    wfMessage( 'discussiontools-topicsubscription-preferences-editsubscriptions' )->text()
190                )
191            )->escaped();
192        if ( isset( $preferences['echo-subscriptions']['rows'] ) ) {
193            $preferences['echo-subscriptions']['rows'] = static::arrayRenameKey(
194                $preferences['echo-subscriptions']['rows'],
195                $categoryMessage,
196                $categoryMessageExtra
197            );
198        }
199        if ( isset( $preferences['echo-subscriptions']['tooltips'] ) ) {
200            $preferences['echo-subscriptions']['tooltips'] = static::arrayRenameKey(
201                // Phan insists that this key doesn't exist, even though we just checked with isset()
202                // @phan-suppress-next-line PhanTypeInvalidDimOffset, PhanTypeMismatchArgument
203                $preferences['echo-subscriptions']['tooltips'],
204                $categoryMessage,
205                $categoryMessageExtra
206            );
207        }
208    }
209
210    /**
211     * Handler for LocalUserCreated hook.
212     * @see https://www.mediawiki.org/wiki/Manual:Hooks/LocalUserCreated
213     * @param User $user User object for the created user
214     * @param bool $autocreated Whether this was an auto-creation
215     * @return bool|void True or no return value to continue or false to abort
216     */
217    public function onLocalUserCreated( $user, $autocreated ) {
218        if ( $user->isTemp() ) {
219            // Temp users can't have preferences (and we don't let them have topic subscriptions anyway)
220            return;
221        }
222
223        $userOptionsManager = MediaWikiServices::getInstance()->getUserOptionsManager();
224        // We want new users to be created with email-subscriptions to our notifications enabled
225        $userOptionsManager->setOption( $user, 'echo-subscriptions-email-dt-subscription', true );
226        // The auto topic subscription feature is disabled by default for existing users, but
227        // we enable it for new users (T294398).
228        // This can only occur when the feature is available for everyone; when it's in beta,
229        // the new user won't have the beta enabled, so it'll never be available here.
230        if ( HookUtils::isFeatureAvailableToUser( $user, HookUtils::AUTOTOPICSUB ) ) {
231            $userOptionsManager->setOption( $user, 'discussiontools-' . HookUtils::AUTOTOPICSUB, 1 );
232        }
233    }
234
235}