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