<?php
/**
 * The model file of backup module of ZenTaoPMS.
 *
 * @copyright   Copyright 2009-2023 禅道软件（青岛）有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
 * @license     ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
 * @author      Yidong Wang <yidong@cnezsoft.com>
 * @package     backup
 * @version     $Id$
 * @link        https://www.zentao.net
 */
class backupModel extends model
{
    /**
     * Backup SQL.
     *
     * @param  string    $backupFile
     * @access public
     * @return object
     */
    public function backSQL($backupFile)
    {
        $zdb = $this->app->loadClass('zdb');
        return $zdb->dump($backupFile);
    }

    /**
     * Backup file.
     *
     * @param  string    $backupFile
     * @access public
     * @return object
     */
    public function backFile($backupFile)
    {
        $zfile  = $this->app->loadClass('zfile');

        $return = new stdclass();
        $return->result = true;
        $return->error  = '';

        if(!is_dir($backupFile)) mkdir($backupFile, 0777, true);

        $tmpLogFile = $this->getTmpLogFile($backupFile);
        $dataDir    = $this->app->getAppRoot() . 'www/data/';
        $count      = $zfile->getCount($dataDir, array('course'));
        file_put_contents($tmpLogFile, json_encode(array('allCount' => $count)));

        $result = $zfile->copyDir($dataDir, $backupFile, $logLevel = false, $tmpLogFile, array('course'));
        $this->processSummary($backupFile, $result['count'], $result['size'], $result['errorFiles'], $count);
        unlink($tmpLogFile);

        return $return;
    }

    /**
     * Backup code.
     *
     * @param  string    $backupFile
     * @access public
     * @return object
     */
    public function backCode($backupFile)
    {
        $zfile  = $this->app->loadClass('zfile');
        $return = new stdclass();
        $return->result = true;
        $return->error  = '';

        $tmpLogFile  = $this->getTmpLogFile($backupFile);
        $appRoot     = $this->app->getAppRoot();
        $fileList    = glob($appRoot . '*');
        $wwwFileList = glob($appRoot . 'www/*');

        $tmpFile  = array_search($appRoot . 'tmp', $fileList);
        $wwwFile  = array_search($appRoot . 'www', $fileList);
        $dataFile = array_search($appRoot . 'www/data', $wwwFileList);
        unset($fileList[$tmpFile]);
        unset($fileList[$wwwFile]);
        unset($wwwFileList[$dataFile]);

        $fileList = array_merge($fileList, $wwwFileList);

        if(!is_dir($backupFile)) mkdir($backupFile, 0777, true);

        $allCount = 0;
        foreach($fileList as $codeFile) $allCount += $zfile->getCount($codeFile);
        file_put_contents($tmpLogFile, json_encode(array('allCount' => $allCount)));

        $copiedCount = 0;
        $copiedSize  = 0;
        $errorFiles  = array();
        foreach($fileList as $codeFile)
        {
            $file = trim(str_replace($appRoot, '', $codeFile), DS);
            if(is_dir($codeFile))
            {
                if(!is_dir($backupFile . DS . $file)) mkdir($backupFile . DS . $file, 0777, true);
                $result = $zfile->copyDir($codeFile, $backupFile . DS . $file, $logLevel = false, $tmpLogFile);
                $copiedCount += $result['count'];
                $copiedSize  += $result['size'];
                $errorFiles  += $result['errorFiles'];
            }
            else
            {
                $dirName = dirname($file);
                if(!is_dir($backupFile . DS . $dirName)) mkdir($backupFile . DS . $dirName, 0777, true);
                if($zfile->copyFile($codeFile, $backupFile . DS . $file))
                {
                    $copiedCount += 1;
                    $copiedSize  += filesize($codeFile);
                }
                else
                {
                    $errorFiles[] = $codeFile;
                }
            }
        }

        $this->processSummary($backupFile, $copiedCount, $copiedSize, $errorFiles, $allCount);
        unlink($tmpLogFile);

        return $return;
    }

    /**
     * Restore SQL.
     *
     * @param  string    $backupFile
     * @access public
     * @return object
     */
    public function restoreSQL($backupFile)
    {
        $zdb       = $this->app->loadClass('zdb');
        $allTables = $zdb->getAllTables();
        foreach($allTables as $tableName => $tableType)
        {
            try
            {
                $this->dbh->query("DROP $tableType IF EXISTS `$tableName`");
            }
            catch(PDOException $e){}
        }

        $this->dao->clearCache();

        return $zdb->import($backupFile);
    }

    /**
     * Restore File.
     *
     * @param  string    $backupFile
     * @access public
     * @return object
     */
    public function restoreFile($backupFile)
    {
        $return = new stdclass();
        $return->result = true;
        $return->error  = '';

        if(is_dir($backupFile))
        {
            $zfile = $this->app->loadClass('zfile');
            $zfile->copyDir($backupFile, $this->app->getAppRoot() . 'www/data/', $showDetails = false);
        }

        return $return;
    }

    /**
     * Add file header.
     *
     * @param  string    $fileName
     * @access public
     * @return bool
     */
    public function addFileHeader($fileName)
    {
        $die     = "<?php die();?" . ">\n";
        $tmpFile = $fileName . '.tmp';
        rename($fileName, $tmpFile);

        file_put_contents($fileName, $die);
        $fh     = fopen($tmpFile, 'r');
        $length = 2 * 1024 * 1024;
        while(!feof($fh))
        {
            $buff = fread($fh, $length);
            file_put_contents($fileName, $buff, FILE_APPEND);
        }
        fclose($fh);
        unlink($tmpFile);

        return true;
    }

    /**
     * Remove file header.
     *
     * @param  string    $fileName
     * @access public
     * @return bool
     */
    public function removeFileHeader($fileName)
    {
        $tmpFile = $fileName . '.tmp';
        $fh      = fopen($fileName, 'r');
        $length  = 2 * 1024 * 1024;
        $readedFirstLine = false;
        while(!feof($fh))
        {
            if(!$readedFirstLine)
            {
                fgets($fh);
                $readedFirstLine = true;
                continue;
            }

            $buff = fread($fh, $length);
            file_put_contents($tmpFile, $buff, FILE_APPEND);
        }
        fclose($fh);
        rename($tmpFile, $fileName);

        return true;
    }

    /**
     * Get dir size.
     *
     * @param  string    $backup
     * @access public
     * @return array
     */
    public function getBackupSummary($backup)
    {
        $zfile = $this->app->loadClass('zfile');
        if(is_file($backup))
        {
            $summary = array();
            $summary['allCount'] = 1;
            $summary['count']    = 1;
            $summary['size']     = $zfile->getFileSize($backup);

            return $summary;
        }

        $summaryFile = dirname($backup) . DS . 'summary';
        if(!file_exists($summaryFile)) return array();

        $summary = json_decode(file_get_contents(dirname($backup) . DS . 'summary'), true);
        return zget($summary, basename($backup), array());
    }

    /**
     * Get backup path.
     *
     * @access public
     * @return string
     */
    public function getBackupPath()
    {
        $backupPath = empty($this->config->backup->settingDir) ? $this->app->getTmpRoot() . 'backup' . DS : $this->config->backup->settingDir;
        return rtrim(str_replace(DS, '/', $backupPath), '/') . '/';
    }

    /**
     * Get backup file.
     *
     * @param  string  $name
     * @param  string  $type   sql|file|code
     * @access public
     * @return string|false
     */
    public function getBackupFile($name, $type)
    {
        $backupPath = $this->getBackupPath();
        if($type == 'sql')
        {
            if(file_exists($backupPath . $name . ".{$type}"))     return $backupPath . $name . ".{$type}";
            if(file_exists($backupPath . $name . ".{$type}.php")) return $backupPath . $name . ".{$type}.php";
        }

        if(file_exists($backupPath . $name . ".{$type}")) return $backupPath . $name . ".{$type}";

        return false;
    }

    /**
     * Get tmp log file.
     *
     * @param  string $backupFile
     * @access public
     * @return string
     */
    public function getTmpLogFile($backupFile)
    {
        return $backupFile . '.tmp.summary';
    }

    /**
     * Get backup dir progress.
     *
     * @param  string $backup
     * @access public
     * @return array
     */
    public function getBackupDirProgress($backup)
    {
        $tmpLogFile = $this->getTmpLogFile($backup);
        if(file_exists($tmpLogFile))
        {
            $log = json_decode(file_get_contents($tmpLogFile), true);
            return empty($log) ? array() : $log;
        }
        return array('allCount' => 0, 'count' => 0);
    }

    /**
     * Process filesize.
     *
     * @param  int    $fileSize
     * @access public
     * @return string
     */
    public function processFileSize($fileSize)
    {
        $bit = 'KB';
        $fileSize = round($fileSize / 1024, 2);
        if($fileSize >= 1024)
        {
            $bit = 'MB';
            $fileSize = round($fileSize / 1024, 2);
        }
        if($fileSize >= 1024)
        {
            $bit = 'GB';
            $fileSize = round($fileSize / 1024, 2);
        }

        return $fileSize . $bit;
    }

    /**
     * Process backup summary.
     *
     * @param  string $file
     * @param  int    $count
     * @param  int    $size
     * @param  array  $errorFiles
     * @param  int    $allCount
     * @param  string $action  add|delete
     * @access public
     * @return bool
     */
    public function processSummary($file, $count, $size, $errorFiles = array(), $allCount = 0, $action = 'add')
    {
        $backupPath = dirname($file);
        $fileName   = basename($file);

        $summaryFile = $backupPath . DS . 'summary';
        if(!file_exists($summaryFile) and !touch($summaryFile)) return false;

        $summary = json_decode(file_get_contents($summaryFile), true);
        if(empty($summary)) $summary = array();

        if($action == 'add')
        {
            $summary[$fileName]['allCount']   = $allCount;
            $summary[$fileName]['errorFiles'] = $errorFiles;
            $summary[$fileName]['count']      = $count;
            $summary[$fileName]['size']       = $size;
        }
        else
        {
            unset($summary[$fileName]);
        }

        if(file_put_contents($summaryFile, json_encode($summary))) return true;
        return false;
    }

    /**
     * Get disk space.
     *
     * @param  string $backupPath
     * @access public
     * @return string
     */
    public function getDiskSpace($backupPath)
    {
        $nofile        = strpos($this->config->backup->setting, 'nofile') !== false;
        $diskFreeSpace = disk_free_space($backupPath);
        $backFileSize  = 0;

        if(!$nofile)
        {
            $appRoot      = $this->app->getAppRoot();
            $appRootSize  = $this->getDirSize($appRoot);
            $backFileSize = $appRootSize - $this->getDirSize($appRoot . 'tmp') - $this->getDirSize($appRoot . 'www/data/course');
        }

        $backSqlSize = $this->dao->select('sum(data_length+index_length) as size')
            ->from('information_schema.tables')
            ->where('TABLE_SCHEMA')->eq($this->config->db->name)
            ->groupBy('TABLE_SCHEMA')
            ->fetch('size');

        return $diskFreeSpace . ',' . ($backFileSize + $backSqlSize);
    }

    /**
     * Get directory size.
     *
     * @param  string $dir
     * @access public
     * @return int
     */
    public function getDirSize($dir)
    {
        if(!file_exists($dir)) return 0;
        if(!is_readable($dir)) return 0;
        $totalSize = 0;
        $iterator  = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::LEAVES_ONLY, RecursiveIteratorIterator::CATCH_GET_CHILD);

        foreach($iterator as $file) $totalSize += $file->getSize();
        return $totalSize;
    }
}
