Passed
Push — master ( 032fef...5c3390 )
by Mattia
03:11
created

UuidResolver::updateUserFailUpdate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 9
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\UsernameChangeEvent;
9
use App\Helpers\Storage\Files\SkinsStorage;
10
use App\Minecraft\MinecraftDefaults;
11
use App\Minecraft\MojangAccount;
12
use App\Minecraft\MojangClient;
13
use App\Models\Account;
14
use App\Repositories\AccountRepository;
15
use App\Repositories\AccountStatsRepository;
16
use Event;
17
use Log;
18
19
class UuidResolver
20
{
21
    /**
22
     * Requested string.
23
     *
24
     * @var string
25
     */
26
    private string $request = '';
27
    /**
28
     * @var string
29
     */
30
    private string $uuid = MinecraftDefaults::UUID;
31
    /**
32
     * Userdata from/to DB.
33
     *
34
     * @var Account
35
     */
36
    private ?Account $account;
37
    /**
38
     * Full Minecraft/Mojang Account Data.
39
     *
40
     * @var MojangAccount
41
     */
42
    private ?MojangAccount $mojangAccount;
43
    /**
44
     * User data has been updated?
45
     *
46
     * @var bool
47
     */
48
    private bool $dataUpdated = false;
49
    /**
50
     * Set force update.
51
     *
52
     * @var bool
53
     */
54
    private bool $forceUpdate = false;
55
    /**
56
     * @var AccountRepository
57
     */
58
    private AccountRepository $accountRepository;
59
    /**
60
     * @var AccountStatsRepository
61
     */
62
    private AccountStatsRepository $accountStatsRepository;
63
64
    /**
65
     * @var MojangClient
66
     */
67
    private MojangClient $mojangClient;
68
69
    /**
70
     * Core constructor.
71
     *
72
     * @param AccountRepository      $accountRepository      Where user data is stored
73
     * @param AccountStatsRepository $accountStatsRepository
74
     * @param MojangClient           $mojangClient           Client for Mojang API
75
     */
76
    public function __construct(
77
        AccountRepository $accountRepository,
78
        AccountStatsRepository $accountStatsRepository,
79
        MojangClient $mojangClient
80
    ) {
81
        $this->accountRepository = $accountRepository;
82
        $this->accountStatsRepository = $accountStatsRepository;
83
        $this->mojangClient = $mojangClient;
84
    }
85
86
    /**
87
     * @return string
88
     */
89
    public function getUuid(): string
90
    {
91
        return $this->uuid;
92
    }
93
94
    /**
95
     * Check if cache is still valid.
96
     *
97
     * @param int
98
     *
99
     * @return bool
100
     */
101
    private function checkDbCache(): bool
102
    {
103
        $accountUpdatedAtTimestamp = $this->account->updated_at->timestamp ?? 0;
0 ignored issues
show
Bug introduced by
The property timestamp does not exist on string.
Loading history...
104
105
        return (\time() - $accountUpdatedAtTimestamp) < env('USERDATA_CACHE_TIME');
106
    }
107
108
    /**
109
     * Return loaded user data.
110
     *
111
     * @return Account
112
     */
113
    public function getAccount(): Account
114
    {
115
        return $this->account ?? new Account();
116
    }
117
118
    /**
119
     * Check if an UUID is in the database.
120
     *
121
     * @return bool Returns true/false
122
     */
123
    private function requestedUuidInDb(): bool
124
    {
125
        $this->account = $this->accountRepository->findByUuid($this->request);
126
127
        if ($this->account === null) {
128
            return false;
129
        }
130
131
        $this->uuid = $this->account->uuid;
132
133
        return true;
134
    }
135
136
    /**
137
     * Insert user data in database.
138
     *
139
     * @param void
140
     *
141
     * @return bool
142
     */
143
    public function insertNewUuid(): bool
144
    {
145
        if (UserNotFoundCache::has($this->request)) {
146
            return false;
147
        }
148
149
        if ($this->getFullUserdataApi()) {
150
            $this->account = $this->accountRepository->create([
151
                'username' => $this->mojangAccount->getUsername(),
152
                'uuid' => $this->mojangAccount->getUuid(),
153
                'skin' => $this->mojangAccount->getSkin(),
154
                'cape' => $this->mojangAccount->getCape(),
155
            ]);
156
157
            $this->saveRemoteSkin();
158
            $this->accountStatsRepository->createEmptyStatsForUuid($this->account->uuid);
159
160
            $this->uuid = $this->mojangAccount->getUuid();
161
162
            return true;
163
        }
164
165
        UserNotFoundCache::add($this->request);
166
167
        return false;
168
    }
169
170
    /**
171
     * Check requested string and initialize objects.
172
     *
173
     * @param string
174
     *
175
     * @throws \Exception
176
     *
177
     * @return bool
178
     */
179
    public function resolve(string $string): bool
180
    {
181
        $this->dataUpdated = false;
182
        $this->request = $string;
183
184
        if ($this->initializeUuidRequest()) {
185
            return true;
186
        }
187
188
        $this->setFailedRequest('Account not found');
189
190
        return false;
191
    }
192
193
    /**
194
     * Update current user fail count.
195
     */
196
    private function updateUserFailUpdate(): bool
197
    {
198
        if (isset($this->account->uuid)) {
199
            ++$this->account->fail_count;
200
201
            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

201
            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...
202
        }
203
204
        return false;
205
    }
206
207
    /**
208
     * Update db user data.
209
     */
210
    private function updateDbUser(): bool
211
    {
212
        if (isset($this->account->username) && $this->account->uuid !== '') {
213
            // Get data from API
214
            if ($this->getFullUserdataApi()) {
215
                $previousUsername = $this->account->username;
216
                // Update database
217
                $this->accountRepository->update([
218
                    'username' => $this->mojangAccount->getUsername(),
219
                    'skin' => $this->mojangAccount->getSkin(),
220
                    'cape' => $this->mojangAccount->getCape(),
221
                    'fail_count' => 0,
222
                ], $this->account->id);
223
224
                $this->account->touch();
225
                $this->account->refresh();
226
227
                // Update skin
228
                $this->saveRemoteSkin();
229
                $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

229
                $this->logUsernameChange(/** @scrutinizer ignore-type */ $this->account, $previousUsername);
Loading history...
230
231
                $this->dataUpdated = true;
232
233
                return true;
234
            }
235
236
            $this->updateUserFailUpdate();
237
238
            if (!SkinsStorage::exists($this->account->uuid)) {
239
                SkinsStorage::copyAsSteve($this->account->uuid);
240
            }
241
        }
242
        $this->dataUpdated = false;
243
244
        return false;
245
    }
246
247
    /**
248
     * Return if data has been updated.
249
     */
250
    public function userDataUpdated(): bool
251
    {
252
        return $this->dataUpdated;
253
    }
254
255
    /**
256
     * Log the username change.
257
     *
258
     * @param $account Account User Account
259
     * @param $previousUsername string Previous username
260
     */
261
    private function logUsernameChange(Account $account, string $previousUsername): void
262
    {
263
        if ($account->username !== $previousUsername && $previousUsername !== '') {
264
            Event::dispatch(new UsernameChangeEvent($account->uuid, $previousUsername, $account->username));
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

264
            Event::/** @scrutinizer ignore-call */ 
265
                   dispatch(new UsernameChangeEvent($account->uuid, $previousUsername, $account->username));

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