<?php
/**
 * DouPHP
 * --------------------------------------------------------------------------------------------------
 * 版权所有 2013-2024 漳州豆壳网络科技有限公司，并保留所有权利。
 * 网站地址: http://www.douphp.com
 * --------------------------------------------------------------------------------------------------
 * 这不是一个自由软件！您只能在遵守授权协议前提下对程序代码进行修改和使用；不允许对程序代码以任何形式任何目的的再发布。
 * 授权协议: http://www.douphp.com/license.html
 * --------------------------------------------------------------------------------------------------
 * Author: DouCo Co.,Ltd.
 * Release Date: 2024-09-30
 */
define('IN_DOUCO', true);

$_CUR = 'backup';
require (dirname(__FILE__) . '/include/init.php');
$smarty->assign('cur', $_CUR);

// rec操作项的初始化
$rec = $check->is_rec($_REQUEST['rec']) ? $_REQUEST['rec'] : 'default';

// 初始化
require (ROOT_PATH . ADMIN_PATH . '/include/backup.class.php');
$backup = new Backup();
@ set_time_limit(0);

// 赋值给模板
$smarty->assign('rec', $rec);

/**
 * +----------------------------------------------------------
 * 数据备份
 * +----------------------------------------------------------
 */
if ($rec == 'default') {
    $smarty->assign('ur_here', $_LANG['backup']);
    $smarty->assign('action_link', array (
            'text' => $_LANG['backup_restore'],
            'href' => 'backup.php?rec=restore' 
    ));
    
    $query = $dou->query("SHOW TABLE STATUS LIKE '" . $prefix . "%'");
    while ($table = $dou->fetch_array($query)) {
        $table['checked'] = $table['Engine'] == 'MyISAM' ? ' ' : 'disabled';
        $totalsize += $table['Data_length'] + $table['Index_length'];
        
        if ($table['Data_length'] > 10240) {
            $table['Data_length'] = ceil($table['Data_length'] / 1024) . "KB";
        }
        $tables[] = $table;
    }
    $totalsize = ceil($totalsize / 1024);
    
    // 根据时间生成备份文件名
    $filename = 'D' . date('Ymd') . 'T' . date('His');
    
    // CSRF防御令牌生成
    $smarty->assign('token', $token = $firewall->get_token());
    
    // 初始化数据
    $smarty->assign('tables', $tables);
    $smarty->assign('totalsize', $totalsize);
    $smarty->assign('filename', $filename);
    
    $smarty->display('backup.htm');
}

/**
 * +----------------------------------------------------------
 * 将备份写入SQL文件
 * +----------------------------------------------------------
 */
if ($rec == 'backup') {
    $act = $check->is_rec($_REQUEST['act']) ? $_REQUEST['act'] : 'select'; // 备份所选还是全部，全部为all
    $restore_filename = $_REQUEST['restore_filename']; // // 如果携带一个待恢复文件名，就会在备份完成后进入安全恢复阶段
    $back = $_REQUEST['back'] ? $_REQUEST['back'] : 'backup.php?rec=restore'; // 备份所选还是全部，全部为all
    
    // 判断是否是压缩包，选择是否备份资源目录
    if ($dou->file_info($restore_filename, 'ext') == 'zip') {
        $asset = 'yes'; // 如果是压缩包，默认就是要备份资源目录
    } else {
        $asset = $check->is_rec($_REQUEST['asset']) ? $_REQUEST['asset'] : 'no';
    }
    
    $volid = isset($_REQUEST['volid']) ? $_REQUEST['volid'] : 1; // 分卷ID
    $vol_size = $_REQUEST['vol_size'] ? $_REQUEST['vol_size'] : 2048; // 每个分卷文件大小
 
    if ($act == 'all') {
        $query = $dou->query("SHOW TABLE STATUS LIKE '" . $prefix . "%'");
        while ($table = $dou->fetch_array($query)) {
            $totalsize += $table['Data_length'] + $table['Index_length'];
            $tables[] = $table['Name'];
        }
        $totalsize = ceil($totalsize / 1024);
        $filename = 'AUTO_BACKUP_' . date('Ym');
    } else {
        $tables = $_REQUEST['tables']; // 选择的表
        $totalsize = $_REQUEST['totalsize']; // 所选数据库大小
        $filename = $_REQUEST['filename']; // 备份文件名
    }
    
    // 判断备份文件名是否规范
    if (!$backup->is_backup_file($filename . '.sql'))
        $dou->dou_msg($_LANG['backup_filename_not_valid'], 'backup.php');
 
    // 原始文件名，主要是在导入ZIP格式时，导入文件名会发生变化
    $showname = $backup->is_backup_file($_REQUEST['showname']) ? $_REQUEST['showname'] : $filename . ($act == 'asset' ? '.zip' : '.sql');
        
    // CSRF防御令牌验证
    $firewall->check_token($_REQUEST['token']);
    
    // 根据分卷执行操作（没有分卷$volid默认值为1,有分卷则$volid代表分卷1）
    if ($volid == 1 && $tables) { // 分卷为1或者没有分卷要验证从FORM也没是否有提交数据表列表信息过来，然后将其写入缓存文件
        if (!isset($tables) || !is_array($tables)) {
            $dou->dou_msg($_LANG['backup_no_select'], 'backup.php');
        }
        $cache_file = ROOT_PATH . 'data/backup/tables.php';
        $content = "<?php\r\n";
        $content .= "\$data = " . var_export($tables, true) . ";\r\n";
        $content .= "?>";
        file_put_contents($cache_file, $content, LOCK_EX);
    } else { // 遍历到分卷2开始就是从tables.php文件中获取数据表列表信息
        include ROOT_PATH . 'data/backup/tables.php';
        $tables = $data;
        if (!$tables) {
            $dou->dou_msg($_LANG['backup_no_select'], 'backup.php');
        }
    }
    
    if ($dou->version() > '4.1' && $dou->charset) {
        $dou->query("SET NAMES '" . $dou->charset . "';\n\n");
    }
    
    $sqldump = '';
    $tableid = isset($_REQUEST['tableid']) ? $_REQUEST['tableid'] - 1 : 0;
    $startfrom = isset($_REQUEST['startfrom']) ? intval($_REQUEST['startfrom']) : 0;
    $tablenumber = count($tables);
    
    for($i = $tableid; $i < $tablenumber && strlen($sqldump) < $vol_size * 1024; $i++) {
        $sqldump .= $backup->sql_dumptable($tables[$i], $vol_size, $startfrom, strlen($sqldump));
        $startfrom = 0;
    }
    
    if (trim($sqldump)) { // 当前执行的分卷存在内容
        $sqldump = "-- DouPHP v1.x SQL Dump Program\n" . "-- " . ROOT_URL . "\n" . "-- \n" . "-- DATE : " . date('Y-m-d H:i:s') . "\n" . "-- MYSQL SERVER VERSION : " . $dou->version() . "\n" . "-- PHP VERSION : " . PHP_VERSION . "\n" . "-- DouPHP VERSION : " . $_CFG['douphp_version'] . "\n\n" . $sqldump;
        
        $tableid = $i;
        
        // 用于保存的文件名
        $sql_filename = $filename . '_' . $volid . '.sql';
     
        // 显示的文件名
        if ($totalsize < $vol_size) {
            $action_filename = $filename . '.sql';
        } else {
            $action_filename = $filename . '_' . $volid . '.sql';
        }
        
        $volid++;
        
        $bakfile = ROOT_PATH . '/data/backup/' . $sql_filename;
        
        if (!is_writable(ROOT_PATH . '/data/backup/'))
            $dou->dou_msg($_LANG['backup_no_save'], 'backup.php');
        
        file_put_contents($bakfile, $sqldump);
        @chmod($bakfile, 0777);
        
        $_LANG['backup_file_success'] = preg_replace('/d%/Ums', $action_filename, $_LANG['backup_file_success']);
        $dou->dou_msg($_LANG['backup_file_success'], 'backup.php?rec=backup&act=' . $act . '&asset=' . $asset . '&vol_size=' . $vol_size . '&totalsize=' . $totalsize . '&filename=' . $filename . '&token=' . $_REQUEST['token'] . '&tableid=' . $tableid . '&volid=' . $volid . '&startfrom=' . $startrow . '&showname=' . $showname . '&restore_filename=' . $restore_filename . '&back=' . $back, '', 1);
    } else { // 当前执行分卷不存在内容，则代表已经备份完成
        if ($volid == 2) // 当volid为2时就已经跳出循环说明这次备份没有分卷
            @rename(ROOT_PATH . '/data/backup/' . $filename . '_1.sql', ROOT_PATH . '/data/backup/' . $filename . '.sql');
        
        @unlink(ROOT_PATH . 'data/backup/tables.php');
     
        // 备份images资源目录
        if ($asset == 'yes') {
            include_once (ROOT_PATH . ADMIN_PATH . '/include/pclzip.class.php');

            // 压缩文件名，要包含路径信息，不包含就只直接压缩到被压缩文件所在目录
            $zipfile = ROOT_PATH . 'data/backup/' . $filename . '.zip';
            $zip = new PclZip($zipfile);

            // 查找分卷
            $vol_file_list = glob(ROOT_PATH . 'data/backup/' . $filename . '_*.sql');

            if ($vol_file_list) {
                foreach ($vol_file_list as $vol_file) {
                    $filelist[] = $vol_file;
                }
            } else {
                $filelist[] = ROOT_PATH . 'data/backup/' . $filename . '.sql';
            }

            $filelist[] = ROOT_PATH . 'images';

            // 待压缩文件，一个或几个文件名或者文件夹的数组，比如：array('file.txt', 'data/text.txt', 'folder')；单个文件名或者文件夹的字符串，比如：file.txt；逗号分离的多个文件名或者文件夹的字符串，比如：file.txt,data/text.txt,folder
            // PCLZIP_OPT_REMOVE_PATH 表示删除压缩文件中指定路径，这里默认就是删除当前站点的系统根路径
            $response = $zip->create($filelist, PCLZIP_OPT_REMOVE_PATH, ROOT_PATH);

            if ($response == 0) {
                die("Error : " . $zip->errorInfo(true));
            } else {
                if ($vol_file_list) {
                    foreach ($vol_file_list as $vol_file) {
                        @unlink($vol_file);
                    }
                } else {
                    @unlink(ROOT_PATH . 'data/backup/' . $filename . '.sql');
                }
            }
        }

        $dou->create_admin_log($_LANG['backup'] . ': ' . $showname);
     
        // 如果携带一个待恢复文件名，就会在备份完成后进入安全恢复阶段
        if ($restore_filename) {
            $token = $firewall->get_token();
            $dou->dou_msg($_LANG['backup_success_restore_auto'], "backup.php?rec=import&sql_filename=$restore_filename&token=$token");
        }

        if ($asset == 'yes') {
            $dou->dou_msg($_LANG['backup_file_zip_success'], $back);
        } else {
            $dou->dou_msg($_LANG['backup_success'], $back);
        }
    }
    
}

/**
 * +----------------------------------------------------------
 * 恢复备份列表
 * +----------------------------------------------------------
 */
if ($rec == 'restore') {
    $smarty->assign('ur_here', $_LANG['backup_restore']);
    $smarty->assign('action_link', array (
            'text' => $_LANG['backup'],
            'href' => 'backup.php' 
    ));
    
    $backup_file_list = glob(ROOT_PATH . 'data/backup/*.{sql,zip}', GLOB_BRACE);
    $backup_file_list = $dou->get_filelist_by_time($backup_file_list);
    
    // 隐藏掉分卷文件
    foreach ($backup_file_list as $row) {
        if ($row['number'] <= 1 || !$row['number']) {
            // 如果有分卷，这显示分卷1，并去掉分卷1的尾巴
            if ($row['number'] == 1) {
                $showname = preg_replace('/_([0-9])+/Ums', '', $row['showname']);
                
                // 计算当前备份有几个分卷
                $vol_file_list = glob(ROOT_PATH . 'data/backup/' . $dou->file_info($showname, 'name') . '_*.' . $row['ext']);
                $vol_file_number = count($vol_file_list);
                
                $row['showname'] = $showname;
                $row['vol_number'] = $vol_file_number;
            } else {
                $row['vol_number'] = 1;
            }
            
            $file_list[] = $row;
        }
    }
    
    // CSRF防御令牌生成
    $smarty->assign('token', $token = $firewall->get_token());
    
    $smarty->assign('file_list', $file_list);
    $smarty->display('backup.htm');
}

/**
 * +----------------------------------------------------------
 * 恢复备份
 * +----------------------------------------------------------
 */
if ($rec == 'import') {
    // 验证并获取合法的备份文件名
    $sql_filename = $backup->is_backup_file($_REQUEST['sql_filename']) ? $_REQUEST['sql_filename'] : $dou->dou_msg($_LANG['backup_filename_not_valid'], 'backup.php?rec=restore');
 
    // 原始文件名，主要是在导入ZIP格式时，导入文件名会发生变化
    $showname = $backup->is_backup_file($_REQUEST['showname']) ? $_REQUEST['showname'] : preg_replace('/_([0-9])+/Ums', '', $sql_filename);
    
    // 判断是否是压缩包
    if ($dou->file_info($sql_filename, 'ext') == 'zip') {
        if ($dou->file_unzip(ROOT_PATH . 'data/backup/' . $sql_filename, ROOT_PATH)) {
            $name = $dou->file_info($sql_filename, 'name');
            $vol_file_list = glob(ROOT_PATH . 'data/backup/' . $name . '_*.sql');
            
            // 如果存在分卷，要将首次恢复的数据库文件名改成分卷1文件名
            if ($vol_file_list) {
                $sql_filename = $name . '_1.sql';
            } else {
                $sql_filename = $name . '.sql';
            }
         
            $dou->dou_msg($showname . $_LANG['backup_file_unzip_success'] . $sql_filename, 'backup.php?rec=import&sql_filename=' . $sql_filename . '&showname=' . $showname . '&token=' . $_REQUEST['token']);
        }
    }
        
    // CSRF防御令牌验证
    $firewall->check_token($_REQUEST['token']);
    
    // 判断备份文件名是否是分卷的格式
    preg_match('/(.*)_([0-9])+\.sql$/', $sql_filename, $match);
    
    // 判断是否有分卷
    if ($match) {
        $volid = $_REQUEST['volid'] ? $_REQUEST['volid'] : 1;
        $sql_filename = $match['1'] . "_" . $volid . ".sql";
    }
    
    $restore_now = preg_replace('/d%/Ums', $sql_filename, $_LANG['backup_restore_now']);
    
    $file_path = ROOT_PATH . 'data/backup/' . $sql_filename;
    
    // 判断是否有分卷
    if ($match) {
        // 判断SQL文件是否存在
        if (file_exists($file_path)) {
            $sql = file_get_contents($file_path);
            $dou->fn_execute($sql);
            
            // 递增查找分卷直到无后续分卷，则结束查找并且将恢复操作设定为完成
            $volid++;
            $dou->dou_msg($restore_now, 'backup.php?rec=' . $rec . '&sql_filename=' . $sql_filename . '&showname=' . $showname . '&volid=' . $volid . '&token=' . $_REQUEST['token']);
        } else {
            // 判断是否是压缩包
            if ($dou->file_info($showname, 'ext') == 'zip') {
                $name = $dou->file_info($showname, 'name');
                $vol_file_list = glob(ROOT_PATH . 'data/backup/' . $dou->file_info($showname, 'name') . '_*.sql');

                if ($vol_file_list) {
                    foreach ($vol_file_list as $vol_file) {
                        @unlink($vol_file);
                    }
                } else {
                    @unlink(ROOT_PATH . 'data/backup/' . $name . '.sql');
                }
            }

            $dou->create_admin_log($_LANG['backup_restore'] . ': ' . $showname);
            $dou->dou_msg($_LANG['backup_restore_success'], 'backup.php?rec=restore');
        }
    } else {
        $sql = file_get_contents($file_path);
        $dou->fn_execute($sql);
     
        // 判断是否是压缩包
        if ($dou->file_info($showname, 'ext') == 'zip') {
            @unlink(ROOT_PATH . 'data/backup/' . $dou->file_info($showname, 'name') . '.sql');
        }
        
        $dou->create_admin_log($_LANG['backup_restore'] . ': ' . $showname);
        $dou->dou_msg($_LANG['backup_restore_success'], 'backup.php?rec=restore');
    }
}

/**
 * +----------------------------------------------------------
 * 备份删除
 * +----------------------------------------------------------
 */
if ($rec == 'del') {
    // 验证并获取合法的备份文件名
    $sql_filename = $backup->is_backup_file($_REQUEST['sql_filename']) ? $_REQUEST['sql_filename'] : $dou->dou_msg($_LANG['backup_filename_not_valid'], 'backup.php');
    
    if (isset($_POST['confirm'])) {
        if (file_exists(ROOT_PATH . 'data/backup/' . $sql_filename)) {
            @unlink(ROOT_PATH . 'data/backup/' . $sql_filename);
        }
        
        preg_match('/(.*)_([0-9])+\.sql$/', $sql_filename, $match);
        
        // 如果存在分卷则将分卷删除
        if ($match) {
            $sqlfiles = glob(ROOT_PATH . 'data/backup/' . $match['1'] . '_*.sql');
            
            $sql_filename .= ' ' . $_LANG['backup_vol_include'] . ' : ';
            
            foreach ($sqlfiles as $id => $sqlfile) {
                if (file_exists(ROOT_PATH . 'data/backup/' . basename($sqlfile))) {
                    @unlink(ROOT_PATH . 'data/backup/' . basename($sqlfile));
                }
                
                $sql_filename .= basename($sqlfile) . ',';
            }
        }
        
        $dou->create_admin_log($_LANG['backup_del'] . ': ' . $sql_filename);
        $dou->dou_msg(preg_replace('/d%/Ums', $sql_filename, $_LANG['backup_del_success']), 'backup.php?rec=restore');
    } else {
        $_LANG['del_check'] = preg_replace('/d%/Ums', $sql_filename, $_LANG['del_check']);
        $dou->dou_msg($_LANG['del_check'], 'backup.php?rec=restore', '', '30', "backup.php?rec=del&sql_filename=$sql_filename");
    }
}

/**
 * +----------------------------------------------------------
 * 备份下载
 * +----------------------------------------------------------
 */
if ($rec == 'down') {
    // 验证并获取合法的备份文件名
    $sql_filename = $backup->is_backup_file($_REQUEST['sql_filename']) ? $_REQUEST['sql_filename'] : $dou->dou_msg($_LANG['backup_filename_not_valid'], 'backup.php');
    
    ob_clean();
    if ($fp = @ fopen(ROOT_PATH . 'data/backup/' . $sql_filename, 'r')) {
        header("Content-type: application/zip");
        header("Content-Disposition: attachment; filename=" . $sql_filename);
        header("Accept-Ranges: bytes");
        header("Content-Length:" . filesize(ROOT_PATH . 'data/backup/' . $sql_filename));
        header('Content-transfer-encoding: binary');
        while (!@ feof($fp))
            echo fread($fp, 10240);
    }
}

?>