Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 93
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
PreferenceHooks
0.00% covered (danger)
0.00%
0 / 93
0.00% covered (danger)
0.00%
0 / 3
420
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 / 85
0.00% covered (danger)
0.00%
0 / 1
272
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\Config\Config;
13use MediaWiki\Config\ConfigFactory;
14use MediaWiki\Html\Html;
15use MediaWiki\Linker\LinkRenderer;
16use MediaWiki\Preferences\Hook\GetPreferencesHook;
17use MediaWiki\SpecialPage\SpecialPage;
18use MediaWiki\User\User;
19
20class PreferenceHooks implements
21    GetPreferencesHook
22{
23
24    private Config $config;
25    private LinkRenderer $linkRenderer;
26
27    public function __construct(
28        ConfigFactory $configFactory,
29        LinkRenderer $linkRenderer
30    ) {
31        $this->config = $configFactory->makeConfig( 'discussiontools' );
32        $this->linkRenderer = $linkRenderer;
33    }
34
35    /**
36     * Rename a key in an array while preserving the order of associative array keys.
37     *
38     * @param array $array
39     * @param string $from
40     * @param string $to
41     * @return array Modified array
42     */
43    private static function arrayRenameKey( array $array, string $from, string $to ): array {
44        $out = [];
45        foreach ( $array as $key => $value ) {
46            if ( $key === $from ) {
47                $key = $to;
48            }
49            $out[$key] = $value;
50        }
51        return $out;
52    }
53
54    /**
55     * Handler for the GetPreferences hook, to add and hide user preferences as configured
56     *
57     * @param User $user
58     * @param array &$preferences
59     */
60    public function onGetPreferences( $user, &$preferences ) {
61        if ( HookUtils::isFeatureAvailableToUser( $user ) ) {
62            $preferences['discussiontools-summary'] = [
63                'type' => 'info',
64                'default' => wfMessage( 'discussiontools-preference-summary' )->parse(),
65                'raw' => true,
66                'section' => 'editing/discussion',
67            ];
68        }
69        foreach ( HookUtils::FEATURES as $feature ) {
70            if (
71                $feature === HookUtils::VISUALENHANCEMENTS_REPLY ||
72                $feature === HookUtils::VISUALENHANCEMENTS_PAGEFRAME
73            ) {
74                // Feature is never user-configurable
75                continue;
76            }
77            if ( HookUtils::isFeatureAvailableToUser( $user, $feature ) ) {
78                $preferences["discussiontools-$feature"] = [
79                    'type' => 'toggle',
80                    // The following messages are used here:
81                    // * discussiontools-preference-autotopicsub
82                    // * discussiontools-preference-newtopictool
83                    // * discussiontools-preference-replytool
84                    // * discussiontools-preference-sourcemodetoolbar
85                    // * discussiontools-preference-topicsubscription
86                    // * discussiontools-preference-visualenhancements
87                    'label-message' => "discussiontools-preference-$feature",
88                    // The following messages are used here:
89                    // * discussiontools-preference-autotopicsub-help
90                    // * discussiontools-preference-newtopictool-help
91                    // * discussiontools-preference-replytool-help
92                    // * discussiontools-preference-sourcemodetoolbar-help
93                    // * discussiontools-preference-topicsubscription-help
94                    // * discussiontools-preference-visualenhancements-help
95                    'help-message' => "discussiontools-preference-$feature-help",
96                    'section' => 'editing/discussion',
97                ];
98
99                // Option to enable/disable new topic tool on pages that haven't been created
100                // (it's inside this loop to place the options in a nice order)
101                if ( $feature === HookUtils::NEWTOPICTOOL ) {
102                    $preferences["discussiontools-newtopictool-createpage"] = [
103                        'type' => 'radio',
104                        'cssclass' => 'mw-htmlform-checkradio-indent',
105                        'label-message' => 'discussiontools-preference-newtopictool-createpage',
106                        'options-messages' => [
107                            'discussiontools-preference-newtopictool-createpage-newtopictool' => 1,
108                            'discussiontools-preference-newtopictool-createpage-editor' => 0,
109                        ],
110                        'disable-if' => [ '===', 'discussiontools-' . HookUtils::NEWTOPICTOOL, '' ],
111                        'section' => 'editing/discussion',
112                    ];
113                }
114
115                // Make this option unavailable when a conflicting Convenient Discussions gadget exists
116                // (we can't use 'disable-if' or 'hide-if', because they don't let us change the labels).
117                if ( HookUtils::featureConflictsWithGadget( $user, $feature ) ) {
118                    $preferences["discussiontools-$feature"]['disabled'] = true;
119                    $preferences["discussiontools-$feature"]['help-message'] =
120                        [ 'discussiontools-preference-gadget-conflict', 'Special:Preferences#mw-prefsection-gadgets' ];
121                }
122            }
123        }
124
125        foreach ( HookUtils::FEATURES_DEPENDENCIES as $feature => $dependencies ) {
126            if ( !isset( $preferences["discussiontools-$feature"] ) ) {
127                continue;
128            }
129            $fields = array_filter( $dependencies,
130                static fn ( $dep ) => isset( $preferences["discussiontools-$dep"] ) &&
131                    // GlobalPreferences would remove disabled fields, avoid referencing them
132                    !( $preferences["discussiontools-$dep"]['disabled'] ?? false )
133            );
134            // Disable the option when it would have no effect: its dependencies
135            // are all disabled by the user or by the conflicting Gadget
136            if ( count( $fields ) ) {
137                $preferences["discussiontools-$feature"]['disable-if'] = [ 'AND' ];
138                foreach ( $fields as $field ) {
139                    $preferences["discussiontools-$feature"]['disable-if'][] = [
140                        '===', "discussiontools-$field", ''
141                    ];
142                }
143            } else {
144                $preferences["discussiontools-$feature"]['disabled'] = true;
145            }
146        }
147
148        $preferences['discussiontools-showadvanced'] = [
149            'type' => 'api',
150        ];
151        $preferences['discussiontools-seenautotopicsubpopup'] = [
152            'type' => 'api',
153        ];
154
155        if ( !$this->config->get( 'DiscussionToolsBeta' ) ) {
156            // When out of beta, preserve the user preference in case we
157            // bring back the beta feature for a new sub-feature. (T272071)
158            $preferences['discussiontools-betaenable'] = [
159                'type' => 'api'
160            ];
161        }
162
163        $preferences['discussiontools-editmode'] = [
164            'type' => 'api',
165            'validation-callback' => static function ( $value ) {
166                return in_array( $value, [ '', 'source', 'visual' ], true );
167            },
168        ];
169
170        // Add a link to Special:TopicSubscriptions to the Echo preferences matrix
171        $categoryMessage = wfMessage( 'echo-category-title-dt-subscription' )->numParams( 1 )->escaped();
172        $categoryMessageExtra = $categoryMessage .
173            Html::element( 'br' ) .
174            wfMessage( 'parentheses' )->rawParams(
175                $this->linkRenderer->makeLink(
176                    SpecialPage::getTitleFor( 'TopicSubscriptions' ),
177                    wfMessage( 'discussiontools-topicsubscription-preferences-editsubscriptions' )->text()
178                )
179            )->escaped();
180        if ( isset( $preferences['echo-subscriptions']['rows'] ) ) {
181            $preferences['echo-subscriptions']['rows'] = static::arrayRenameKey(
182                $preferences['echo-subscriptions']['rows'],
183                $categoryMessage,
184                $categoryMessageExtra
185            );
186        }
187        if ( isset( $preferences['echo-subscriptions']['tooltips'] ) ) {
188            $preferences['echo-subscriptions']['tooltips'] = static::arrayRenameKey(
189                // Phan insists that this key doesn't exist, even though we just checked with isset()
190                // @phan-suppress-next-line PhanTypeInvalidDimOffset, PhanTypeMismatchArgument
191                $preferences['echo-subscriptions']['tooltips'],
192                $categoryMessage,
193                $categoryMessageExtra
194            );
195        }
196    }
197
198}