Passed
Push — master ( f76f45...88719b )
by Mattia
04:38
created

UuidResolver::updateStats()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 2
nc 2
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Resolvers;
6
7
use App\Cache\UserNotFoundCache;
8
use App\Events\Account\AccountCreatedEvent;
9
use App\Events\Account\UsernameChangeEvent;
10
use App\Helpers\Storage\Files\SkinsStorage;
11
use App\Minecraft\MinecraftDefaults;
12
use App\Minecraft\MojangAccount;
13
use App\Minecraft\MojangClient;
14
use App\Models\Account;
15
use Event;
16
use Log;
17
18
class UuidResolver
19
{
20
    /**
21
     * Requested string.
22
     *
23
     * @var string
24
     */
25
    private string $request = '';
26
    /**
27
     * @var string
28
     */
29
    private string $uuid = MinecraftDefaults::UUID;
30
    /**
31
     * Userdata from/to DB.
32
     *
33
     * @var Account|null
34
     */
35
    private ?Account $account;
36
    /**
37
     * Full Minecraft/Mojang Account Data.
38
     *
39
     * @var MojangAccount|null
40
     */
41
    private ?MojangAccount $mojangAccount;
42
    /**
43
     * User data has been updated?
44
     *
45
     * @var bool
46
     */
47
    private bool $dataUpdated = false;
48
    /**
49
     * Set force update.
50
     *
51
     * @var bool
52
     */
53
    private bool $forceUpdate = false;
54
55
    /**
56
     * @var MojangClient
57
     */
58
    private MojangClient $mojangClient;
59
60
    /**
61
     * @param MojangClient $mojangClient Client for Mojang API
62
     */
63
    public function __construct(
64
        MojangClient $mojangClient
65
    ) {
66
        $this->mojangClient = $mojangClient;
67
    }
68
69
    /**
70
     * @return string
71
     */
72
    public function getUuid(): string
73
    {
74
        return $this->uuid;
75
    }
76
77
    /**
78
     * Check if cache is still valid.
79
     *
80
     * @param int
81
     *
82
     * @return bool
83
     */
84
    private function checkDbCache(): bool
85
    {
86
        $accountUpdatedAtTimestamp = $this->account->updated_at->timestamp ?? 0;
0 ignored issues
show
Bug introduced by
The property timestamp does not exist on string.
Loading history...
87
88
        return (\time() - $accountUpdatedAtTimestamp) < env('USERDATA_CACHE_TIME');
89
    }
90
91
    /**
92
     * Return loaded user data.
93
     *
94
     * @return Account
95
     */
96
    public function getAccount(): Account
97
    {
98
        return $this->account ?? new Account();
99
    }
100
101
    /**
102
     * Check if an UUID is in the database.
103
     *
104
     * @return bool Returns true/false
105
     */
106
    private function requestedUuidInDb(): bool
107
    {
108
        $this->account = Account::query()
109
            ->whereUuid($this->request)
110
            ->first();
111
112
        if ($this->account === null) {
113
            return false;
114
        }
115
116
        $this->uuid = $this->account->uuid;
117
118
        return true;
119
    }
120
121
    /**
122
     * Insert user data in database.
123
     **
124
     * @return bool
125
     */
126
    public function insertNewUuid(): bool
127
    {
128
        if (UserNotFoundCache::has($this->request)) {
129
            return false;
130
        }
131
132
        if ($this->getFullUserdataApi()) {
133
            $this->account = Account::create([
134
                'username' => $this->mojangAccount->getUsername(),
135
                'uuid' => $this->mojangAccount->getUuid(),
136
                'skin' => $this->mojangAccount->getSkin(),
137
                'cape' => $this->mojangAccount->getCape(),
138
            ]);
139
140
            $this->saveRemoteSkin();
141
142
            $this->uuid = $this->account->uuid;
143
            Event::dispatch(new AccountCreatedEvent($this->account));
0 ignored issues
show
Bug introduced by
The method dispatch() does not exist on Event. ( Ignorable by Annotation )

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

143
            Event::/** @scrutinizer ignore-call */ 
144
                   dispatch(new AccountCreatedEvent($this->account));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
144
145
            return true;
146
        }
147
148
        UserNotFoundCache::add($this->request);
149
150
        return false;
151
    }
152
153
    /**
154
     * Check requested string and initialize objects.
155
     *
156
     * @param string
157
     *
158
     * @throws \Exception
159
     *
160
     * @return bool
161
     */
162
    public function resolve(string $string): bool
163
    {
164
        $this->dataUpdated = false;
165
        $this->request = $string;
166
167
        if ($this->initializeUuidRequest()) {
168
            return true;
169
        }
170
171
        $this->setFailedRequest('Account not found');
172
173
        return false;
174
    }
175
176
    /**
177
     * Update current user fail count.
178
     */
179
    private function updateUserFailUpdate(): bool
180
    {
181
        if (isset($this->account->uuid)) {
182
            ++$this->account->fail_count;
183
184
            return $this->account->save();
0 ignored issues
show
Bug introduced by
The method save() does not exist on null. ( Ignorable by Annotation )

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

184
            return $this->account->/** @scrutinizer ignore-call */ save();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
185
        }
186
187
        return false;
188
    }
189
190
    /**
191
     * Update db user data.
192
     */
193
    private function updateDbUser(): bool
194
    {
195
        if (isset($this->account->username) && $this->account->uuid !== '') {
196
            // Get data from API
197
            if ($this->getFullUserdataApi()) {
198
                $previousUsername = $this->account->username;
199
                // Update database
200
                $this->account->username = $this->mojangAccount->getUsername();
201
                $this->account->skin = $this->mojangAccount->getSkin();
202
                $this->account->cape = $this->mojangAccount->getCape();
203
                $this->account->fail_count = 0;
204
                $this->account->save();
205
206
                $this->account->refresh();
207
208
                // Update skin
209
                $this->saveRemoteSkin();
210
                $this->logUsernameChange($this->account, $previousUsername);
0 ignored issues
show
Bug introduced by
It seems like $this->account can also be of type null; however, parameter $account of App\Resolvers\UuidResolver::logUsernameChange() does only seem to accept App\Models\Account, 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

210
                $this->logUsernameChange(/** @scrutinizer ignore-type */ $this->account, $previousUsername);
Loading history...
211
212
                $this->dataUpdated = true;
213
214
                return true;
215
            }
216
217
            $this->updateUserFailUpdate();
218
219
            if (!SkinsStorage::exists($this->account->uuid)) {
220
                SkinsStorage::copyAsSteve($this->account->uuid);
221
            }
222
        }
223
        $this->dataUpdated = false;
224
225
        return false;
226
    }
227
228
    /**
229
     * Return if data has been updated.
230
     */
231
    public function userDataUpdated(): bool
232
    {
233
        return $this->dataUpdated;
234
    }
235
236
    /**
237
     * Log the username change.
238
     *
239
     * @param $account Account User Account
240
     * @param $previousUsername string Previous username
241
     */
242
    private function logUsernameChange(Account $account, string $previousUsername): void
243
    {
244
        if ($account->username !== $previousUsername && $previousUsername !== '') {
245
            Event::dispatch(new UsernameChangeEvent($account->uuid, $previousUsername, $account->username));
246
        }
247
    }
248
249
    /**
250
     * Get userdata from Mojang API.
251
     *
252
     * @param mixed
253
     *
254
     * @throws \Throwable
255
     *
256
     * @return bool
257
     */
258
    private function getFullUserdataApi(): bool
259
    {
260
        try {
261
            $this->mojangAccount = $this->mojangClient->getUuidInfo($this->request);
262
263
            return true;
264
        } catch (\Exception $e) {
265
            Log::error($e->getTraceAsString(), ['request' => $this->request]);
266
            $this->mojangAccount = null;
267
268
            return false;
269
        }
270
    }
271
272
    /**
273
     * Save skin image.
274
     *
275
     * @param mixed
276
     *
277
     * @return bool
278
     */
279
    public function saveRemoteSkin(): bool
280
    {
281
        if (!empty($this->account->skin) && $this->account->skin !== '') {
282
            try {
283
                $skinData = $this->mojangClient->getSkin($this->account->skin);
284
285
                return SkinsStorage::save($this->account->uuid, $skinData);
286
            } catch (\Exception $e) {
287
                Log::error($e->getTraceAsString());
288
            }
289
        }
290
291
        return SkinsStorage::copyAsSteve($this->account->uuid);
292
    }
293
294
    /**
295
     * Set force update.
296
     *
297
     * @param bool $forceUpdate
298
     */
299
    public function setForceUpdate(bool $forceUpdate): void
300
    {
301
        $this->forceUpdate = $forceUpdate;
302
    }
303
304
    /**
305
     * Can I exec force update?
306
     */
307
    private function forceUpdatePossible(): bool
308
    {
309
        return ($this->forceUpdate) &&
310
            ((\time() - $this->account->updated_at->timestamp) > env('MIN_USERDATA_UPDATE_INTERVAL'));
0 ignored issues
show
Bug introduced by
The property timestamp does not exist on string.
Loading history...
311
    }
312
313
    /**
314
     * @return bool
315
     */
316
    private function initializeUuidRequest(): bool
317
    {
318
        if ($this->requestedUuidInDb()) {
319
            // Check if UUID is in my database
320
            // Data cache still valid?
321
            if (!$this->checkDbCache() || $this->forceUpdatePossible()) {
322
                Log::debug('Refreshing User DB Data');
323
                // Nope, updating data
324
                $this->updateDbUser();
325
            }
326
327
            if (!SkinsStorage::exists($this->account->uuid)) {
328
                $this->saveRemoteSkin();
329
            }
330
331
            return true;
332
        }
333
334
        if ($this->insertNewUuid()) {
335
            return true;
336
        }
337
338
        return false;
339
    }
340
341
    /**
342
     * Set failed request.
343
     *
344
     * @param string $errorMessage
345
     */
346
    private function setFailedRequest(string $errorMessage = ''): void
347
    {
348
        Log::notice($errorMessage, ['request' => $this->request]);
349
        $this->account = null;
350
        $this->request = '';
351
    }
352
}
353