Completed
Push — cleanup_command ( 4d7395 )
by Armando
59:11
created

CleanupCommand::getQueries()   F

Complexity

Conditions 11
Paths 257

Size

Total Lines 181
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 181
ccs 0
cts 165
cp 0
rs 3.8181
cc 11
eloc 59
nc 257
nop 1
crap 132

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file is part of the TelegramBot package.
4
 *
5
 * (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Longman\TelegramBot\Commands\AdminCommands;
12
13
use Longman\TelegramBot\Commands\AdminCommand;
14
use Longman\TelegramBot\DB;
15
use Longman\TelegramBot\Exception\TelegramException;
16
use Longman\TelegramBot\Request;
17
use Longman\TelegramBot\TelegramLog;
18
use PDOException;
19
20
/**
21
 * User "/cleanup" command
22
 */
23
class CleanupCommand extends AdminCommand
24
{
25
    /**
26
     * @var string
27
     */
28
    protected $name = 'cleanup';
29
30
    /**
31
     * @var string
32
     */
33
    protected $description = 'Clean up the database from old records';
34
35
    /**
36
     * @var string
37
     */
38
    protected $usage = '/cleanup <days> or /cleanup <count> <unit> (e.g. 3 weeks)';
39
40
    /**
41
     * @var string
42
     */
43
    protected $version = '1.0.0';
44
45
    /**
46
     * Default tables to clean, cleaning 'chat', 'user' and 'user_chat' by default is bad practice!
47
     *
48
     * @var array
49
     */
50
    protected static $default_tables_to_clean = [
51
        'botan_shortener',
52
        'callback_query',
53
        'chosen_inline_result',
54
        'conversation',
55
        'edited_message',
56
        'inline_query',
57
        'message',
58
        'request_limiter',
59
        'telegram_update',
60
    ];
61
62
    /**
63
     * By default, remove records older than X days/hours/anything from these tables.
64
     *
65
     * @var array
66
     */
67
    protected static $default_clean_older_than = [
68
        'botan_shortener'      => '30 days',
69
        'chat'                 => '365 days',
70
        'callback_query'       => '30 days',
71
        'chosen_inline_result' => '30 days',
72
        'conversation'         => '30 days',
73
        'edited_message'       => '30 days',
74
        'inline_query'         => '30 days',
75
        'message'              => '30 days',
76
        'request_limiter'      => '1 minute',
77
        'telegram_update'      => '30 days',
78
        'user'                 => '365 days',
79
        'user_chat'            => '365 days',
80
    ];
81
82
    /**
83
     * Set command config
84
     *
85
     * @param string $custom_time
86
     *
87
     * @return array
88
     */
89
    private function getSettings($custom_time = '')
90
    {
91
        $tables_to_clean      = self::$default_tables_to_clean;
92
        $user_tables_to_clean = $this->getConfig('tables_to_clean');
93
        if (is_array($user_tables_to_clean)) {
94
            $tables_to_clean = $user_tables_to_clean;
95
        }
96
97
        $clean_older_than      = self::$default_clean_older_than;
98
        $user_clean_older_than = $this->getConfig('clean_older_than');
99
        if (is_array($user_clean_older_than)) {
100
            $clean_older_than = array_merge($clean_older_than, $user_clean_older_than);
101
        }
102
103
        // Convert numeric-only values to days.
104
        array_walk($clean_older_than, function (&$time) use ($custom_time) {
105
            if (!empty($custom_time)) {
106
                $time = $custom_time;
107
            }
108
            if (is_numeric($time)) {
109
                $time .= ' days';
110
            }
111
        });
112
113
        return compact('tables_to_clean', 'clean_older_than');
114
    }
115
116
    /**
117
     * Get SQL queries array based on settings provided
118
     *
119
     * @param $settings
120
     *
121
     * @return array
122
     * @throws TelegramException
123
     */
124
    private function getQueries($settings)
125
    {
126
        if (empty($settings) || !is_array($settings)) {
127
            throw new TelegramException('Settings variable is not an array or is empty!');
128
        }
129
130
        // Convert all clean_older_than times to correct format.
131
        $clean_older_than = $settings['clean_older_than'];
132
        foreach ($clean_older_than as $table => $time) {
133
            $clean_older_than[$table] = date('Y-m-d H:i:s', strtotime('-' . $time));
134
        }
135
        $tables_to_clean = $settings['tables_to_clean'];
136
137
        $queries = [];
138
139
        if (in_array('telegram_update', $tables_to_clean, true)) {
140
            $queries[] = sprintf('
141
              DELETE FROM `%3$s`
142
              WHERE `id` != \'%1$s\'
143
                AND `chat_id` NOT IN (
144
                  SELECT `id`
145
                  FROM `%4$s` 
146
                  WHERE `chat_id` = `%4$s`.`id`
147
                )
148
                AND (
149
                  `message_id` IS NOT NULL
150
                  AND `message_id` IN (
151
                    SELECT f.id
152
                    FROM `%5$s` f 
153
                    WHERE `date` < \'%2$s\'
154
                  )
155
                ) 
156
                OR (
157
                  `edited_message_id` IS NOT NULL
158
                  AND `edited_message_id` IN (
159
                    SELECT f.id
160
                    FROM `%6$s` f
161
                    WHERE `edit_date` < \'%2$s\'
162
                  )
163
                )
164
                OR (
165
                  `inline_query_id` IS NOT NULL 
166
                  AND `inline_query_id` IN (
167
                    SELECT f.id 
168
                    FROM `%7$s` f 
169
                    WHERE `created_at` < \'%2$s\'
170
                  )
171
                ) 
172
                OR (
173
                  `chosen_inline_result_id` IS NOT NULL 
174
                  AND `chosen_inline_result_id` IN (
175
                    SELECT f.id 
176
                    FROM `%8$s` f 
177
                    WHERE `created_at` < \'%2$s\'
178
                  )
179
                ) 
180
                OR (
181
                  `callback_query_id` IS NOT NULL 
182
                  AND `callback_query_id` IN (
183
                    SELECT f.id 
184
                    FROM `%9$s` f 
185
                    WHERE `created_at` < \'%2$s\'
186
                  )
187
                )
188
            ',
189
                $this->getUpdate()->getUpdateId(),
190
                $clean_older_than['telegram_update'],
191
                TB_TELEGRAM_UPDATE,
192
                TB_CHAT,
193
                TB_MESSAGE,
194
                TB_EDITED_MESSAGE,
195
                TB_INLINE_QUERY,
196
                TB_CHOSEN_INLINE_RESULT,
197
                TB_CALLBACK_QUERY
198
            );
199
        }
200
201
        if (in_array('user_chat', $tables_to_clean, true)) {
202
            $queries[] = sprintf('
203
                DELETE FROM `%1$s` 
204
                WHERE `user_id` IN (
205
                  SELECT f.id
206
                  FROM `%2$s` f 
207
                  WHERE `updated_at` < \'%3$s\'
208
                )
209
            ',
210
                TB_USER_CHAT,
211
                TB_USER,
212
                $clean_older_than['chat']
213
            );
214
        }
215
216
        // Simple.
217
        $simple_tables = [
218
            'user'            => ['table' => TB_USER, 'field' => 'updated_at'],
219
            'chat'            => ['table' => TB_CHAT, 'field' => 'updated_at'],
220
            'conversation'    => ['table' => TB_CONVERSATION, 'field' => 'updated_at'],
221
            'request_limiter' => ['table' => TB_REQUEST_LIMITER, 'field' => 'created_at'],
222
        ];
223
224
        // Botan table is only available if enabled.
225
        if (defined('TB_BOTAN_SHORTENER')) {
226
            $simple_tables['botan_shortener'] = ['table' => TB_BOTAN_SHORTENER, 'field' => 'created_at'];
227
        }
228
229
        foreach (array_intersect(array_keys($simple_tables), $tables_to_clean) as $table_to_clean) {
230
            $queries[] = sprintf('
231
                DELETE FROM `%1$s` 
232
                WHERE `%2$s` < \'%3$s\'
233
            ',
234
                $simple_tables[$table_to_clean]['table'],
235
                $simple_tables[$table_to_clean]['field'],
236
                $clean_older_than[$table_to_clean]
237
            );
238
        }
239
240
        // Queries.
241
        $query_tables = [
242
            'inline_query'         => ['table' => TB_INLINE_QUERY, 'field' => 'created_at'],
243
            'chosen_inline_result' => ['table' => TB_CHOSEN_INLINE_RESULT, 'field' => 'created_at'],
244
            'callback_query'       => ['table' => TB_CALLBACK_QUERY, 'field' => 'created_at'],
245
        ];
246
        foreach (array_intersect(array_keys($query_tables), $tables_to_clean) as $table_to_clean) {
247
            $queries[] = sprintf('
248
                DELETE FROM `%1$s` 
249
                WHERE `%2$s` < \'%3$s\'
250
                  AND `id` NOT IN (
251
                    SELECT `%4$s` 
252
                    FROM `%5$s` 
253
                    WHERE `%4$s` = `%1$s`.`id`
254
                  )
255
            ',
256
                $query_tables[$table_to_clean]['table'],
257
                $query_tables[$table_to_clean]['field'],
258
                $clean_older_than[$table_to_clean],
259
                $table_to_clean . '_id',
260
                TB_TELEGRAM_UPDATE
261
            );
262
        }
263
264
        // Messages
265
        if (in_array('edited_message', $tables_to_clean, true)) {
266
            $queries[] = sprintf('
267
                DELETE FROM `%1$s` 
268
                WHERE `edit_date` < \'%2$s\'
269
                  AND `id` NOT IN (
270
                    SELECT `message_id`
271
                    FROM `%3$s`
272
                    WHERE `edited_message_id` = `%1$s`.`id`
273
                  )
274
            ',
275
                TB_EDITED_MESSAGE,
276
                $clean_older_than['edited_message'],
277
                TB_TELEGRAM_UPDATE
278
            );
279
        }
280
281
        if (in_array('message', $tables_to_clean, true)) {
282
            $queries[] = sprintf('
283
                DELETE FROM `%1$s` 
284
                WHERE `date` < \'%2$s\'
285
                  AND `id` NOT IN (
286
                    SELECT `message_id`
287
                    FROM `%3$s`
288
                    WHERE `message_id` = `%1$s`.`id`
289
                  )
290
                  AND `id` NOT IN (
291
                    SELECT `message_id`
292
                    FROM `%4$s`
293
                    WHERE `message_id` = `%1$s`.`id`
294
                  )
295
            ',
296
                TB_MESSAGE,
297
                $clean_older_than['message'],
298
                TB_TELEGRAM_UPDATE,
299
                TB_CALLBACK_QUERY
300
            );
301
        }
302
303
        return $queries;
304
    }
305
306
    /**
307
     * Command execute method
308
     *
309
     * @return \Longman\TelegramBot\Entities\ServerResponse
310
     * @throws \Longman\TelegramBot\Exception\TelegramException
311
     */
312
    public function execute()
313
    {
314
        $message = $this->getMessage();
315
        $chat_id = $message->getFrom()->getId();
316
        $text    = $message->getText(true);
317
318
        $data = [
319
            'chat_id'    => $chat_id,
320
            'parse_mode' => 'Markdown',
321
        ];
322
323
        if (!$message->getChat()->isPrivateChat()) {
324
            $data['text'] = '/cleanup command is only available in a private chat.';
325
326
            return Request::sendMessage($data);
327
        }
328
329
        $settings = $this->getSettings($text);
330
        $queries  = $this->getQueries($settings);
331
332
        $infos = [];
333
        foreach ($settings['tables_to_clean'] as $table) {
334
            $info = '*' . $table . '*';
335
336
            if (isset($settings['clean_older_than'][$table])) {
337
                $info .= ' (' . $settings['clean_older_than'][$table] . ')';
338
            }
339
340
            $infos[] = $info;
341
        }
342
343
        $data['text'] = 'Cleaning up tables:' . PHP_EOL . implode(PHP_EOL, $infos);
344
345
        Request::sendMessage($data);
346
347
        $rows = 0;
348
        $pdo  = DB::getPdo();
349
        try {
350
            $pdo->beginTransaction();
351
352
            foreach ($queries as $query) {
353
                if ($dbq = $pdo->query($query)) {
354
                    $rows += $dbq->rowCount();
355
                } else {
356
                    TelegramLog::error('Error while executing query: ' . $query);
357
                }
358
            }
359
360
            $pdo->commit();     // commit changes to the database and end transaction
361
        } catch (PDOException $e) {
362
            $pdo->rollBack();   // rollback changes on exception (useful if you want to track down error - you can't replicate it when some of the data is already deleted...)
363
364
            $data['text'] = '*Database cleanup failed!* _(check your error logs)_';
365
            Request::sendMessage($data);
366
367
            throw new TelegramException($e->getMessage());
368
        }
369
370
        if ($rows > 0) {
371
            $data['text'] = '*Database cleanup done!* _(removed ' . $rows . ' rows)_';
372
        } else {
373
            $data['text'] = '*No data to clean!*';
374
        }
375
376
        return Request::sendMessage($data);
377
    }
378
}
379