<?php
declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Classes\Validator;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
use Illuminate\View\View;
class BackupController extends AdminController
{
public string $date;
/**
* Конструктор
*/
public function __construct()
{
if (function_exists('set_time_limit')) {
set_time_limit(600);
}
$this->date = date('d-M-Y_H-i-s', SITETIME);
}
/**
* Главная страница
*
* @return View
*/
public function index(): View
{
$files = glob(storage_path('backups/*.{zip,gz,bz2,sql}'), GLOB_BRACE);
arsort($files);
return view('admin/backups/index', compact('files'));
}
/**
* Создание нового бэкапа
*
* @param Request $request
* @param Validator $validator
*
* @return View|RedirectResponse
*/
public function create(Request $request, Validator $validator)
{
if ($request->isMethod('post')) {
$sheets = check($request->input('sheets'));
$method = $request->input('method');
$level = int($request->input('level'));
$validator->equal($request->input('_token'), csrf_token(), __('validator.token'))
->notEmpty($sheets, ['sheets' => __('admin.backup.no_tables_save')])
->in($method, ['none', 'gzip', 'bzip'], ['method' => __('admin.backup.wrong_compression_method')])
->between($level, 0, 9, ['level' => __('admin.backup.wrong_compression_ratio')]);
if ($validator->isValid()) {
$selectTables = DB::select('SHOW TABLE STATUS where name IN("' . implode('","', $sheets) . '")');
$limit = 3000;
$filename = 'backup_'.$this->date.'.sql';
$fp = $this->fopen(storage_path('backups/'.$filename), 'w', $method, $level);
foreach ($selectTables as $table) {
$show = DB::selectOne("SHOW CREATE TABLE `{$table->Name}`");
$columnsFields = DB::select("SHOW COLUMNS FROM `{$table->Name}`");
$columns = '(' .implode(',', array_column($columnsFields, 'Field')) . ')';
$this->fwrite($fp, "--\n-- Structure table `{$table->Name}`\n--\n\n", $method);
$this->fwrite($fp, "DROP TABLE IF EXISTS `{$table->Name}`;\n{$show->{'Create Table'}};\n\n", $method);
$total = DB::table($table->Name)->count();
if (! $total) {
continue;
}
$this->fwrite($fp, "--\n-- Dump table `{$table->Name}`\n--\n\n", $method);
$this->fwrite($fp, "INSERT INTO `{$table->Name}` {$columns} VALUES ", $method);
for ($i = 0; $i < $total; $i += $limit) {
$cols = DB::table($table->Name)->lockForUpdate()->limit($limit)->offset($i)->get();
foreach ($cols as $key => $col) {
$records = get_object_vars($col);
$columns = [];
foreach ($records as $record) {
$record = is_int($record) || is_null($record) ? $record : "'" . str_replace("'", "''", $record) . "'";
$columns[] = $record ?? 'null';
}
$this->fwrite($fp, ($key || $i ? ',' : '') . '(' . implode(',', $columns) . ')', $method);
unset($columns);
}
unset($cols);
}
$this->fwrite($fp, ";\n\n", $method);
}
$this->fclose($fp, $method);
setFlash('success', __('admin.backup.database_success_saved'));
return redirect('admin/backups');
}
setInput($request->all());
setFlash('danger', $validator->getErrors());
}
$tables = DB::select('SHOW TABLE STATUS');
$bzopen = function_exists('bzopen');
$gzopen = function_exists('gzopen');
$levels = range(0, 9);
return view('admin/backups/create', compact('tables', 'bzopen', 'gzopen', 'levels'));
}
/**
* Удаляет сохраненный бэкап
*
* @param Request $request
* @param Validator $validator
*
* @return RedirectResponse
*/
public function delete(Request $request, Validator $validator): RedirectResponse
{
$file = $request->input('file');
$validator->equal($request->input('_token'), csrf_token(), __('validator.token'))
->notEmpty($file, __('admin.backup.backup_not_indicated'))
->regex($file, '|^[\w\.\-]+$|i', __('admin.backup.invalid_backup_name'))
->true(file_exists(storage_path('backups/' . $file)), __('admin.backup.backup_not_exist'));
if ($validator->isValid()) {
unlink(storage_path('backups/' . $file));
setFlash('success', __('admin.backup.backup_success_deleted'));
} else {
setFlash('danger', $validator->getErrors());
}
return redirect('admin/backups');
}
/**
* Открывает поток
*
* @param string $name
* @param string $mode
* @param string $method
* @param int $level
*
* @return bool|resource
*/
private function fopen(string $name, string $mode, string $method, int $level)
{
if ($method === 'bzip') {
return bzopen($name . '.bz2', $mode);
}
if ($method === 'gzip') {
return gzopen($name . '.gz', "{$mode}b{$level}");
}
return fopen($name, $mode . 'b');
}
/**
* Записывает данные в поток
*
* @param resource $fp
* @param string $str
* @param string $method
*/
private function fwrite($fp, string $str, string $method): void
{
if ($method === 'bzip') {
bzwrite($fp, $str);
} elseif ($method === 'gzip') {
gzwrite($fp, $str);
} else {
fwrite($fp, $str);
}
}
/**
* Закрывает поток
*
* @param resource $fp
* @param string $method
*/
private function fclose($fp, string $method): void
{
if ($method === 'bzip') {
bzclose($fp);
} elseif ($method === 'gzip') {
gzclose($fp);
} else {
fflush($fp);
fclose($fp);
}
}
}