Passed
Push — master ( 9aa048...898b5e )
by Mattia
04:05
created

UuidResolver::updateDbUser()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 33
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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

129
        if (UserNotFoundCache::has(/** @scrutinizer ignore-type */ $this->request)) {
Loading history...
130
            return false;
131
        }
132
133
        if ($this->getFullUserdataApi()) {
134
            $this->account = Account::create([
135
                'username' => $this->mojangAccount->getUsername(),
136
                'uuid' => $this->mojangAccount->getUuid(),
137
                'skin' => $this->mojangAccount->getSkin(),
138
                'cape' => $this->mojangAccount->getCape(),
139
            ]);
140
141
            $this->saveRemoteSkin();
142
143
            $this->uuid = $this->account->uuid;
144
            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

144
            Event::/** @scrutinizer ignore-call */ 
145
                   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...
145
146
            return true;
147
        }
148
149
        UserNotFoundCache::add($this->request);
150
151
        return false;
152
    }
153
154
    /**
155
     * Check requested string and initialize objects.
156
     *
157
     * @param string|null
158
     *
159
     * @throws \Exception
160
     *
161
     * @return bool
162
     */
163
    public function resolve(?string $uuid): bool
164
    {
165
        $this->dataUpdated = false;
166
        $this->request = $uuid;
167
168
        if ($uuid === null) {
169
            Log::debug('UUID is null');
170
171
            return false;
172
        }
173
174
        if ($this->initializeUuidRequest()) {
175
            return true;
176
        }
177
178
        $this->setFailedRequest('Account not found');
179
180
        return false;
181
    }
182
183
    /**
184
     * Update current user fail count.
185
     */
186
    private function updateUserFailUpdate(): bool
187
    {
188
        if (isset($this->account->uuid)) {
189
            ++$this->account->fail_count;
190
191
            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

191
            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...
192
        }
193
194
        return false;
195
    }
196
197
    /**
198
     * Update db user data.
199
     */
200
    private function updateDbUser(): bool
201
    {
202
        if (isset($this->account->username) && $this->account->uuid !== '') {
203
            // Get data from API
204
            if ($this->getFullUserdataApi()) {
205
                $previousUsername = $this->account->username;
206
                // Update database
207
                $this->account->username = $this->mojangAccount->getUsername();
208
                $this->account->skin = $this->mojangAccount->getSkin();
209
                $this->account->cape = $this->mojangAccount->getCape();
210
                $this->account->fail_count = 0;
211
                $this->account->save();
212
213
                $this->account->refresh();
214
215
                // Update skin
216
                $this->saveRemoteSkin();
217
                $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 Minepic\Resolvers\UuidRe...er::logUsernameChange() does only seem to accept Minepic\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

217
                $this->logUsernameChange(/** @scrutinizer ignore-type */ $this->account, $previousUsername);
Loading history...
218
219
                $this->dataUpdated = true;
220
221
                return true;
222
            }
223
224
            $this->updateUserFailUpdate();
225
226
            if (!SkinsStorage::exists($this->account->uuid)) {
227
                SkinsStorage::copyAsSteve($this->account->uuid);
228
            }
229
        }
230
        $this->dataUpdated = false;
231
232
        return false;
233
    }
234
235
    /**
236
     * Return if data has been updated.
237
     */
238
    public function userDataUpdated(): bool
239
    {
240
        return $this->dataUpdated;
241
    }
242
243
    /**
244
     * Log the username change.
245
     *
246
     * @param $account Account User Account
247
     * @param $previousUsername string Previous username
248
     */
249
    private function logUsernameChange(Account $account, string $previousUsername): void
250
    {
251
        if ($account->username !== $previousUsername && $previousUsername !== '') {
252
            Event::dispatch(new UsernameChangeEvent($account->uuid, $previousUsername, $account->username));
253
        }
254
    }
255
256
    /**
257
     * Get userdata from Mojang API.
258
     *
259
     * @param mixed
260
     *
261
     * @throws \Throwable
262
     *
263
     * @return bool
264
     */
265
    private function getFullUserdataApi(): bool
266
    {
267
        try {
268
            $this->mojangAccount = $this->mojangClient->getUuidInfo($this->request);
0 ignored issues
show
Bug introduced by
It seems like $this->request can also be of type null; however, parameter $uuid of Minepic\Minecraft\MojangClient::getUuidInfo() does only seem to accept string, 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

268
            $this->mojangAccount = $this->mojangClient->getUuidInfo(/** @scrutinizer ignore-type */ $this->request);
Loading history...
269
270
            return true;
271
        } catch (\Exception $e) {
272
            Log::error($e->getTraceAsString(), ['request' => $this->request]);
273
            $this->mojangAccount = null;
274
275
            return false;
276
        }
277
    }
278
279
    /**
280
     * Save skin image.
281
     *
282
     * @param mixed
283
     *
284
     * @throws \Throwable
285
     *
286
     * @return bool
287
     */
288
    public function saveRemoteSkin(): bool
289
    {
290
        if (!empty($this->account->skin) && $this->account->skin !== '') {
291
            try {
292
                $skinData = $this->mojangClient->getSkin($this->account->skin);
293
294
                return SkinsStorage::save($this->account->uuid, $skinData);
295
            } catch (\Exception $e) {
296
                Log::error($e->getTraceAsString());
297
            }
298
        }
299
300
        return SkinsStorage::copyAsSteve($this->account->uuid);
301
    }
302
303
    /**
304
     * Set force update.
305
     *
306
     * @param bool $forceUpdate
307
     */
308
    public function setForceUpdate(bool $forceUpdate): void
309
    {
310
        $this->forceUpdate = $forceUpdate;
311
    }
312
313
    /**
314
     * Can I exec force update?
315
     */
316
    private function forceUpdatePossible(): bool
317
    {
318
        return ($this->forceUpdate) &&
319
            ((\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...
320
    }
321
322
    /**
323
     * @return bool
324
     */
325
    private function initializeUuidRequest(): bool
326
    {
327
        if ($this->requestedUuidInDb()) {
328
            // Check if UUID is in my database
329
            // Data cache still valid?
330
            if (!$this->checkDbCache() || $this->forceUpdatePossible()) {
331
                Log::debug('Refreshing User DB Data');
332
                // Nope, updating data
333
                $this->updateDbUser();
334
            }
335
336
            if (!SkinsStorage::exists($this->account->uuid)) {
337
                $this->saveRemoteSkin();
338
            }
339
340
            return true;
341
        }
342
343
        if ($this->insertNewUuid()) {
344
            return true;
345
        }
346
347
        return false;
348
    }
349
350
    /**
351
     * Set failed request.
352
     *
353
     * @param string $errorMessage
354
     */
355
    private function setFailedRequest(string $errorMessage = ''): void
356
    {
357
        Log::notice($errorMessage, ['request' => $this->request]);
358
        $this->account = null;
359
        $this->request = '';
360
    }
361
}
362