Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
SettingsManager
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
4 / 4
5
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 saveUserOptions
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 logSettingsChange
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 saveOptionsInternal
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * Backend for setting (and eventually retrieving) user settings
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
20 *
21 * @file
22 */
23
24namespace MediaWiki\Extension\GlobalWatchlist;
25
26use FormatJson;
27use IBufferingStatsdDataFactory;
28use MediaWiki\User\Options\UserOptionsManager;
29use MediaWiki\User\UserIdentity;
30use Psr\Log\LoggerInterface;
31
32/**
33 * @author DannyS712
34 * @internal
35 */
36class SettingsManager {
37
38    /**
39     * Name for the user option in the database with the user's global watchlist settings
40     * @note This must be the same as the options name used in getSettings.js
41     */
42    public const PREFERENCE_NAME = 'global-watchlist-options';
43
44    /**
45     * Latest version of the format the settings are in, in case it changes
46     */
47    public const PREFERENCE_VERSION = 1;
48
49    /**
50     * Make the code clearer by using constants instead of 0, 1, or 2 to represent the filter
51     * statuses. Used for anon filter, bot filter, and minor filter
52     */
53
54    /** Don't care, not filtered */
55    public const FILTER_EITHER = 0;
56    /** Require that the condition (anon/bot/minor) be matched */
57    public const FILTER_REQUIRE = 1;
58    /** Exclude edits that match the condition */
59    public const FILTER_EXCLUDE = 2;
60
61    /** @var LoggerInterface */
62    private $logger;
63
64    /** @var UserOptionsManager */
65    private $userOptionsManager;
66
67    /** @var IBufferingStatsdDataFactory */
68    private $statsdDataFactory;
69
70    /**
71     * @param LoggerInterface $logger
72     * @param UserOptionsManager $userOptionsManager
73     * @param IBufferingStatsdDataFactory $statsdDataFactory
74     */
75    public function __construct(
76        LoggerInterface $logger,
77        UserOptionsManager $userOptionsManager,
78        IBufferingStatsdDataFactory $statsdDataFactory
79    ) {
80        $this->logger = $logger;
81        $this->userOptionsManager = $userOptionsManager;
82        $this->statsdDataFactory = $statsdDataFactory;
83    }
84
85    /**
86     * Save user settings
87     *
88     * @param UserIdentity $userIdentity
89     * @param array $options
90     */
91    public function saveUserOptions( UserIdentity $userIdentity, array $options ) {
92        $this->logSettingsChange( $userIdentity );
93
94        // When saving the options, make sure that `version` is at the start of the
95        // serialized JSON. In case we ever need to make a breaking change to the
96        // preferences and use a maintenance script to migrate existing settings,
97        // we will need to be able to search for users that have the old version
98        // number. Its a lot quicker to search for a substring at the start of
99        // the blob than in the middle or at the end.
100        $dbOptions = [ 'version' => self::PREFERENCE_VERSION ] + $options;
101
102        $this->saveOptionsInternal( $userIdentity, FormatJson::encode( $dbOptions ) );
103    }
104
105    /**
106     * Log the user saving their settings
107     *
108     * Differentiate between users changing existing settings and users saving settings
109     * for the first time
110     *
111     * @param UserIdentity $userIdentity
112     */
113    private function logSettingsChange( UserIdentity $userIdentity ) {
114        $currentOptions = $this->userOptionsManager->getOption(
115            $userIdentity,
116            self::PREFERENCE_NAME,
117            false
118        );
119
120        // $currentOptions is `false` if the user is saving their settings for the
121        // first time, or a string with the user's option otherwise
122        if ( $currentOptions === false ) {
123            $this->statsdDataFactory->increment( 'globalwatchlist.settings.new' );
124        } else {
125            $this->statsdDataFactory->increment( 'globalwatchlist.settings.change' );
126        }
127    }
128
129    /**
130     * Actually save user options in the database
131     *
132     * @param UserIdentity $userIdentity
133     * @param string $options
134     */
135    private function saveOptionsInternal( UserIdentity $userIdentity, string $options ) {
136        $this->logger->info(
137            "Saving options for {username}: {userOptions}",
138            [
139                'username' => $userIdentity->getName(),
140                'userOptions' => $options
141            ]
142        );
143
144        $this->userOptionsManager->setOption(
145            $userIdentity,
146            self::PREFERENCE_NAME,
147            $options
148        );
149
150        $this->userOptionsManager->saveOptions( $userIdentity );
151    }
152
153}