Completed
Pull Request — development (#767)
by Nick
04:18
created

GdprHandler::deleteUserOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace OcLegacy\Admin\Gdpr;
4
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\FetchMode;
7
use Exception;
8
9
class GdprHandler
10
{
11
    /**
12
     * @var Connection
13
     */
14
    private $connection;
15
    /**
16
     * @var string
17
     */
18
    private $projectDir;
19
20
    public function __construct(Connection $connection, string $projectDir)
21
    {
22
        $this->connection = $connection;
23
        $this->projectDir = $projectDir;
24
    }
25
26
    public function handle(\user $user, bool $execute = false): array
27
    {
28
        $userId = $user->getUserId();
29
30
        $caches = $this->getCaches($userId);
31
        $cacheLogs = $this->getCacheLogs($userId);
32
33
        $cachePictures = $this->fetchPictures($caches, 'cache_id', OBJECT_CACHE);
34
        $cacheLogPictures = $this->fetchPictures($cacheLogs, 'id', OBJECT_CACHELOG);
35
36
        $cachePicturesModified = $this->fetchPicturesModified($caches, 'cache_id', OBJECT_CACHE);
37
        $cacheLogPicturesModified = $this->fetchPicturesModified($cacheLogs, 'id', OBJECT_CACHELOG);
38
39
        $cacheCount = count($caches);
40
        $cacheLogCount = count($cacheLogs);
41
        $cachePicturesCount = count($cachePictures) + count($cachePicturesModified);
42
        $cacheLogPicturesCount = count($cacheLogPictures) + count($cacheLogPicturesModified);
43
44
        $executed = false;
45
46
        if ($execute) {
47
            try {
48
                $this->connection->beginTransaction();
49
50
                $this->processUser($userId);
51
                $this->processCaches($userId, $caches);
52
                $this->deletePictures($cachePictures);
53
                $this->deletePictures($cacheLogPictures);
54
55
                $this->deletePicturesModified($cachePicturesModified);
56
                $this->deletePicturesModified($cacheLogPicturesModified);
57
58
                $this->deleteCacheLogSubTables($userId);
59
                $this->deleteCacheIgnore($userId);
60
                $this->deleteFieldNotes($userId);
61
                $this->deleteLogEntries($userId);
62
                $this->deleteLogins($userId);
63
                $this->deleteOkapiAuthorizations($userId);
64
                $this->deleteWatches($userId);
65
                $this->deleteWsSessions($userId);
66
67
                $this->connection->commit();
68
69
                $executed = true;
70
            } catch (Exception $e) {
71
                $this->connection->rollBack();
72
73
                return [
74
                    'userId' => $userId,
75
                    'username' => $user->getUsername(),
76
                    'email' => $user->getEMail(),
77
                    'error' => $e->getMessage()
78
                ];
79
            }
80
        }
81
82
        return [
83
            'userId' => $userId,
84
            'username' => $user->getUsername(),
85
            'email' => $user->getEMail(),
86
            'cacheCount' => $cacheCount,
87
            'cacheLogCount' => $cacheLogCount,
88
            'cachePicturesCount' => $cachePicturesCount,
89
            'cacheLogPicturesCount' => $cacheLogPicturesCount,
90
            'executed' => $executed
91
        ];
92
    }
93
94
    private function processUser(int $userId): void
95
    {
96
        $this->anonymizeUser($userId);
97
        $this->deleteUserOptions($userId);
98
        $this->deleteUserCacheLists($userId);
99
        $this->deleteEmailUser($userId);
100
        $this->deleteQueries($userId);
101
    }
102
103
    private function processCaches(int $userId, array $caches): void
104
    {
105
        $this->anonymizeCaches($caches);
106
        $this->anonymizeCacheLogs($userId);
107
        $this->deleteCacheAdoptions($userId);
108
    }
109
110
    private function anonymizeUser(int $userId): void
111
    {
112
        $newUsername = 'delete_' . $userId;
113
114
        $this->connection->executeQuery(<<<SQL
115
            UPDATE user SET
116
                username = :username,
117
                last_login = NULL,
118
                password = NULL,
119
                roles = '',
120
                email = NULL,
121
                is_active_flag = 0,
122
                latitude = 0,
123
                longitude = 0,
124
                accept_mailing = 0,
125
                usermail_send_addr = 0,
126
                last_name = '',
127
                first_name = '',
128
                watchmail_mode = 0,
129
                watchmail_hour = 0,
130
                watchmail_nextmail = 0,
131
                watchmail_day = 0,
132
                statpic_logo = 0,
133
                statpic_text = '',
134
                notify_radius = 0,
135
                notify_oconly = 0,
136
                gdpr_deletion = 1,
137
                description = '',
138
                date_created = '1970-01-01 00:00:00',
139
                last_modified = '1970-01-01 00:00:00',
140
                last_email_problem = NULL,
141
                new_pw_code = NULL,
142
                new_pw_date = NULL,
143
                permanent_login_flag = 0,
144
                admin = 0,
145
                domain = NULL
146
            WHERE user_id = :userId
147
SQL
148
        , [
149
            'userId' => $userId,
150
            'username' => $newUsername,
151
        ]);
152
    }
153
154
    private function deleteCacheAdoptions(int $userId): void
155
    {
156
        $this->connection->executeQuery('DELETE FROM cache_adoptions WHERE from_user_id = :userId OR to_user_id = :userId', [
157
            'userId' => $userId,
158
        ]);
159
    }
160
161
    private function deleteUserOptions(int $userId): void
162
    {
163
        $this->connection->executeQuery('DELETE FROM user_options WHERE user_id = :userId', [
164
            'userId' => $userId,
165
        ]);
166
    }
167
168 View Code Duplication
    private function deleteUserCacheLists(int $userId): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
169
    {
170
        $this->connection->executeQuery('DELETE FROM cache_lists WHERE user_id = :userId', [
171
            'userId' => $userId,
172
        ]);
173
174
        $this->connection->executeQuery('DELETE FROM cache_list_watches WHERE user_id = :userId', [
175
            'userId' => $userId,
176
        ]);
177
178
        $this->connection->executeQuery('DELETE FROM cache_list_bookmarks WHERE user_id = :userId', [
179
            'userId' => $userId,
180
        ]);
181
    }
182
183
    private function anonymizeCacheLogs(int $userId): void
184
    {
185
        $this->connection->executeQuery('UPDATE cache_logs SET text=\'-User gelöscht-\', gdpr_deletion = 1 WHERE user_id = :userId', [
186
            'userId' => $userId,
187
        ]);
188
    }
189
190
    private function deleteCacheLogSubTables(int $userId): void
191
    {
192
        $this->connection->executeQuery('DELETE FROM cache_logs_archived WHERE user_id = :userId', [
193
            'userId' => $userId,
194
        ]);
195
196
        $this->connection->executeQuery('DELETE FROM cache_logs_modified WHERE user_id = :userId', [
197
            'userId' => $userId,
198
        ]);
199
    }
200
201
    private function getCaches(int $userId): array
202
    {
203
        return $this->connection->fetchAll('SELECT * FROM caches WHERE user_id = :userId', [
204
            'userId' => $userId,
205
        ]);
206
    }
207
208
    private function getCacheLogs(int $userId): array
209
    {
210
        return $this->connection->fetchAll('SELECT * FROM cache_logs WHERE user_id = :userId', [
211
            'userId' => $userId,
212
        ]);
213
    }
214
215
    private function anonymizeCaches(array $caches): void
216
    {
217
        foreach ($caches as $cache) {
218
            $this->connection->executeQuery(<<<'SQL'
219
                UPDATE caches SET
220
                    name='-Cache nach DSGVO gelöscht-',
221
                    show_cachelists = 0,
222
                    status = 6,
223
                    wp_gc = '',
224
                    wp_gc_maintained ='',
225
                    gdpr_deletion = 1
226
                WHERE cache_id = :cacheId;
227
SQL
228
            , [
229
                'cacheId' => (int)$cache['cache_id'],
230
            ]);
231
232
            $this->connection->executeQuery(<<<'SQL'
233
                UPDATE cache_desc SET
234
                    `desc` = '### DSGVO Löschung ###<br>Dieses Listing wurde aufgrund der Anforderungen des Urhebers, seine Daten im Rahmen des Datenschutzes zu löschen - neutralisiert.<br>Leider können wir keine Details aus dem Listing beibehalten. Die nicht personenbezogenen Parameter des Caches wie Lage, Wertung und die Logs Dritter können zum Erhalt des Spieles aber beibehalten.<br><br>###<br>Eurer OC Team - im Auftrag des Datenschutzbeauftragen',
235
                    desc_html = 1,
236
                    hint = '',
237
                    short_desc = ''
238
                WHERE cache_id = :cacheId
239
SQL
240
            , [
241
                'cacheId' => (int)$cache['cache_id'],
242
            ]);
243
244
            $this->connection->executeQuery('DELETE FROM cache_desc_modified WHERE cache_id = :cacheId', [
245
                'cacheId' => (int)$cache['cache_id'],
246
            ]);
247
248
            $this->connection->executeQuery('DELETE FROM cache_ignore WHERE cache_id = :cacheId', [
249
                'cacheId' => (int)$cache['cache_id'],
250
            ]);
251
252
            $this->connection->executeQuery('DELETE FROM caches_modified WHERE cache_id = :cacheId', [
253
                'cacheId' => (int)$cache['cache_id'],
254
            ]);
255
        }
256
    }
257
258
    public function fetchPictures(array $data, string $idField, int $objectType): array
259
    {
260
        if ($data === []) {
261
            return [];
262
        }
263
264
        $ids = array_map(static function (array $cache) use ($idField) {
265
            return (int) $cache[$idField];
266
        }, $data);
267
268
        $pictures = $this->connection->fetchAll('SELECT * FROM pictures WHERE object_id IN (' . implode(',', $ids) . ') AND object_type = :objectType', [
269
            'objectType' => $objectType,
270
        ]);
271
272
        $modifiedPictures = $this->connection->fetchAll('SELECT * FROM pictures_modified WHERE object_id IN (' . implode(',', $ids) . ') AND object_type = :objectType', [
273
            'objectType' => $objectType,
274
        ]);
275
276
        return array_merge($pictures, $modifiedPictures);
277
    }
278
279
280
    public function fetchPicturesModified(array $data, string $idField, int $objectType): array
281
    {
282
        if ($data === []) {
283
            return [];
284
        }
285
286
        $ids = array_map(static function (array $cache) use ($idField) {
287
            return (int) $cache[$idField];
288
        }, $data);
289
290
        return $this->connection->fetchAll('SELECT * FROM pictures_modified WHERE object_id IN (' . implode(',', $ids) . ') AND object_type = :objectType', [
291
            'objectType' => $objectType,
292
        ]);
293
    }
294
295
    private function deletePictures(array $pictures): void
296
    {
297
        foreach ($pictures as $picture) {
298
            $imagePath = parse_url($picture['url'], PHP_URL_PATH);
299
            @unlink($this->projectDir . $imagePath);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
300
301
            if(isset($picture['thumb_url'])) {
302
                $thumbPath = parse_url($picture['thumb_url'], PHP_URL_PATH);
303
                @unlink($this->projectDir . $thumbPath);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
304
            }
305
306
            $this->connection->executeQuery('DELETE FROM pictures WHERE object_id = :objectId AND object_type = :objectType', [
307
                'objectId' => $picture['object_id'],
308
                'objectType' => $picture['object_type'],
309
            ]);
310
        }
311
    }
312
313
    private function deletePicturesModified(array $pictures): void
314
    {
315
        foreach ($pictures as $picture) {
316
            $imagePath = parse_url($picture['url'], PHP_URL_PATH);
317
318
            @unlink($this->projectDir . $imagePath);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
319
320
            $this->connection->executeQuery('DELETE FROM pictures_modified WHERE object_id = :objectId AND object_type = :objectType', [
321
                'objectId' => $picture['object_id'],
322
                'objectType' => $picture['object_type'],
323
            ]);
324
        }
325
    }
326
327
    private function deleteCacheIgnore(int $userId): void
328
    {
329
        $this->connection->executeQuery('DELETE FROM cache_ignore WHERE user_id = :userId', [
330
            'userId' => $userId,
331
        ]);
332
    }
333
334
    private function deleteEmailUser(int $userId): void
335
    {
336
        $this->connection->executeQuery('DELETE FROM email_user WHERE from_user_id = :userId OR to_user_id = :userId', [
337
            'userId' => $userId,
338
        ]);
339
    }
340
341
    private function deleteFieldNotes(int $userId): void
342
    {
343
        $this->connection->executeQuery('DELETE FROM field_note WHERE user_id = :userId', [
344
            'userId' => $userId,
345
        ]);
346
    }
347
348
    private function deleteLogEntries(int $userId): void
349
    {
350
        $this->connection->executeQuery('DELETE FROM logentries WHERE userid = :userId', [
351
            'userId' => $userId,
352
        ]);
353
    }
354
355
    private function deleteLogins(int $userId): void
356
    {
357
        $this->connection->executeQuery('DELETE FROM logins WHERE user_id = :userId', [
358
            'userId' => $userId,
359
        ]);
360
    }
361
362
    private function deleteOkapiAuthorizations(int $userId): void
363
    {
364
        $this->connection->executeQuery(<<<'SQL'
365
            DELETE oa, oc
366
            FROM okapi_authorizations oa
367
            INNER JOIN okapi_consumers oc ON oa.consumer_key = oc.`key`
368
            INNER JOIN okapi_nonces ono ON oa.consumer_key = ono.consumer_key
369
            INNER JOIN okapi_diagnostics od ON oa.consumer_key = od.consumer_key
370
            INNER JOIN okapi_cache oca ON oa.consumer_key = oca.`key`
371
            INNER JOIN okapi_stats_hourly osh ON oa.consumer_key = osh.consumer_key
372
            INNER JOIN okapi_stats_monthly osm ON oa.consumer_key = osm.consumer_key
373
            INNER JOIN okapi_stats_temp ost ON oa.consumer_key = ost.consumer_key
374
            INNER JOIN okapi_submitted_objects oso ON oa.consumer_key = oso.consumer_key
375
            INNER JOIN okapi_tokens ot ON oa.consumer_key = ot.consumer_key
376
            WHERE oa.user_id = :userId
377
SQL
378
, [
379
            'userId' => $userId,
380
        ]);
381
    }
382
383
    private function deleteQueries(int $userId): void
384
    {
385
        $this->connection->executeQuery('DELETE FROM queries WHERE user_id = :userId', [
386
            'userId' => $userId,
387
        ]);
388
    }
389
390 View Code Duplication
    private function deleteWatches(int $userId): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
391
    {
392
        $this->connection->executeQuery('DELETE FROM watches_logqueue WHERE user_id = :userId', [
393
            'userId' => $userId,
394
        ]);
395
396
        $this->connection->executeQuery('DELETE FROM watches_notified WHERE user_id = :userId', [
397
            'userId' => $userId,
398
        ]);
399
400
        $this->connection->executeQuery('DELETE FROM watches_waiting WHERE user_id = :userId', [
401
            'userId' => $userId,
402
        ]);
403
    }
404
405
    private function deleteWsSessions(int $userId): void
406
    {
407
        $this->connection->executeQuery('DELETE FROM ws_sessions WHERE user_id = :userId', [
408
            'userId' => $userId,
409
        ]);
410
    }
411
}
412