<?php

$cms['modules']['update.mod.php'] = array(
    'name' => __('Updates', 'update.mod.php'),
    'description' => __('Update module', 'update.mod.php'),
    'url_title' => __('Module page'),
    'url_link' => __('https://coffee-cms.com/en/modules/update/'),
    'author' => __('Coffee CMS team'),
    'version' => '21.06',
    'files' => array(
        '.cms/mod/update.mod.php',
        '.cms/js/update.js',
        '.cms/lang/ru_RU.UTF-8/update.mod.php',
    ),
    'sort'    => 9999,
);

// Return if module disabled
if ( isset($cms['config']['update.mod.php']['disabled'])
&& $cms['config']['update.mod.php']['disabled'] === true ) return;

if ( is_admin() ) {
    cms_add_function('admin', 'cms_update_admin');
    cms_add_function('admin_header', 'cms_update_admin_header');
    cms_add_function('api', 'cms_update_api');
}

function cms_update_admin_header() {
    global $cms;
    if ( ! is_admin() ) { return; }
    echo "<script src='{$cms['base_path']}js/update.js'></script>";
}

function cms_update_admin() {
    global $cms;
    if (!is_admin()) return;

    $cms_files_backup   = __("CMS files backup", "update.mod.php");
    $create_filelist    = __("Create Filelist", "update.mod.php");
    $create_zip         = __("Create Zip", "update.mod.php");
    $create_backup      = __("Create", "update.mod.php");
    $remove_backup      = __("Remove", "update.mod.php");

    $updates            = __("Updates", "update.mod.php");
    $update             = __("Update", "update.mod.php");
    $dev_update         = __("Dev Update", "update.mod.php");
    $check_update       = __("Check", "update.mod.php");
    $check_dev_update   = __("Check Dev", "update.mod.php");

    $developers_buttons = __("Developers Buttons", "update.mod.php");
    
    $backup_link = ""; // prevent warning
    foreach( glob( $cms['site_dir']."/revert_to_*.php" ) as $file ) {
        $file = preg_replace( "/.*\//", "", $file );
        $backup_link = "<p>".__("Rollback URL:", "update.mod.php")." {$cms["url"]["scheme"]}://{$cms["url"]["host"]}{$cms['base_path']}{$file}</p>";
    }

    if ( empty( $cms['config']['update.mod.php']['update']['last'] ) ) {
        $last_version = __("Unknown", "update.mod.php");
    } else {
        $last_version = $cms['config']['update.mod.php']['update']['last'];
    }
    if ( empty( $cms['config']['update.mod.php']['last_check'] ) ) {
        $last_check = __("Never", "update.mod.php");
    } else {
        $last_check = strftime("%c", $cms['config']['update.mod.php']['last_check']);
    }
    $update_info  = "<p>".__("Current version:", "update.mod.php")." {$cms['modules']['update.mod.php']['version']}</p>";
    $update_info .= "<p>".__("Last version:", "update.mod.php")." {$last_version}</p>";
    $update_info .= "<p>".__("Last check:", "update.mod.php")." {$last_check}</p>";

    $page = <<<EOF
<div class="main-header"></div>
<div class="main-main">

    <div class='info'>
        <h1>{$cms_files_backup}</h1>
        <p>
            <button class="button-save" data-cms-update data-fn="create_backup">{$create_backup}</button>
            <button class="button-save" data-cms-update data-fn="remove_backup">{$remove_backup}</button>
        </p>
        <div data-create_backup-answer data-remove_backup-answer>{$backup_link}</div>
    </div>

    <div class='info'>
        <h1>{$updates}</h1>
        <p>
            <button class="button-save" data-cms-update data-fn="cms_check_update">{$check_update}</button>
            <button class="button-save" data-cms-update data-fn="cms_update">{$update}</button>
        </p>
        <div data-cms_check_update-answer data-cms_check_dev_update-answer>{$update_info}</div>
        <div data-cms_update-answer></div>
        <div data-cms_dev_update-answer></div>
    </div>

    <div class='info developers_only'>
        <h1>{$developers_buttons}</h1>
        <button class="button-save" data-cms-update data-fn="create_filelist">{$create_filelist}</button>
        <button class="button-save" data-cms-update data-fn="create_zip">{$create_zip}</button>
        <button class="button-save" data-cms-update data-fn="cms_check_dev_update">{$check_dev_update}</button>
        <div data-create_filelist-answer></div>
        <div data-create_zip-answer></div>
    </div>

</div>
<div class="main-footer"></div>
EOF;

    // Создаем пункт меню, если еще не создан
    if ( empty( $cms['config']['update.mod.php']['menu']['update'] ) ) {
        $cms['config']['update.mod.php']['menu']['update'] = array(
            'title'    => 'Updates',
            'sort'     => 20,
            'class'    => '',
            'section'  => 'Settings',
        );
        cms_save_config();
    }

    $cms['admin_pages']['update'] = $page;

}


function cms_update_api() {
    global $cms;
    if (!is_admin()) return;
    if (empty($cms['base'])) cms_base_connect();

    if (!empty($_POST['fn'])) {

        switch ($_POST['fn']) {

            case "create_filelist":
                $list = cms_update_create_filelist();
                exit( json_encode( array(
                    'info_text'  => __("Filelist created", "update.mod.php"),
                    'info_class' => 'info-success',
                    'info_time'  => 1000,
                    'answer'     => $list,
                ) ) );
            break;

            case "create_zip":
                $list = cms_update_create_zip();
                exit( json_encode( array(
                    'info_text'  => __("Zip created", "update.mod.php"),
                    'info_class' => 'info-success',
                    'info_time'  => 1000,
                    'answer'     => $list,
                ) ) );
            break;

            case "create_backup":
                $answer = cms_update_create_backup();
                exit( json_encode( array(
                    'info_text'  => __("Created", "update.mod.php"),
                    'info_class' => 'info-success',
                    'info_time'  => 3000,
                    'answer'     => $answer,
                ) ) );
            break;

            case "cms_check_update":
                cms_update_check( "update.json" );
            break;

            case "cms_check_dev_update":
                cms_update_check( "update_dev.json" );
            break;

            case "cms_update":
                cms_update_cms_update();
            break;
            
            case "remove_backup":
                $answer = cms_update_remove_backup();
                exit( json_encode( array(
                    'info_text'  => $answer["message"],
                    'info_class' => $answer["class"],
                    'info_time'  => 3000,
                    'answer'     => $answer["logs"],
                    'ok'         => $answer["ok"],
                ) ) );
            break;

        }

    }

}


function cms_update_create_filelist() {
    global $cms;

    $logs = "";

    $queue = array(
        'uploads/.keep',
        '.htaccess',
        '.cms/admin/html.php',
        '.cms/css/admin.cold.dark.css',
        '.cms/css/admin.dark.dark.css',
        '.cms/css/admin.modern.dark.css',
        '.cms/css/admin.warm.default.css',
        '.cms/data/cms_filelist.php',
        '.cms/fonts/open-sans-400.woff2',
        '.cms/fonts/open-sans-600.woff2',
        '.cms/fonts/source-code-pro-400.woff2',
        '.cms/fonts/source-code-pro-600.woff2',
        '.cms/img/check.svg',
        '.cms/img/favicon.png',
        '.cms/img/icon.default.svg',
        '.cms/img/icon.doc.svg',
        '.cms/img/icon.docx.svg',
        '.cms/img/icon.exe.svg',
        '.cms/img/icon.flac.svg',
        '.cms/img/icon.m4a.svg',
        '.cms/img/icon.mkv.svg',
        '.cms/img/icon.mp3.svg',
        '.cms/img/icon.mp4.svg',
        '.cms/img/icon.ogg.svg',
        '.cms/img/icon.pdf.svg',
        '.cms/img/icon.rar.svg',
        '.cms/img/icon.xls.svg',
        '.cms/img/icon.xlsx.svg',
        '.cms/img/icon.zip.svg',
        '.cms/img/logo.svg',
        '.cms/js/admin.js',
        '.cms/js/jquery.min.js',
        '.cms/js/jquery.form.min.js',
        '.cms/js/menu.js',
        '.cms/js/pages.js',
        '.cms/js/update.js',
        '.cms/lang',
        '.cms/lib/codemirror',
        '.cms/mini',
        '.cms/mod/admin.mod.php',
        '.cms/mod/base.mod.php',
        '.cms/mod/menu.mod.php',
        '.cms/mod/pages.mod.php',
        '.cms/mod/sitemap.mod.php',
        '.cms/mod/template.mod.php',
        '.cms/mod/update.mod.php',
        '.cms/index.fn.php',
        '.cms/index.php',
        '.cms/license.txt',
    );
    $list  = array();

    while ( $cur = array_shift( $queue ) ) {
        if ( substr( $cur, 0, 1 ) !== '/' ) {
            $cur = $cms['site_dir'].'/'.$cur;
        }
        if ( is_dir( $cur ) ) {
            $queue = array_merge( $queue, glob( $cur . "/*" ) );
        } elseif ( is_file( $cur ) ) {
            $sha1 = sha1_file($cur);
            $size = filesize($cur);
            $file = str_replace( $cms['site_dir'].'/', '', $cur );
            $list[$file] = array(
                'sha1' => $sha1,
                'size' => $size,
            );
        } else {
            $logs .= "<p>Нет файла или каталога '{$cur}'</p>";
        }
    }
    
    file_put_contents( $cms['cms_dir'].'/data/cms_filelist.php', '<?php $list = '.var_export( $list, true ).';' );

    $count = count( $list );

    $logs .= "<p>".__("Files:", "update.mod.php")." {$count}</p>";

    return $logs;
    
}

function cms_update_create_backup() {
    global $cms;
    require( $cms['cms_dir']."/data/cms_filelist.php" );
    
    $old = $cms['site_dir'].'/.old';
    recurse_rm( $old );
    foreach( glob( $cms['site_dir']."/revert_to_*.php" ) as $file ) {
        unlink( $file );
    }

    if ( !is_dir( $old ) ) {
        mkdir( $old, 0777, true );
    }

    $list[".cms/config.php"] = array();
    $success = true;
    foreach( $list as $fn => $file ) {
        $from = $cms["site_dir"]."/".$fn;
        $to   = $old."/".$fn;
        $dir  = preg_replace( '/\/[^\/]+$/u', '', $to );
        if ( !is_dir( $dir ) ) {
            mkdir( $dir, 0777, true );
        }
        $c = copy( $from, $to );
        $success = $success && $c;
        if (!$success) {
            break;
        }
    }

    if ( !$success ) {
        return __( "Error create backup on file", "update.mod.php" )." {$from}";
    } else {
        $fn = "revert_to_".$cms['modules']['update.mod.php']['version']."_".date("YmdHis").".php";
        file_put_contents( $cms["site_dir"]."/".$fn, <<<EOF
<?php
require("{$cms["site_dir"]}/.old/.cms/data/cms_filelist.php");
\$oldlist = \$list;

require("{$cms["cms_dir"]}/data/cms_filelist.php");
foreach( \$list as \$fn => \$file ) {
    \$from = "{$cms["site_dir"]}/{\$fn}";
    unlink( \$from );
}

\$success = true;
foreach( \$oldlist as \$fn => \$file ) {
    \$from = "{$cms["site_dir"]}/.old/{\$fn}";
    \$to   = "{$cms["site_dir"]}/{\$fn}";
    \$dir  = preg_replace( '/\/[^\/]+$/u', '', \$to );
     if ( !is_dir( \$dir ) ) {
        mkdir( \$dir, 0777, true );
    }
    \$c = copy( \$from, \$to );
    \$success = \$success && \$c;
}
if ( \$success ) {
    echo "success";
    unlink(__FILE__);
    recurse_rm("{$cms["site_dir"]}/.old");
}

function recurse_rm(\$src) /* bool */ {
    if ( !file_exists( \$src ) ) {
        return false;
    }
    \$dir = opendir( \$src );
    while( false !== ( \$file = readdir( \$dir ) ) ) {
        if ( ( \$file != '.' ) && ( \$file != '..' ) ) {
            if ( is_dir( \$src.'/'.\$file ) ) {
                recurse_rm( \$src.'/'.\$file );
            } else {
                if ( !unlink( \$src.'/'.\$file ) ) {
                    return false;
                }
            }
        }
    }
    closedir( \$dir );
    return rmdir( \$src );
}
EOF
);
        return __("Copy and save rollback URL:", "update.mod.php")." ".$cms["url"]["scheme"]."://".$cms["url"]["host"].$cms['base_path'].$fn;
    }
}

function cms_update_check( $f ) {
    global $cms;
    // prevent warning
    if ( empty( $cms['config']['update.mod.php']['last_check'] ) ) {
        $cms['config']['update.mod.php']['last_check'] = 0;
    }

    $t = time();
    if ( ($t - $cms['config']['update.mod.php']['last_check'] < 10*60) && ($f !== "update_dev.json") ) {
        exit( json_encode( array(
            'info_text'  => __("Try it in 10 minutes", "update.mod.php"),
            'info_class' => 'info-error',
            'info_time'  => 1000,
        ) ) );
    }

    $url = "https://coffee-cms.com/{$f}";
    if ( !$update = file_get_contents( $url ) ) {
        exit( json_encode( array(
            'info_text'  => __("Can't get file", "update.mod.php")." {$url}",
            'info_class' => 'info-error',
            'info_time'  => 1000,
        ) ) );
    }

    if ( !$update = json_decode( $update, true ) ) {
        exit( json_encode( array(
            'info_text'  => __("JSON error", "update.mod.php")." {$url}",
            'info_class' => 'info-error',
            'info_time'  => 1000,
        ) ) );
    }

    $cms['config']['update.mod.php']['last_check'] = $t;
    $cms['config']['update.mod.php']['update'] = $update;
    cms_save_config();

    $update_info  = "<p>".__("Current version:", "update.mod.php")." {$cms['modules']['update.mod.php']['version']}</p>";
    $update_info .= "<p>".__("Last version:", "update.mod.php")." {$cms['config']['update.mod.php']['update']['last']}</p>";
    $update_info .= "<p>".__("Last check:", "update.mod.php")." ".strftime("%c", $cms['config']['update.mod.php']['last_check'])."</p>";

    exit( json_encode( array(
        'info_text'  => __("Ok", "update.mod.php"),
        'info_class' => 'info-success',
        'info_time'  => 1000,
        'answer'     => $update_info,
    ) ) );

}

function cms_update_cms_update() {
    global $cms;

    if ( empty( $cms['config']['update.mod.php']['update'] ) ) {
        exit( json_encode( array(
            'info_text'  => __("Please check updates", "update.mod.php"),
            'info_class' => 'info-error',
            'info_time'  => 1000,
        ) ) );
    }

    $update = $cms['config']['update.mod.php']['update'];

    if ( $update['last'] > $cms['modules']['update.mod.php']['version'] ) {

        if ( empty( $cms['config']['update.mod.php']['last_update'] ) ) {
            $cms['config']['update.mod.php']['last_update'] = 0;
        }

        if ( (time() - $cms['config']['update.mod.php']['last_update'] < 10*60) ) {
            exit( json_encode( array(
                'info_text'  => __("Try it in 10 minutes", "update.mod.php"),
                'info_class' => 'info-error',
                'info_time'  => 1000,
            ) ) );
        }

        // Download New Version
        if ( ! $content = file_get_contents( $update['download'] ) ) {
            exit( json_encode( array(
                'info_text'  => __("Download error", "update.mod.php"),
                'info_class' => 'info-error',
                'info_time'  => 3000,
            ) ) );
        }
        $tmp = $cms['site_dir']."/.tmp";
        if ( is_dir( $tmp ) ) {
            recurse_rm( $tmp );
        }
        if ( ! mkdir( $tmp ) ) {
            exit( json_encode( array(
                'info_text'  => __("Can't create tmp dir", "update.mod.php")." {$tmp}",
                'info_class' => 'info-error',
                'info_time'  => 3000,
            ) ) );
        }
        $fn = preg_replace( "/.*\//", "", $update['download'] );
        $file = $tmp."/".$fn;
        if ( ! file_put_contents( $file, $content ) ) {
            exit( json_encode( array(
                'info_text'  => __("Can't write file", "update.mod.php")." {$file}",
                'info_class' => 'info-error',
                'info_time'  => 3000,
            ) ) );
        }

        // Unpack New Version
        // Object Oriented Style for future compability with PHP 8
        $zip = new ZipArchive;
        if ( $zip->open( $file ) === TRUE ) {
            $zip->extractTo( $tmp );
            $zip->close();
        } else {
            exit( json_encode( array(
                'info_text'  => __("Can't unzip", "update.mod.php")." {$file}",
                'info_class' => 'info-error',
                'info_time'  => 3000,
            ) ) );
        }

        if ( $_SERVER['SERVER_NAME'] === "dev.coffee-cms.com" ) {
            exit( json_encode( array(
                'info_text'  => $_SERVER['SERVER_NAME'],
                'info_class' => 'info-error',
                'info_time'  => 3000,
            ) ) );
        }

        // Load New File List
        $fl = $tmp."/.cms/data/cms_filelist.php";
        if ( ! file_exists( $fl ) ) {
            exit( json_encode( array(
                'info_text'  => __("File list missing", "update.mod.php")." {$fl}",
                'info_class' => 'info-error',
                'info_time'  => 3000,
            ) ) );
        }
        require( $fl );
        $newlist = $list;

        // Check Current Version
        $fl = $cms['cms_dir']."/data/cms_filelist.php";
        if ( ! file_exists( $fl ) ) {
            exit( json_encode( array(
                'info_text'  => __("File list missing", "update.mod.php")." {$fl}",
                'info_class' => 'info-error',
                'info_time'  => 3000,
            ) ) );
        }
        require( $fl );
        foreach( $list as $fn => $file ) {
            $f = $cms["site_dir"]."/".$fn;
            if ( !file_exists( $f ) ) {
                exit( json_encode( array(
                    'info_text'  => __("File not exists:", "update.mod.php")." {$f}",
                    'info_class' => 'info-error',
                    'info_time'  => 3000,
                ) ) );
            }
            $sha1 = sha1_file( $f );
            $size = filesize( $f );
            if ( $fn !== ".cms/data/cms_filelist.php" && $fn !== ".cms/mod/update.mod.php" ) {
                if ( $file["sha1"] !== $sha1 || $file["size"] !== $size ) {
                    exit( json_encode( array(
                        'info_text'  => __("Changed file:", "update.mod.php")." {$f}",
                        'info_class' => 'info-error',
                        'info_time'  => 3000,
                    ) ) );
                }
            }
        }

        // Check Rights
        $rights = true;
        foreach( $list as $fn => $file ) {
            $from = $cms["site_dir"]."/".$fn;
            $r = is_writable( $from );
            $rights = $rights && $r;
            if ( !$rights ) { break; }
        }

        if ( !$rights ) {
            exit( json_encode( array(
                'info_text'  => __("Update error when test current files rights. Please check files rights.", "update.mod.php")." {$from}",
                'info_class' => 'info-error',
                'info_time'  => 3000,
            ) ) );
        }

        // Remove Current Version
        $removed = true;
        foreach( $list as $fn => $file ) {
            $from = $cms["site_dir"]."/".$fn;
            $c = unlink( $from );
            $removed = $removed && $c;
            // try remove dir
            $rdir = preg_replace( '/\/[^\/]+$/u', '', $from );
            @rmdir( $rdir );
        }

        // Move New Version to Current Location
        $moved = true;
        foreach( $newlist as $fn => $file ) {
            $from = $tmp."/".$fn;
            $to   = $cms["site_dir"]."/".$fn;
            $ndir = preg_replace( '/\/[^\/]+$/u', '', $to );
            if ( !is_dir( $ndir ) ) {
                mkdir( $ndir, 0777, true );
            }
            $c = copy( $from, $to );
            $moved = $moved && $c;
        }

        // Remove tmp
        recurse_rm( $tmp );

        if ( !$moved ) {
            exit( json_encode( array(
                'info_text'  => __("Update error when moving new files. Please check files rights.", "update.mod.php"),
                'info_class' => 'info-error',
                'info_time'  => 3000,
            ) ) );
        }

        $cms['config']['update.mod.php']['last_update'] = time();
        cms_save_config();

        cms_clear_cache();
        //cms_create_cache(); 404 после обновления

        exit( json_encode( array(
            'info_text'  => __("Successfull update.", "update.mod.php"),
            'info_class' => 'info-success',
            'info_time'  => 3000,
            'reload'     => true,
        ) ) );
    } else {

        exit( json_encode( array(
            'info_text'  => __("Already updated", "update.mod.php"),
            'info_class' => 'info-success',
            'info_time'  => 3000,
        ) ) );

    }

}

function cms_update_remove_backup() {
    global $cms;

    $ok = true;
    $logs = "";
    
    $old = $cms['site_dir']."/.old";
    if ( file_exists( $old ) ) {
        if ( recurse_rm( $old ) ) {
            $logs .= "<p>".__( "Removed", "update.mod.php" )." {$old}</p>";
        } else {
            $logs .= "<p>".__( "Can't remove", "update.mod.php" )." {$old}</p>";
            $ok = false;
        }
    }
    
    $tmp = $cms['site_dir']."/.tmp";
    if ( file_exists( $tmp ) ) {
        if ( recurse_rm( $tmp ) ) {
            $logs .= "<p>".__( "Removed", "update.mod.php" )." {$tmp}</p>";
        } else {
            $logs .= "<p>".__( "Can't remove", "update.mod.php" )." {$tmp}</p>";
            $ok = false;
        }
    }
    
    foreach( glob( $cms['site_dir']."/revert_to_*.php" ) as $file ) {
        if ( unlink( $file ) ) {
            $logs .= "<p>".__( "Removed", "update.mod.php" )." {$file}</p>";
        } else {
            $logs .= "<p>".__( "Can't remove", "update.mod.php" )." {$file}</p>";
            $ok = false;
        }
    }

    if ( $ok ) {
        $m = "Removed";
        $c = "info-success";
    } else {
        $m = "Have errors";
        $c = "info-error";
    }

    return array(
        "ok" => $ok,
        "logs" => $logs,
        "message" => __( $m, "update.mod.php" ),
        "class" => $c,
    );
}

function cms_update_create_zip() {
    global $cms;

    cms_update_create_filelist();

    require( $cms['cms_dir']."/data/cms_filelist.php" );
    $zip = new ZipArchive();
    $v = date("y.m.dHi");
    $name = "coffee-cms-".$v.".zip";
    $zipname = "/home/www/coffee-cms.com/htdocs/".$name;

    if ( $zip->open( $zipname, ZipArchive::CREATE ) !== true ) {
        return "Can't create {$zipname}";
    }

    $zipped = true;
    foreach( $list as $fn => $file ) {
        $from = $cms['site_dir']."/".$fn;
        $c = $zip->addFile( $from, $fn );
        $zipped = $zipped && $c;
    }

    $zip->close();
    $logs = "<p>".__("Archive created:", "update.mod.php")." https://coffee-cms.com/{$name}</p>";

    file_put_contents( "/home/www/coffee-cms.com/htdocs/update_dev.json", json_encode( array( 
        "last" => $v, 
        "download" => "https://coffee-cms.com/coffee-cms-{$v}.zip" 
    ) ) );

    return $logs;
}
