1: <?php
2: namespace Opencart\Admin\Controller\Tool;
3: /**
4: * Class Backup
5: *
6: * @package Opencart\Admin\Controller\Tool
7: */
8: class Backup extends \Opencart\System\Engine\Controller {
9: /**
10: * Index
11: *
12: * @return void
13: */
14: public function index(): void {
15: $this->load->language('tool/backup');
16:
17: $this->document->setTitle($this->language->get('heading_title'));
18:
19: // Use the ini_get('upload_max_filesize') for the max file size
20: $data['error_upload_size'] = sprintf($this->language->get('error_upload_size'), ini_get('upload_max_filesize'));
21:
22: $data['config_file_max_size'] = ((int)preg_filter('/[^0-9]/', '', ini_get('upload_max_filesize')) * 1024 * 1024);
23:
24: $data['breadcrumbs'] = [];
25:
26: $data['breadcrumbs'][] = [
27: 'text' => $this->language->get('text_home'),
28: 'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'])
29: ];
30:
31: $data['breadcrumbs'][] = [
32: 'text' => $this->language->get('heading_title'),
33: 'href' => $this->url->link('tool/backup', 'user_token=' . $this->session->data['user_token'])
34: ];
35:
36: $data['upload'] = $this->url->link('tool/backup.upload', 'user_token=' . $this->session->data['user_token']);
37:
38: $this->load->model('tool/backup');
39:
40: $ignore = [
41: DB_PREFIX . 'user',
42: DB_PREFIX . 'user_group'
43: ];
44:
45: $data['tables'] = [];
46:
47: $results = $this->model_tool_backup->getTables();
48:
49: foreach ($results as $result) {
50: if (!in_array($result, $ignore)) {
51: $data['tables'][] = $result;
52: }
53: }
54:
55: $data['history'] = $this->getHistory();
56:
57: $data['user_token'] = $this->session->data['user_token'];
58:
59: $data['header'] = $this->load->controller('common/header');
60: $data['column_left'] = $this->load->controller('common/column_left');
61: $data['footer'] = $this->load->controller('common/footer');
62:
63: $this->response->setOutput($this->load->view('tool/backup', $data));
64: }
65:
66: /**
67: * History
68: *
69: * @return void
70: */
71: public function history(): void {
72: $this->load->language('tool/backup');
73:
74: $this->response->setOutput($this->getHistory());
75: }
76:
77: /**
78: * Get History
79: *
80: * @return string
81: */
82: public function getHistory(): string {
83: $this->load->language('tool/backup');
84:
85: $data['histories'] = [];
86:
87: $files = glob(DIR_STORAGE . 'backup/*.sql');
88:
89: foreach ($files as $file) {
90: $size = filesize($file);
91:
92: $i = 0;
93:
94: $suffix = [
95: 'B',
96: 'KB',
97: 'MB',
98: 'GB',
99: 'TB',
100: 'PB',
101: 'EB',
102: 'ZB',
103: 'YB'
104: ];
105:
106: while (($size / 1024) > 1) {
107: $size /= 1024;
108:
109: $i++;
110: }
111:
112: $data['histories'][] = [
113: 'filename' => basename($file),
114: 'size' => round(substr($size, 0, strpos($size, '.') + 4), 2) . $suffix[$i],
115: 'date_added' => date($this->language->get('datetime_format'), filemtime($file)),
116: 'download' => $this->url->link('tool/backup.download', 'user_token=' . $this->session->data['user_token'] . '&filename=' . urlencode(basename($file))),
117: ];
118: }
119:
120: return $this->load->view('tool/backup_history', $data);
121: }
122:
123: /**
124: * Backup
125: *
126: * @return void
127: */
128: public function backup(): void {
129: $this->load->language('tool/backup');
130:
131: $json = [];
132:
133: if (isset($this->request->get['filename'])) {
134: $filename = basename(html_entity_decode($this->request->get['filename'], ENT_QUOTES, 'UTF-8'));
135: } else {
136: $filename = date('Y-m-d H.i.s') . '.sql';
137: }
138:
139: if (isset($this->request->get['table'])) {
140: $table = $this->request->get['table'];
141: } else {
142: $table = '';
143: }
144:
145: if (isset($this->request->post['backup'])) {
146: $backup = $this->request->post['backup'];
147: } else {
148: $backup = [];
149: }
150:
151: if (isset($this->request->get['page'])) {
152: $page = (int)$this->request->get['page'];
153: } else {
154: $page = 1;
155: }
156:
157: if (!$this->user->hasPermission('modify', 'tool/backup')) {
158: $json['error'] = $this->language->get('error_permission');
159: }
160:
161: $this->load->model('tool/backup');
162:
163: $allowed = $this->model_tool_backup->getTables();
164:
165: if (!in_array($table, $allowed)) {
166: $json['error'] = sprintf($this->language->get('error_table'), $table);
167: }
168:
169: if (!$json) {
170: $output = '';
171:
172: if ($page == 1) {
173: $output .= 'TRUNCATE TABLE `' . $this->db->escape($table) . '`;' . "\n\n";
174: }
175:
176: $results = $this->model_tool_backup->getRecords($table, ($page - 1) * 200, 200);
177:
178: foreach ($results as $result) {
179: $fields = '';
180:
181: foreach (array_keys($result) as $value) {
182: $fields .= '`' . $value . '`, ';
183: }
184:
185: $values = '';
186:
187: foreach (array_values($result) as $value) {
188: if ($value !== null) {
189: $value = str_replace(["\x00", "\x0a", "\x0d", "\x1a"], ['\0', '\n', '\r', '\Z'], $value);
190: $value = str_replace(["\n", "\r", "\t"], ['\n', '\r', '\t'], $value);
191: $value = str_replace('\\', '\\\\', $value);
192: $value = str_replace('\'', '\\\'', $value);
193: $value = str_replace('\\\n', '\n', $value);
194: $value = str_replace('\\\r', '\r', $value);
195: $value = str_replace('\\\t', '\t', $value);
196:
197: $values .= '\'' . $value . '\', ';
198: } else {
199: $values .= 'NULL, ';
200: }
201: }
202:
203: $output .= 'INSERT INTO `' . $table . '` (' . preg_replace('/, $/', '', $fields) . ') VALUES (' . preg_replace('/, $/', '', $values) . ');' . "\n";
204: }
205:
206: $position = array_search($table, $backup);
207:
208: $record_total = $this->model_tool_backup->getTotalRecords($table);
209:
210: if (($page * 200) >= $record_total) {
211: $output .= "\n";
212:
213: if (isset($backup[$position + 1])) {
214: $table = $backup[$position + 1];
215: } else {
216: $table = '';
217: }
218: }
219:
220: if ($position !== false) {
221: $json['progress'] = round(($position / count($backup)) * 100);
222: } else {
223: $json['progress'] = 0;
224: }
225:
226: $handle = fopen(DIR_STORAGE . 'backup/' . $filename, 'a');
227:
228: fwrite($handle, $output);
229:
230: fclose($handle);
231:
232: if (!$table) {
233: $json['success'] = $this->language->get('text_success');
234: } elseif (($page * 200) >= $record_total) {
235: $json['text'] = sprintf($this->language->get('text_backup'), $table, ($page - 1) * 200, $record_total);
236:
237: $json['next'] = $this->url->link('tool/backup.backup', 'user_token=' . $this->session->data['user_token'] . '&filename=' . urlencode($filename) . '&table=' . $table . '&page=1', true);
238: } else {
239: $json['text'] = sprintf($this->language->get('text_backup'), $table, ($page - 1) * 200, $page * 200);
240:
241: $json['next'] = $this->url->link('tool/backup.backup', 'user_token=' . $this->session->data['user_token'] . '&filename=' . urlencode($filename) . '&table=' . $table . '&page=' . ($page + 1), true);
242: }
243: }
244:
245: $this->response->addHeader('Content-Type: application/json');
246: $this->response->setOutput(json_encode($json));
247: }
248:
249: /**
250: * Restore
251: *
252: * @return void
253: */
254: public function restore(): void {
255: $this->load->language('tool/backup');
256:
257: $json = [];
258:
259: if (isset($this->request->get['filename'])) {
260: $filename = basename(html_entity_decode($this->request->get['filename'], ENT_QUOTES, 'UTF-8'));
261: } else {
262: $filename = '';
263: }
264:
265: if (isset($this->request->get['position'])) {
266: $position = $this->request->get['position'];
267: } else {
268: $position = 0;
269: }
270:
271: if (!$this->user->hasPermission('modify', 'tool/backup')) {
272: $json['error'] = $this->language->get('error_permission');
273: }
274:
275: $file = DIR_STORAGE . 'backup/' . $filename;
276:
277: if (!is_file($file)) {
278: $json['error'] = $this->language->get('error_file');
279: }
280:
281: if (!$json) {
282: // We set $i so we can batch execute the queries rather than do them all at once.
283: $i = 0;
284:
285: $handle = fopen($file, 'r');
286:
287: fseek($handle, $position, SEEK_SET);
288:
289: while (!feof($handle) && ($i < 100)) {
290: $position = ftell($handle);
291:
292: $line = fgets($handle, 1000000);
293:
294: if ($i > 0 && (substr($line, 0, strlen('TRUNCATE TABLE `' . DB_PREFIX . 'user`')) == 'TRUNCATE TABLE `' . DB_PREFIX . 'user`' || substr($line, 0, strlen('TRUNCATE TABLE `' . DB_PREFIX . 'user_group`')) == 'TRUNCATE TABLE `' . DB_PREFIX . 'user_group`')) {
295: fseek($handle, $position, SEEK_SET);
296:
297: break;
298: }
299:
300: if ((substr($line, 0, 14) == 'TRUNCATE TABLE' || substr($line, 0, 11) == 'INSERT INTO') && substr($line, -2) == ";\n") {
301: $this->db->query(substr($line, 0, strlen($line) - 2));
302: }
303:
304: $i++;
305: }
306:
307: $position = ftell($handle);
308:
309: $size = filesize($file);
310:
311: if ($position) {
312: $json['progress'] = round(($position / $size) * 100);
313: } else {
314: $json['progress'] = 0;
315: }
316:
317: if ($position && !feof($handle)) {
318: $json['text'] = sprintf($this->language->get('text_restore'), $position, $size);
319:
320: $json['next'] = $this->url->link('tool/backup.restore', 'user_token=' . $this->session->data['user_token'] . '&filename=' . urlencode($filename) . '&position=' . $position, true);
321: } else {
322: $json['success'] = $this->language->get('text_success');
323:
324: $this->cache->delete('*');
325: }
326:
327: fclose($handle);
328: }
329:
330: $this->response->addHeader('Content-Type: application/json');
331: $this->response->setOutput(json_encode($json));
332: }
333:
334: /**
335: * Upload
336: *
337: * @return void
338: */
339: public function upload(): void {
340: $this->load->language('tool/backup');
341:
342: $json = [];
343:
344: // Check user has permission
345: if (!$this->user->hasPermission('modify', 'tool/backup')) {
346: $json['error'] = $this->language->get('error_permission');
347: }
348:
349: if (empty($this->request->files['upload']['name']) || !is_file($this->request->files['upload']['tmp_name'])) {
350: $json['error'] = $this->language->get('error_upload');
351: }
352:
353: if (!$json) {
354: // Sanitize the filename
355: $filename = basename(html_entity_decode($this->request->files['upload']['name'], ENT_QUOTES, 'UTF-8'));
356:
357: if ((oc_strlen($filename) < 3) || (oc_strlen($filename) > 128)) {
358: $json['error'] = $this->language->get('error_filename');
359: }
360:
361: // Allowed file extension types
362: if (strtolower(substr(strrchr($filename, '.'), 1)) != 'sql') {
363: $json['error'] = $this->language->get('error_file_type');
364: }
365: }
366:
367: if (!$json) {
368: move_uploaded_file($this->request->files['upload']['tmp_name'], DIR_STORAGE . 'backup/' . $filename);
369:
370: $json['success'] = $this->language->get('text_success');
371: }
372:
373: $this->response->addHeader('Content-Type: application/json');
374: $this->response->setOutput(json_encode($json));
375: }
376:
377: /**
378: * Download
379: *
380: * @return void
381: */
382: public function download(): void {
383: $this->load->language('tool/backup');
384:
385: $json = [];
386:
387: if (isset($this->request->get['filename'])) {
388: $filename = basename(html_entity_decode($this->request->get['filename'], ENT_QUOTES, 'UTF-8'));
389: } else {
390: $filename = '';
391: }
392:
393: // Check user has permission
394: if (!$this->user->hasPermission('modify', 'tool/backup')) {
395: $this->response->redirect($this->url->link('error/permission', 'user_token=' . $this->session->data['user_token'], true));
396: }
397:
398: $file = DIR_STORAGE . 'backup/' . $filename;
399:
400: if (!is_file($file)) {
401: $this->response->redirect($this->url->link('error/not_found', 'user_token=' . $this->session->data['user_token'], true));
402: }
403:
404: if (!headers_sent()) {
405: header('Content-Type: application/octet-stream');
406: header('Content-Disposition: attachment; filename="' . $filename . '"');
407: header('Expires: 0');
408: header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
409: header('Pragma: public');
410: header('Content-Length: ' . filesize($file));
411:
412: if (ob_get_level()) {
413: ob_end_clean();
414: }
415:
416: readfile($file);
417:
418: exit();
419: } else {
420: exit($this->language->get('error_headers_sent'));
421: }
422: }
423:
424: /**
425: * Delete
426: *
427: * @return void
428: */
429: public function delete(): void {
430: $this->load->language('tool/backup');
431:
432: $json = [];
433:
434: if (isset($this->request->get['filename'])) {
435: $filename = basename(html_entity_decode($this->request->get['filename'], ENT_QUOTES, 'UTF-8'));
436: } else {
437: $filename = '';
438: }
439:
440: // Check user has permission
441: if (!$this->user->hasPermission('modify', 'tool/backup')) {
442: $json['error'] = $this->language->get('error_permission');
443: }
444:
445: $file = DIR_STORAGE . 'backup/' . $filename;
446:
447: if (!is_file($file)) {
448: $json['error'] = $this->language->get('error_file');
449: }
450:
451: if (!$json) {
452: unlink($file);
453:
454: $json['success'] = $this->language->get('text_success');
455: }
456:
457: $this->response->addHeader('Content-Type: application/json');
458: $this->response->setOutput(json_encode($json));
459: }
460: }
461: