Issues (186)

includes/Helpers/PreferenceManager.php (1 issue)

Labels
Severity
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 * ACC Development Team. Please see team.json for a list of contributors.     *
5
 *                                                                            *
6
 * This is free and unencumbered software released into the public domain.    *
7
 * Please see LICENSE.md for the full licencing statement.                    *
8
 ******************************************************************************/
9
10
namespace Waca\Helpers;
11
12
use PDO;
13
use Waca\DataObjects\User;
14
use Waca\DataObjects\UserPreference;
15
use Waca\Exceptions\OptimisticLockFailedException;
16
use Waca\PdoDatabase;
17
use Waca\WebRequest;
18
19
class PreferenceManager
20
{
21
    const PREF_WELCOMETEMPLATE = 'welcomeTemplate';
22
    const PREF_SKIP_JS_ABORT = 'skipJsAbort';
23
    const PREF_EMAIL_SIGNATURE = 'emailSignature';
24
    const PREF_CREATION_MODE = 'creationMode';
25
    const PREF_SKIN = 'skin';
26
    const PREF_DEFAULT_DOMAIN = 'defaultDomain';
27
    const PREF_QUEUE_HELP = 'showQueueHelp';
28
29
    const ADMIN_PREF_PREVENT_REACTIVATION = 'preventReactivation';
30
31
    const CREATION_MANUAL = 0;
32
    const CREATION_OAUTH = 1;
33
    const CREATION_BOT = 2;
34
    /** @var PdoDatabase */
35
    private $database;
36
    /** @var int */
37
    private $user;
38
    /** @var ?int */
39
    private $domain;
40
    /** @var PreferenceManager|null */
41
    private static $currentUser = null;
42
    private $cachedPreferences = null;
43
44
    public function __construct(PdoDatabase $database, int $user, ?int $domain)
45
    {
46
        $this->database = $database;
47
        $this->user = $user;
48
        $this->domain = $domain;
49
    }
50
51
    public static function getForCurrent(PdoDatabase $database): PreferenceManager
52
    {
53
        if (self::$currentUser === null) {
54
            $user = User::getCurrent($database)->getId();
55
            $domain = WebRequest::getSessionDomain();
56
57
            self::$currentUser = new self($database, $user, $domain);
58
        }
59
60
        return self::$currentUser;
61
    }
62
63
    public function setLocalPreference(string $preference, $value): void
64
    {
65
        if ($this->cachedPreferences === null) {
66
            $this->loadPreferences();
67
        }
68
69
        if ($this->cachedPreferences[$preference]['value'] == $value
70
            && $this->cachedPreferences[$preference]['global'] === false) {
71
            return;
72
        }
73
74
        $localPreference = UserPreference::getLocalPreference($this->database, $this->user, $preference, $this->domain);
0 ignored issues
show
It seems like $this->domain can also be of type null; however, parameter $domain of Waca\DataObjects\UserPre...e::getLocalPreference() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

74
        $localPreference = UserPreference::getLocalPreference($this->database, $this->user, $preference, /** @scrutinizer ignore-type */ $this->domain);
Loading history...
75
        if ($localPreference === false) {
76
            $localPreference = new UserPreference();
77
            $localPreference->setDatabase($this->database);
78
            $localPreference->setDomain($this->domain);
79
            $localPreference->setUser($this->user);
80
            $localPreference->setPreference($preference);
81
        }
82
83
        $localPreference->setValue($value);
84
        $localPreference->save();
85
86
        $this->cachedPreferences[$preference] = [
87
            'value'  => $value,
88
            'global' => false,
89
        ];
90
    }
91
92
    public function setGlobalPreference(string $preference, $value): void
93
    {
94
        if ($this->cachedPreferences === null) {
95
            $this->loadPreferences();
96
        }
97
98
        if ($this->cachedPreferences[$preference]['value'] == $value
99
            && $this->cachedPreferences[$preference]['global'] === true) {
100
            return;
101
        }
102
103
        $this->deleteLocalPreference($preference);
104
105
        $globalPreference = UserPreference::getGlobalPreference($this->database, $this->user, $preference);
106
        if ($globalPreference === false) {
107
            $globalPreference = new UserPreference();
108
            $globalPreference->setDatabase($this->database);
109
            $globalPreference->setDomain(null);
110
            $globalPreference->setUser($this->user);
111
            $globalPreference->setPreference($preference);
112
        }
113
114
        $globalPreference->setValue($value);
115
        $globalPreference->save();
116
117
        $this->cachedPreferences[$preference] = [
118
            'value'  => $value,
119
            'global' => true,
120
        ];
121
    }
122
123
    public function getPreference(string $preference)
124
    {
125
        if ($this->cachedPreferences === null) {
126
            $this->loadPreferences();
127
        }
128
129
        if (!isset($this->cachedPreferences[$preference])) {
130
            return null;
131
        }
132
133
        return $this->cachedPreferences[$preference]['value'];
134
    }
135
136
    public function isGlobalPreference(string $preference) : ?bool
137
    {
138
        if ($this->cachedPreferences === null) {
139
            $this->loadPreferences();
140
        }
141
142
        if (!isset($this->cachedPreferences[$preference])) {
143
            return null;
144
        }
145
146
        return $this->cachedPreferences[$preference]['global'];
147
    }
148
149
    protected function deleteLocalPreference(string $preference): void
150
    {
151
        $getStatement = $this->database->prepare('SELECT * FROM userpreference WHERE preference = :preference AND USER = :user AND domain = :domain');
152
        $getStatement->execute([
153
            ':user'       => $this->user,
154
            ':preference' => $preference,
155
            ':domain'     => $this->domain
156
        ]);
157
158
        $localPreference = $getStatement->fetchObject(UserPreference::class);
159
        if ($localPreference !== false) {
160
            $localPreference->setDatabase($this->database);
161
            $localPreference->delete();
162
        }
163
    }
164
165
    protected function loadPreferences(): void
166
    {
167
        /**
168
         * OK, this is a bit of a complicated query.
169
         * It's designed to get all the preferences defined for a user in a specified domain, falling back to globally
170
         * defined preferences if a local preference isn't set. As such, this query is the *heart* of how global
171
         * preferences work.
172
         *
173
         * Starting with the WHERE, we filter rows:
174
         *   a) where the row's domain is the domain we're looking for
175
         *   b) where the row's domain is null, thus it's a global setting
176
         *   c) if we don't have a domain we're looking for, fall back to global only
177
         *
178
         * The MAX(...) OVER(...) is a window function, *not* an aggregate. It basically takes the max of all selected
179
         * rows' domain columns, grouped by the preference column. Since any number N < null, this highlights all the
180
         * correct settings (local has precedence over global) such that prefpart == domain.
181
         *
182
         * -1 is used to represent null in the COALESCE() calls, since domain.id is an unsigned int hence -1 is an
183
         * impossible value
184
         */
185
        $sql = /** @lang SQL */
186
            <<<'EOF'
187
WITH allprefs AS (
188
    SELECT up.domain, up.preference, MAX(up.domain) OVER (PARTITION BY up.preference) AS prefpart, up.value, CASE WHEN up.domain IS NULL THEN 1 END AS isglobal
189
    FROM userpreference up
190
    WHERE COALESCE(up.domain, :domainc, -1) = COALESCE(:domain, -1)
191
      AND up.user = :user
192
)
193
SELECT p.preference, p.value, coalesce(p.isglobal, 0) as isglobal
194
FROM allprefs p
195
WHERE COALESCE(p.prefpart, -1) = COALESCE(p.domain, -1);
196
EOF;
197
        $statement = $this->database->prepare($sql);
198
199
        $statement->execute([
200
            ':domain'  => $this->domain,
201
            ':domainc' => $this->domain,
202
            ':user'    => $this->user,
203
        ]);
204
205
        $rawPrefs = $statement->fetchAll(PDO::FETCH_ASSOC);
206
        $prefs = [];
207
208
        foreach ($rawPrefs as $p) {
209
            $prefs[$p['preference']] = [
210
                'value'  => $p['value'],
211
                'global' => $p['isglobal'] == 1,
212
            ];
213
        }
214
215
        $this->cachedPreferences = $prefs;
216
    }
217
}
218