Failed Conditions
Push — multiproject/prefs ( 965e5d )
by Simon
08:54
created

PreferenceManager   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 193
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 23
eloc 93
dl 0
loc 193
rs 10
c 2
b 0
f 1

8 Methods

Rating   Name   Duplication   Size   Complexity  
A setGlobalPreference() 0 28 5
A isGlobalPreference() 0 11 3
A getPreference() 0 11 3
A loadPreferences() 0 51 2
A __construct() 0 5 1
A setLocalPreference() 0 26 5
A deleteLocalPreference() 0 13 2
A getForCurrent() 0 10 2
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 *                                                                            *
5
 * All code in this file is released into the public domain by the ACC        *
6
 * Development Team. Please see team.json for a list of contributors.         *
7
 ******************************************************************************/
8
9
namespace Waca\Helpers;
10
11
use PDO;
12
use Waca\DataObjects\User;
13
use Waca\DataObjects\UserPreference;
14
use Waca\Exceptions\OptimisticLockFailedException;
15
use Waca\PdoDatabase;
16
use Waca\WebRequest;
17
18
class PreferenceManager
19
{
20
    const PREF_WELCOMETEMPLATE = 'welcomeTemplate';
21
    const PREF_SKIP_JS_ABORT = 'skipJsAbort';
22
    const PREF_EMAIL_SIGNATURE = 'emailSignature';
23
    const PREF_CREATION_MODE = 'creationMode';
24
    const PREF_SKIN = 'skin';
25
    const PREF_DEFAULT_DOMAIN = 'defaultDomain';
26
    const CREATION_MANUAL = 0;
27
    const CREATION_OAUTH = 1;
28
    const CREATION_BOT = 2;
29
    /** @var PdoDatabase */
30
    private $database;
31
    /** @var int */
32
    private $user;
33
    /** @var ?int */
34
    private $domain;
35
    /** @var PreferenceManager|null */
36
    private static $currentUser = null;
37
    private $cachedPreferences = null;
38
39
    public function __construct(PdoDatabase $database, int $user, ?int $domain)
40
    {
41
        $this->database = $database;
42
        $this->user = $user;
43
        $this->domain = $domain;
44
    }
45
46
    public static function getForCurrent(PdoDatabase $database): PreferenceManager
47
    {
48
        if (self::$currentUser === null) {
49
            $user = User::getCurrent($database)->getId();
50
            $domain = WebRequest::getSessionDomain();
51
52
            self::$currentUser = new self($database, $user, $domain);
53
        }
54
55
        return self::$currentUser;
56
    }
57
58
    public function setLocalPreference(string $preference, $value): void
59
    {
60
        if ($this->cachedPreferences === null) {
61
            $this->loadPreferences();
62
        }
63
64
        if ($this->cachedPreferences[$preference]['value'] == $value
65
            && $this->cachedPreferences[$preference]['global'] === false) {
66
            return;
67
        }
68
69
        $localPreference = UserPreference::getLocalPreference($this->database, $this->user, $preference, $this->domain);
0 ignored issues
show
Bug introduced by
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

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