1 <?php
2
3 4 5
6 class rex_backup
7 {
8 const IMPORT_ARCHIVE = 1;
9 const IMPORT_DB = 2;
10 const IMPORT_EVENT_PRE = 3;
11 const IMPORT_EVENT_POST = 4;
12
13 public static function getDir()
14 {
15 $dir = rex_path::addonData('backup');
16 rex_dir::create($dir);
17
18 return $dir;
19 }
20
21 public static function getBackupFiles($filePrefix)
22 {
23 $dir = self::getDir();
24
25 $folder = rex_finder::factory($dir)->filesOnly();
26
27 $filtered = [];
28 foreach ($folder as $file) {
29 $file = $file->getFilename();
30 if (substr($file, strlen($file) - strlen($filePrefix)) == $filePrefix) {
31 $filtered[] = $file;
32 }
33 }
34 $folder = $filtered;
35
36 usort($folder, function ($file_a, $file_b) use ($dir) {
37 $time_a = filemtime($dir . '/' . $file_a);
38 $time_b = filemtime($dir . '/' . $file_b);
39
40 if ($time_a == $time_b) {
41 return 0;
42 }
43
44 return ($time_a > $time_b) ? -1 : 1;
45 });
46
47 return $folder;
48 }
49
50 51 52 53 54 55 56 57 58
59 public static function importDb($filename)
60 {
61 $return = [];
62 $return['state'] = false;
63 $return['message'] = '';
64
65 $msg = '';
66 $error = '';
67
68 if ($filename == '' || substr($filename, -4, 4) != '.sql') {
69 $return['message'] = rex_i18n::msg('backup_no_import_file_chosen_or_wrong_version') . '<br>';
70 return $return;
71 }
72
73 $conts = rex_file::get($filename);
74
75
76
77 $mainVersion = rex::getVersion('%s');
78 $version = strpos($conts, '## Redaxo Database Dump Version ' . $mainVersion);
79 if ($version === false) {
80 $return['message'] = rex_i18n::msg('backup_no_valid_import_file') . '. [## Redaxo Database Dump Version ' . $mainVersion . '] is missing';
81 return $return;
82 }
83
84 $conts = trim(str_replace('## Redaxo Database Dump Version ' . $mainVersion, '', $conts));
85
86
87
88 if (preg_match('/^## Prefix ([a-zA-Z0-9\_]*)/', $conts, $matches) && isset($matches[1])) {
89
90 $prefix = $matches[1];
91 $conts = trim(str_replace('## Prefix ' . $prefix, '', $conts));
92 } else {
93
94 $return['message'] = rex_i18n::msg('backup_no_valid_import_file') . '. [## Prefix ' . rex::getTablePrefix() . '] is missing';
95 return $return;
96 }
97
98
99
100 if (preg_match('/^## charset ([a-zA-Z0-9\_\-]*)/', $conts, $matches) && isset($matches[1])) {
101
102 $charset = $matches[1];
103 $conts = trim(str_replace('## charset ' . $charset, '', $conts));
104
105 $rexCharset = 'utf-8';
106 if ($rexCharset != $charset) {
107 $return['message'] = rex_i18n::msg('backup_no_valid_charset') . '. ' . $rexCharset . ' != ' . $charset;
108 return $return;
109 }
110 }
111
112
113 if (rex::getTablePrefix() != $prefix) {
114
115
116 $conts = preg_replace('/(TABLES? `?)' . preg_quote($prefix, '/') . '/i', '$1' . rex::getTablePrefix(), $conts);
117 $conts = preg_replace('/(INTO `?)' . preg_quote($prefix, '/') . '/i', '$1' . rex::getTablePrefix(), $conts);
118 $conts = preg_replace('/(EXISTS `?)' . preg_quote($prefix, '/') . '/i', '$1' . rex::getTablePrefix(), $conts);
119 }
120
121
122 $filesize = filesize($filename);
123 $msg = rex_extension::registerPoint(new rex_extension_point('BACKUP_BEFORE_DB_IMPORT', $msg, [
124 'content' => $conts,
125 'filename' => $filename,
126 'filesize' => $filesize,
127 ]));
128
129
130 self::importScript(str_replace('.sql', '.php', $filename), self::IMPORT_DB, self::IMPORT_EVENT_PRE);
131
132
133 $lines = [];
134 rex_sql_util::splitSqlFile($lines, $conts, 0);
135
136 $sql = rex_sql::factory();
137 foreach ($lines as $line) {
138 try {
139 $sql->setQuery($line['query']);
140 } catch (rex_sql_exception $e) {
141 $error .= "\n" . $e->getMessage();
142 }
143 }
144
145 if ($error != '') {
146 $return['message'] = trim($error);
147 return $return;
148 }
149
150 $msg .= rex_i18n::msg('backup_database_imported') . '. ' . rex_i18n::msg('backup_entry_count', count($lines)) . '<br />';
151 unset($lines);
152
153
154 $tables = rex_sql::factory()->getTables(rex::getTablePrefix());
155 $user_table_found = in_array(rex::getTablePrefix() . 'user', $tables);
156
157 if (!$user_table_found) {
158 $create_user_table = '
159 CREATE TABLE ' . rex::getTablePrefix() . 'user
160 (
161 id int(11) NOT NULL auto_increment,
162 name varchar(255) NOT NULL,
163 description text NOT NULL,
164 login varchar(50) NOT NULL,
165 psw varchar(50) NOT NULL,
166 status varchar(5) NOT NULL,
167 role int(11) NOT NULL,
168 rights text NOT NULL,
169 login_tries tinyint(4) NOT NULL DEFAULT 0,
170 createuser varchar(255) NOT NULL,
171 updateuser varchar(255) NOT NULL,
172 createdate datetime NOT NULL,
173 updatedate datetime NOT NULL,
174 lasttrydate datetime NOT NULL,
175 session_id varchar(255) NOT NULL,
176 PRIMARY KEY(id)
177 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;';
178 $db = rex_sql::factory();
179 try {
180 $db->setQuery($create_user_table);
181 } catch (rex_sql_exception $e) {
182
183 $msg = '';
184 $msg .= $e->getMessage();
185 }
186 }
187
188 $user_role_table_found = in_array(rex::getTablePrefix() . 'user_role', $tables);
189 if (!$user_role_table_found) {
190 $create_user_role_table = '
191 CREATE TABLE ' . rex::getTablePrefix() . 'user_role
192 (
193 id int(11) NOT NULL auto_increment,
194 name varchar(255) NOT NULL,
195 description text NOT NULL,
196 rights text NOT NULL,
197 createuser varchar(255) NOT NULL,
198 updateuser varchar(255) NOT NULL,
199 createdate datetime NOT NULL DEFAULT 0,
200 updatedate datetime NOT NULL DEFAULT 0
201 PRIMARY KEY(id)
202 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;';
203 $db = rex_sql::factory();
204 try {
205 $db->setQuery($create_user_role_table);
206 } catch (rex_sql_exception $e) {
207
208 $msg = '';
209 $msg .= $e->getMessage();
210 }
211 }
212
213
214 if ($error == '') {
215
216 rex_delete_cache();
217
218
219 rex_config::refresh();
220
221
222 $msg = rex_extension::registerPoint(new rex_extension_point('BACKUP_AFTER_DB_IMPORT', $msg, [
223 'content' => $conts,
224 'filename' => $filename,
225 'filesize' => $filesize,
226 ]));
227
228
229 self::importScript(str_replace('.sql', '.php', $filename), self::IMPORT_DB, self::IMPORT_EVENT_POST);
230
231
232 $msg .= rex_delete_cache();
233 $return['state'] = true;
234 }
235
236 $return['message'] = $msg;
237
238 return $return;
239 }
240
241 242 243 244 245 246 247 248 249
250 public static function importFiles($filename)
251 {
252 $return = [];
253 $return['state'] = false;
254
255 if ($filename == '' || substr($filename, -7, 7) != '.tar.gz') {
256 $return['message'] = rex_i18n::msg('backup_no_import_file_chosen') . '<br />';
257 return $return;
258 }
259
260
261 rex_dir::deleteFiles(rex_path::media());
262
263 $tar = new rex_backup_tar();
264
265
266 $tar = rex_extension::registerPoint(new rex_extension_point('BACKUP_BEFORE_FILE_IMPORT', $tar));
267
268
269 self::importScript(str_replace('.tar.gz', '.php', $filename), self::IMPORT_ARCHIVE, self::IMPORT_EVENT_PRE);
270
271 $tar->openTAR($filename);
272 if (!$tar->extractTar()) {
273 $msg = rex_i18n::msg('backup_problem_when_extracting') . '<br />';
274 if (count($tar->getMessages()) > 0) {
275 $msg .= rex_i18n::msg('backup_create_dirs_manually') . '<br />';
276 foreach ($tar->getMessages() as $_message) {
277 $msg .= rex_path::absolute($_message) . '<br />';
278 }
279 }
280 } else {
281 $msg = rex_i18n::msg('backup_file_imported') . '<br />';
282 }
283
284
285 $tar = rex_extension::registerPoint(new rex_extension_point('BACKUP_AFTER_FILE_IMPORT', $tar));
286
287
288 self::importScript(str_replace('.tar.gz', '.php', $filename), self::IMPORT_ARCHIVE, self::IMPORT_EVENT_POST);
289
290 $return['state'] = true;
291 $return['message'] = $msg;
292 return $return;
293 }
294
295 296 297 298 299 300 301 302 303
304 public static function exportDb($filename, array $tables = null)
305 {
306 $fp = @tmpfile();
307 $tempCacheFile = null;
308
309
310 if (!$fp) {
311 $tempCacheFile = rex_path::cache(basename($filename));
312 $fp = fopen($tempCacheFile, 'w');
313 if (!$fp) {
314 return false;
315 }
316 }
317
318 $sql = rex_sql::factory();
319
320 $nl = "\n";
321 $insertSize = 4000;
322
323
324 rex_extension::registerPoint(new rex_extension_point('BACKUP_BEFORE_DB_EXPORT'));
325
326
327 fwrite($fp, '## Redaxo Database Dump Version ' . rex::getVersion('%s') . $nl);
328 fwrite($fp, '## Prefix ' . rex::getTablePrefix() . $nl);
329 fwrite($fp, '## charset utf-8' . $nl . $nl);
330
331
332 fwrite($fp, 'SET FOREIGN_KEY_CHECKS = 0;' . $nl . $nl);
333
334 if (null === $tables) {
335 $tables = [];
336 foreach (rex_sql::factory()->getTables(rex::getTablePrefix()) as $table) {
337 if ($table != rex::getTable('user')
338 && substr($table, 0, strlen(rex::getTablePrefix() . rex::getTempPrefix())) != rex::getTablePrefix() . rex::getTempPrefix()
339 ) {
340 $tables[] = $table;
341 }
342 }
343 }
344 foreach ($tables as $table) {
345
346 $create = rex_sql::showCreateTable($table);
347
348 fwrite($fp, 'DROP TABLE IF EXISTS ' . $sql->escapeIdentifier($table) . ';' . $nl);
349 fwrite($fp, $create . ';' . $nl);
350
351 $fields = $sql->getArray('SHOW FIELDS FROM ' . $sql->escapeIdentifier($table));
352
353 foreach ($fields as &$field) {
354 if (preg_match('#^(bigint|int|smallint|mediumint|tinyint|timestamp)#i', $field['Type'])) {
355 $field = 'int';
356 } elseif (preg_match('#^(float|double|decimal)#', $field['Type'])) {
357 $field = 'double';
358 } elseif (preg_match('#^(char|varchar|text|longtext|mediumtext|tinytext)#', $field['Type'])) {
359 $field = 'string';
360 } elseif (preg_match('#^(date|datetime|time|timestamp|year)#', $field['Type'])) {
361
362 $field = 'raw';
363 }
364
365 }
366
367
368 $start = 0;
369 $max = $insertSize;
370
371 do {
372 $array = $sql->getArray('SELECT * FROM ' . $sql->escapeIdentifier($table) . ' LIMIT ' . $start . ',' . $max, [], PDO::FETCH_NUM);
373 $count = $sql->getRows();
374
375 if ($count > 0 && $start == 0) {
376 fwrite($fp, $nl . 'LOCK TABLES ' . $sql->escapeIdentifier($table) . ' WRITE;');
377 fwrite($fp, $nl . '/*!40000 ALTER TABLE ' . $sql->escapeIdentifier($table) . ' DISABLE KEYS */;');
378 } elseif ($count == 0) {
379 break;
380 }
381
382 $start += $max;
383 $values = [];
384
385 foreach ($array as $row) {
386 $record = [];
387
388 foreach ($fields as $idx => $type) {
389 $column = $row[$idx];
390
391 switch ($type) {
392
393 case 'raw':
394 $record[] = "'". $column ."'";
395 break;
396 case 'int':
397 $record[] = (int) $column;
398 break;
399 case 'double':
400 $record[] = sprintf('%.10F', (float) $column);
401 break;
402 case 'string':
403
404 if ($column === '0' || $column === '' || $column === ' ' || $column === '|' || $column === '||') {
405 $record[] = "'". $column ."'";
406 break;
407 }
408
409
410 if (strlen($column) <= 3 && ctype_alnum($column)) {
411 $record[] = "'". $column ."'";
412 break;
413 }
414
415 default:
416 $record[] = $sql->escape($column);
417 break;
418 }
419 }
420
421 $values[] = $nl . ' (' . implode(',', $record) . ')';
422 }
423
424 if (!empty($values)) {
425 fwrite($fp, $nl . 'INSERT INTO ' . $sql->escapeIdentifier($table) . ' VALUES ' . implode(',', $values) . ';');
426 unset($values);
427 }
428 } while ($count >= $max);
429
430 if ($start > 0) {
431 fwrite($fp, $nl . '/*!40000 ALTER TABLE ' . $sql->escapeIdentifier($table) . ' ENABLE KEYS */;');
432 fwrite($fp, $nl . 'UNLOCK TABLES;' . $nl . $nl);
433 }
434 }
435
436 fwrite($fp, 'SET FOREIGN_KEY_CHECKS = 1;' . $nl);
437
438 $hasContent = true;
439
440
441 if (rex_extension::isRegistered('BACKUP_AFTER_DB_EXPORT')) {
442 $content = rex_file::get($filename);
443 $hashBefore = md5($content);
444
445 $content = rex_extension::registerPoint(new rex_extension_point('BACKUP_AFTER_DB_EXPORT', $content));
446 $hashAfter = md5($content);
447
448 if ($hashAfter != $hashBefore) {
449 rex_file::put($filename, $content);
450 $hasContent = !empty($content);
451 unset($content);
452 }
453 }
454
455
456 if ($tempCacheFile) {
457 fclose($fp);
458 rename($tempCacheFile, $filename);
459 } else {
460 $destination = fopen($filename, 'w');
461 rewind($fp);
462 if (!$destination) {
463 return false;
464 }
465 stream_copy_to_stream($fp, $destination);
466 fclose($fp);
467 fclose($destination);
468 }
469
470 return $hasContent;
471 }
472
473 474 475 476 477 478 479
480 public static function exportFiles($folders)
481 {
482 $tar = new rex_backup_tar();
483
484
485 $tar = rex_extension::registerPoint(new rex_extension_point('BACKUP_BEFORE_FILE_EXPORT', $tar));
486
487 foreach ($folders as $item) {
488 self::addFolderToTar($tar, rex_url::frontend(), $item);
489 }
490
491
492 $tar = rex_extension::registerPoint(new rex_extension_point('BACKUP_AFTER_FILE_EXPORT', $tar));
493
494 return $tar->toTar(null, true);
495 }
496
497 498 499
500 private static function addFolderToTar(rex_backup_tar $tar, $path, $dir)
501 {
502 $handle = opendir($path . $dir);
503 $isMediafolder = realpath($path . $dir) . '/' == rex_path::media();
504 while (false !== ($file = readdir($handle))) {
505
506
507
508
509
510 if ($file == '.' || $file == '..' || $file == '.svn') {
511 continue;
512 }
513
514 if (substr($file, 0, strlen(rex::getTempPrefix())) == rex::getTempPrefix()) {
515 continue;
516 }
517
518 if ($isMediafolder && $file == 'addons') {
519 continue;
520 }
521
522 if (is_dir($path . $dir . '/' . $file)) {
523 self::addFolderToTar($tar, $path . $dir . '/', $file);
524 } else {
525 $tar->addFile($path . $dir . '/' . $file);
526 }
527 }
528 closedir($handle);
529 }
530
531 private static function importScript($filename, $importType, $eventType)
532 {
533 if (file_exists($filename)) {
534 require $filename;
535 }
536 }
537 }
538