Passed
Push — master ( 43d67d...caa518 )
by Nicolaas
14:30
created

CleanUp   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 425
Duplicated Lines 0 %

Importance

Changes 19
Bugs 0 Features 0
Metric Value
wmc 53
eloc 204
c 19
b 0
f 0
dl 0
loc 425
rs 6.96

6 Methods

Rating   Name   Duplication   Size   Complexity  
A createTable() 0 77 4
A setAnonymiser() 0 5 1
A setDatabase() 0 5 1
F runInner() 0 135 34
C createForm() 0 61 9
A run() 0 32 4

How to fix   Complexity   

Complex Class

Complex classes like CleanUp often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CleanUp, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Sunnysideup\DatabaseShareCleanUp;
4
5
use SilverStripe\Control\Director;
6
use SilverStripe\Control\HTTPRequest;
7
use SilverStripe\Dev\BuildTask;
8
use Sunnysideup\DatabaseShareCleanUp\Api\Anonymiser;
9
use Sunnysideup\DatabaseShareCleanUp\Api\DatabaseActions;
10
use Sunnysideup\Flush\FlushNow;
11
12
class CleanUp extends BuildTask
13
{
14
    /**
15
     * @var bool if set to FALSE, keep it from showing in the list
16
     *           and from being executable through URL or CLI
17
     */
18
    protected $enabled = true;
19
20
    /**
21
     * @var string Shown in the overview on the {@link TaskRunner}
22
     *             HTML or CLI interface. Should be short and concise, no HTML allowed.
23
     */
24
    protected $title = 'Cleanup and anonymise database - CAREFUL! Data will be deleted.';
25
26
    /**
27
     * @var string Describe the implications the task has,
28
     *             and the changes it makes. Accepts HTML formatting.
29
     */
30
    protected $description = 'Goes through database and deletes data that may expose personal information and bloat database.';
31
32
    protected $forReal = false;
33
34
    protected $anonymise = false;
35
36
    protected $removeObsolete = false;
37
38
    protected $removeOldVersions = false;
39
40
    protected $debug = false;
41
42
    protected $emptyFields = false;
43
44
    protected $removeRows = false;
45
46
    protected $selectedTables = false;
47
48
    protected $selectedTableList = [];
49
50
    protected $data = [];
51
52
    private static $tables_to_delete_forever = [];
53
54
    private static $tables_to_be_cleaned = [
55
        'LoginAttempt',
56
    ];
57
58
    private static $fields_to_be_cleaned = [];
59
60
    private static $field_table_comboes_to_be_cleaned = [];
61
62
    private static $tables_to_keep = [];
63
64
    private static $fields_to_keep = [
65
        'ClassName',
66
        'Created',
67
        'LastEdited',
68
    ];
69
70
    private static $field_table_combos_to_keep = [];
71
72
    private static $max_table_size_in_mb = 20;
73
74
    private static $max_column_size_in_mb = 2;
75
76
    private static $dependencies = [
77
        'anonymiser' => '%$' . Anonymiser::class,
78
        'database' => '%$' . DatabaseActions::class,
79
    ];
80
81
    private $anonymiser;
82
83
    private $database;
84
85
    /**
86
     * Set a custom url segment (to follow dev/tasks/).
87
     *
88
     * @config
89
     *
90
     * @var string
91
     */
92
    private static $segment = 'database-share-clean-up';
93
94
    public function setAnonymiser($anonymiser)
95
    {
96
        $this->anonymiser = $anonymiser;
97
98
        return $this;
99
    }
100
101
    public function setDatabase($database)
102
    {
103
        $this->database = $database;
104
105
        return $this;
106
    }
107
108
109
    /**
110
     * Implement this method in the task subclass to
111
     * execute via the TaskRunner.
112
     *
113
     * @param HTTPRequest $request
114
     */
115
    public function run($request)
116
    {
117
        $this->anonymise = (bool) $request->getVar('anonymise');
118
        $this->removeObsolete = (bool) $request->getVar('removeobsolete');
119
        $this->removeOldVersions = (bool) $request->getVar('removeoldversions');
120
        $this->removeRows = (bool) $request->getVar('removerows');
121
        $this->emptyFields = (bool) $request->getVar('emptyfields');
122
123
        $this->selectedTables = (bool) $request->getVar('selectedtables');
124
        $this->debug = (bool) $request->getVar('debug');
125
        $this->forReal = (bool) $request->getVar('forreal');
126
        if ($this->forReal) {
127
            $this->debug = true;
128
        }
129
        $this->selectedTableList = $request->getVar('selectedtablelist') ?? [];
130
131
        $this->anonymiser->setDatabaseActions($this->database);
132
        $this->database->setForReal($this->forReal);
133
        $this->database->setDebug($this->debug);
134
135
        if ($this->forReal) {
136
            FlushNow::do_flush('<h3>Running in FOR REAL mode</h3>', 'bad');
137
        } else {
138
            FlushNow::do_flush('<h3>Not runing FOR REAL</h3>', 'good');
139
        }
140
        if ($this->anonymise) {
141
            $this->anonymiser->AnonymisePresets();
142
        }
143
144
        $this->createForm();
145
        $this->runInner();
146
        $this->createTable();
147
    }
148
149
    protected function runInner()
150
    {
151
        $maxTableSize = $this->Config()->get('max_table_size_in_mb');
152
        $maxColumnSize = $this->Config()->get('max_column_size_in_mb');
153
154
        $tablesToDeleteForever = $this->Config()->get('tables_to_delete_forever');
155
156
        $tablesToKeep = $this->Config()->get('tables_to_keep');
157
        $fieldsToKeep = $this->Config()->get('fields_to_keep');
158
        $fieldTableCombosToKeep = $this->Config()->get('field_table_combos_to_keep');
159
160
        $tablesToBeCleaned = $this->Config()->get('tables_to_be_cleaned');
161
        $fieldsToBeCleaned = $this->Config()->get('fields_to_be_cleaned');
162
        $tableFieldCombosToBeCleaned = $this->Config()->get('field_table_comboes_to_be_cleaned');
163
164
        $tables = $this->database->getAllTables();
165
        foreach ($tables as $tableName) {
166
            $this->data[$tableName] = [
167
                'TableName' => $tableName,
168
                'SizeAfter' => 0,
169
                'SizeBefore' => 0,
170
                'Actions' => [],
171
            ];
172
173
            if (in_array($tableName, $tablesToKeep, true)) {
174
                if ($this->debug) {
175
                    $this->data[$tableName]['Actions'][] = 'Skipped because it is in list of tables to keep.';
176
                }
177
178
                continue;
179
            }
180
            if ($this->database->isEmptyTable($tableName)) {
181
                if ($this->debug) {
182
                    $this->data[$tableName]['Actions'][] = 'Skipped because table is empty.';
183
                }
184
185
                continue;
186
            }
187
            $this->data[$tableName]['SizeBefore'] = $this->database->getTableSizeInMegaBytes($tableName);
188
            if ($this->selectedTables && ! in_array($tableName, $this->selectedTableList, true)) {
189
                $this->data[$tableName]['Actions'][] = 'Skipped because it is not a selected table.';
190
191
                continue;
192
            }
193
194
            if ($this->removeObsolete) {
195
                if (in_array($tableName, $tablesToDeleteForever, true)) {
196
                    $this->database->deleteTable($tableName);
197
                    $this->data[$tableName]['Actions'][] = 'DELETING FOREVER.';
198
199
                    continue;
200
                }
201
                $outcome = $this->database->deleteObsoleteTables($tableName);
202
                if ($outcome) {
203
                    $this->data[$tableName]['Actions'][] = 'Deleted because it is obsolete.';
204
                }
205
            }
206
207
            if ($this->removeOldVersions) {
208
                $outcome = $this->database->emptyVersionedTable($tableName);
209
                if ($outcome) {
210
                    $this->data[$tableName]['Actions'][] = 'Remove all and replace with one entry for each record.';
211
                }
212
            }
213
214
            if ($this->anonymise) {
215
                $outcome = $this->anonymiser->AnonymiseTable($tableName);
216
                if ($outcome) {
217
                    $this->data[$tableName]['Actions'][] = 'Anonymised Table.';
218
                }
219
            }
220
            //get fields
221
            $fields = $this->database->getAllFieldsForOneTable($tableName);
222
223
            foreach ($fields as $fieldName) {
224
                if ('ID' === substr($fieldName, -2)) {
225
                    if ($this->debug) {
226
                        $this->data[$tableName]['Actions'][] = ' ... ' . $fieldName . ': skipping!';
227
                    }
228
229
                    continue;
230
                }
231
                if (in_array($fieldName, $fieldsToKeep, true)) {
232
                    if ($this->debug) {
233
                        $this->data[$tableName]['Actions'][] = ' ... ' . $fieldName . ': skipping!';
234
                    }
235
236
                    continue;
237
                }
238
239
                $combo = $tableName . '.' . $fieldName;
240
                if (in_array($combo, $fieldTableCombosToKeep, true)) {
241
                    if ($this->debug) {
242
                        $this->data[$tableName]['Actions'][] = ' ... ' . $fieldName . ': skipping.';
243
                    }
244
245
                    continue;
246
                }
247
                if ($this->anonymise) {
248
                    $outcome = $this->anonymiser->AnonymiseTableField($tableName, $fieldName);
249
                    if ($outcome) {
250
                        $this->data[$tableName]['Actions'][] = ' ... ' . $fieldName . ': anonymised.';
251
                    }
252
                }
253
                if ($this->emptyFields) {
254
                    $columnSize = $this->database->getColumnSizeInMegabytes($tableName, $fieldName);
255
                    $test1 = $columnSize > $maxColumnSize;
256
                    $test2 = in_array($fieldName, $fieldsToBeCleaned, true);
257
                    $test3 = in_array($combo, $tableFieldCombosToBeCleaned, true);
258
                    if ($test1 || $test2 || $test3) {
259
                        $percentageToKeep = $test2 || $test3 ? 0 : $maxColumnSize / $columnSize;
260
                        $outcome = $this->database->removeOldColumnsFromTable($tableName, $fieldName, $percentageToKeep);
261
                        if ($outcome) {
262
                            $this->data[$tableName]['Actions'][] = ' ... ' . $fieldName . ': Removed most rows.';
263
                        }
264
                    }
265
                }
266
            }
267
268
            // clean table
269
            if ($this->removeRows) {
270
                $removeAllRows = in_array($tableName, $tablesToBeCleaned, true);
271
                if ($removeAllRows) {
272
                    $this->database->removeOldRowsFromTable($tableName, 0.01);
273
                    $this->data[$tableName]['Actions'][] = 'Removed most rows.';
274
                } else {
275
                    $tableSize = $this->database->getTableSizeInMegaBytes($tableName);
276
                    if ($tableSize > $maxTableSize) {
277
                        $percentageToKeep = $maxTableSize / $tableSize;
278
                        $this->database->removeOldRowsFromTable($tableName, $percentageToKeep);
279
                        $this->data[$tableName]['Actions'][] = 'Removed old rows.';
280
                    }
281
                }
282
            }
283
            $this->data[$tableName]['SizeAfter'] = $this->database->getTableSizeInMegaBytes($tableName);
284
        }
285
    }
286
287
    protected function createForm()
288
    {
289
        $anonymise = $this->anonymise ? 'checked="checked"' : '';
290
        $removeObsolete = $this->removeObsolete ? 'checked="checked"' : '';
291
        $removeOldVersions = $this->removeOldVersions ? 'checked="checked"' : '';
292
        $removeRows = $this->removeRows ? 'checked="checked"' : '';
293
        $emptyFields = $this->emptyFields ? 'checked="checked"' : '';
294
        $selectedTables = $this->selectedTables ? 'checked="checked"' : '';
295
        $forReal = $this->forReal ? 'checked="checked"' : '';
296
        $debug = $this->debug ? 'checked="checked"' : '';
297
        echo <<<html
298
        <h3>All sizes in Megabytes</h3>
299
        <form method="get">
300
            <div style="
301
                background-color: pink;
302
                width: 300px;
303
                padding: 1vw;
304
                position: fixed;
305
                right: 0;
306
                top: 0;
307
                bottom: 0;
308
                border: 1px solid red;
309
            ">
310
                <h4>What actions to take?</h4>
311
                <div class="field" style="padding: 10px;">
312
                    <input type="checkbox" name="anonymise" {$anonymise} />
313
                    <label>anonymise</label>
314
                </div>
315
                <div class="field" style="padding: 10px;">
316
                    <input type="checkbox" name="removeoldversions" {$removeOldVersions} />
317
                    <label>remove versioned table entries</label>
318
                </div>
319
                <div class="field" style="padding: 10px;">
320
                    <input type="checkbox" name="removeobsolete" {$removeObsolete} />
321
                    <label>remove obsolete tables</label>
322
                </div>
323
324
                <div class="field" style="padding: 10px;">
325
                    <input type="checkbox" name="emptyfields" {$emptyFields} />
326
                    <label>empty large fields</label>
327
                </div>
328
329
                <div class="field" style="padding: 10px;">
330
                    <input type="checkbox" name="removerows" {$removeRows} />
331
                    <label>remove old rows if there are too many (not recommended)</label>
332
                </div>
333
334
                <hr />
335
                <h4>How to apply?</h4>
336
                <div class="field" style="padding: 10px;">
337
                    <input type="checkbox" name="selectedtables" {$selectedTables} />
338
                    <label>apply to selected tables only?</label>
339
                </div>
340
                <div class="field" style="padding: 10px;">
341
                    <input type="checkbox" name="forreal" {$forReal} />
342
                    <label>do it for real?</label>
343
                </div>
344
                <hr />
345
                <h4>See more info?</h4>
346
                <div class="field" style="padding: 10px;">
347
                    <input type="checkbox" name="debug" {$debug} />
348
                    <label>debug</label>
349
                </div>
350
                <hr />
351
                <div class="field" style="padding: 10px;">
352
                    <input type="submit" value="let's do it!" />
353
                </div>
354
            </div>
355
356
357
html;
358
    }
359
360
    protected function createTable()
361
    {
362
        $tbody = '';
363
        $totalSizeBefore = 0;
364
        $totalSizeAfter = 0;
365
        usort(
366
            $this->data,
367
            function ($a, $b) {
368
                return $a['SizeAfter'] <=> $b['SizeAfter'];
369
            }
370
        );
371
        foreach ($this->data as $data) {
372
            $totalSizeBefore += $data['SizeBefore'];
373
            $totalSizeAfter += $data['SizeAfter'];
374
            $actions = '';
375
            if (count($data['Actions'])) {
376
                $actions = '
377
                        <ul>
378
                            <li>
379
                            ' . implode('</li><li>', $data['Actions']) . '
380
                            </li>
381
                        </ul>';
382
            }
383
            $tableList = empty($this->selectedTableList[$data['TableName']]) ? '' : 'checked="checked"';
384
            $tbody .= '
385
                <tr>
386
                    <td>
387
                        <input type="checkbox" name="selectedtablelist[]" value="' . $data['TableName'] . '" ' . $tableList . ' />
388
                    </td>
389
                    <td>
390
                        ' . $data['TableName'] . '
391
                        ' . $actions . '
392
                    </td>
393
                    <td style="text-align: center;">
394
                        ' . $data['SizeBefore'] . '
395
                    </td>
396
                    <td style="text-align: center;">
397
                        ' . $data['SizeAfter'] . '
398
                    </td>
399
                </tr>';
400
        }
401
        $tfoot = '
402
                <tr>
403
                    <th>
404
                        &nbsp;
405
                    </th>
406
                    <th>
407
                        TOTAL
408
                    </th>
409
                    <th>
410
                        ' . $totalSizeBefore . '
411
                    </th>
412
                    <th>
413
                        ' . $totalSizeAfter . '
414
                    </th>
415
                </tr>
416
        ';
417
        echo '
418
        <table border="1" style="width: calc(100% - 380px);">
419
            <thead>
420
                <tr>
421
                    <th width="5%">
422
                        Select
423
                    </th>
424
                    <th width="65%">
425
                        Table Name
426
                    </th>
427
                    <th style="text-align: center;" width="15%">
428
                        Before
429
                    </th>
430
                    <th style="text-align: center;" width="15%">
431
                        After
432
                    </th>
433
                </tr>
434
            </thead>
435
            <tfoot>' . $tfoot . '</tfoot>
436
            <tbody>' . $tbody . '</tbody>
437
        </table>
438
    </form>';
439
    }
440
}
441