view .cms/mod/admin.mod.php @ 0:78edf6b517a0 draft

24.10
author Coffee CMS <info@coffee-cms.ru>
date Fri, 11 Oct 2024 22:40:23 +0000
parents
children
line wrap: on
line source

<?php

$cms["modules"]["admin.mod.php"] = array(
    "name"        => "admin_module_name",
    "description" => "admin_module_description",
    "files"       => array(
        ".cms/admin.cms/html.php",
        ".cms/mod/admin.mod.php",
        ".cms/js/admin.js",
        ".cms/css/admin.css",
        ".cms/lang/ru_RU.UTF-8/admin.mod.php",
        ".cms/lang/en_US.UTF-8/admin.mod.php",
        ".cms/lang/uk_UA.UTF-8/admin.mod.php",
    ),
);

// Return if module disabled
if ( ! empty( $cms["config"]["admin.mod.php"]["disabled"] ) ) {

    return;

} else {

    if ( ! empty( $cms["config"]["locale"] ) ) {
        $translit = "{$cms['cms_dir']}/lang/{$cms['config']['locale']}/translit.php";
        if ( is_file( $translit ) ) {
            include_once( $translit );
        }
    }

    // on install admin_url == base_path
    if ( ! isset( $cms["config"]["admin.mod.php"]["admin_url"] ) ) {
        $cms["config"]["admin.mod.php"]["admin_url"] = "";
    }


    if ( empty( $cms["config"]["admin.mod.php"]["api_url"] ) ) {
        $cms["config"]["admin.mod.php"]["api_url"] = "api" . cms_uid();
        //cms_save_config();
    }

    if ( empty( $cms["config"]["admin.mod.php"]["cron_url"] ) ) {
        $cms["config"]["admin.mod.php"]["cron_url"] = "cron" . cms_uid();
        //cms_save_config();
    }

    if ( ! empty( $cms["config"]["admin.mod.php"]["clear_cache"] ) ) {
        hook_add_fn( "template",  "cms_admin_clear_cache", 4 );
    }


    $cms["urls"]["^{$cms['base_path']}{$cms['config']['admin.mod.php']['admin_url']}$"] = "admin";

    $cms["urls"]["^{$cms['base_path']}{$cms['config']['admin.mod.php']['api_url']}$"] = "api";

    $cms["urls"]["^{$cms['base_path']}{$cms['config']['admin.mod.php']['cron_url']}$"] = "cron";

    // Authorized admin and not authorized admin
    if ( is_admin() ) {
        hook_add_fn( "admin_header",  "cms_admin_admin_header" );
        hook_add_fn( "api",           "cms_admin_api" );
    }
    hook_add_fn( "admin",         "cms_admin_admin", 9999 );

}


function cms_admin_clear_cache() {
    global $cms;
    header( "Clear-Site-Data: \"cache\"" );
    unset( $cms["config"]["admin.mod.php"]["clear_cache"] );
    cms_save_config();
}


function cms_admin_admin_header() {
    global $cms;
    echo "<script src='{$cms['base_path']}js/admin.js'></script>";
}


function is_admin() {
    global $cms;
    return isset( $_COOKIE["sess"] ) && isset( $cms["config"]["logged"][ $_COOKIE["sess"] ] );
}


// Боковое меню в админке
function cms_admin_menu() {
    global $cms;
    
    // Сохраненные настройки боковой панели: $cms["config"]["admin_sections"]
    // Текущее состояние настроек формируемое динамически: $cms["admin_sections"]
    if ( ! empty( $cms["config"]["admin_sections"] ) ) {
        $cms["admin_sections"] = $cms["config"]["admin_sections"];
    } else {
        $cms["admin_sections"] = array();
    }

    // Настройки пунктов сохраняются в настройках модулей, а не в секции,
    // чтобы отключение модуля прятало пункт из боковой панели
    foreach( $cms["config"] as $mod => $mod_cfg ) {
        if ( substr( $mod, -8, 8 ) === ".mod.php" && empty( $mod_cfg["disabled"] ) && isset( $mod_cfg["menu"] ) ) {
            foreach( $mod_cfg["menu"] as $menu => $menu_cfg ) {
                $section = $menu_cfg["section"];
                $menu_cfg["module"] = $mod; // для последующей локализации
                $cms["admin_sections"][$section]["items"][$menu] = $menu_cfg;

                // Локализация заголовка секции
                $locale = $cms["config"]["locale"];
                if ( ! isset( $cms["admin_sections"][$section]["title"] ) && isset( $cms["lang"][$mod][$locale][$section] ) ) {
                    $cms["admin_sections"][$section]["title"] = $cms["lang"][$mod][$locale][$section];
                }

                // default section sort
                if ( isset( $menu_cfg["section_sort"] ) && ! isset( $cms["admin_sections"][$section]["sort"] ) ) {
                    $cms["admin_sections"][$section]["sort"] = $menu_cfg["section_sort"];
                }
            }
        }
    }
    // Затем эти данные используются в .cms/admin.cms/html.php

    // Sort & Translate
    $default_sort = array(
        "content"    => 100,
        "settings"   => 1000,
    );
    foreach( $cms["admin_sections"] as $section => $sec_conf ) {
        // Сортировка. Установить порядковый номер если не установлен.
        if ( ! isset( $cms["admin_sections"][$section]["sort"] ) ) {
            if ( isset( $default_sort[$section] ) ) {
                $cms["admin_sections"][$section]["sort"] = $default_sort[$section];
            } else {
                $cms["admin_sections"][$section]["sort"] = 500;
            }
        }

        // translate
        if ( ! isset( $cms["admin_sections"][$section]["title"] ) ) {
            $cms["admin_sections"][$section]["title"] = __( $section );
        }
    }

    // Sort sections
    cms_asort( $cms["admin_sections"] );

    // Sort items inside sections
    foreach( $cms["admin_sections"] as $section_name => $section ) {
        cms_asort( $cms["admin_sections"][$section_name]["items"] );
    }
}

function cms_admin_admin() {
    global $cms;
    $cms["template"] = "admin.cms";
    $cms["status"] = "200";
    $cms["hooks"]["write"]["disabled"] = true;

    // Not required code if not authorized
    if ( ! is_admin() ) return;
    
    $conf = $cms["config"]["admin.mod.php"]["menu"]["auth"];
    if ( empty( $conf["hide"] ) && empty( $cms["config"]["admin_sections"][ $conf["section"] ]["hide"] ) ) {

    // Save settings
    if ( ! empty( $_POST["change_admin_login"] ) ) {
        $login     = trim( $_POST["admin_login"] );
        $password  = trim( $_POST["admin_password"] );
        $cms["config"]["admin.mod.php"]["admin_login"] = $login;
        $cms["config"]["admin.mod.php"]["admin_password"] = $password;
        $cms["config"]["admin.mod.php"]["admin_url"] = trim( $_POST["admin_url"] );
        cms_save_config();
        header( "Location: {$cms['base_path']}{$cms['config']['admin.mod.php']['admin_url']}" );
        $cms["hooks"]["admin"]["next"] = ""; // Предотвратить выдачу админки
        return;
    }

    $rows = "";
    foreach ( $cms["config"]["logged"] as $login => $attr ) {
        if ( $login === $_COOKIE["sess"] ) {
            $current = "class=current";
        } else {
            $current = "";
        }
        $rows .= "
<div {$current}>
    <div class=del-sess data-login='{$login}'></div>
    <div class=date>{$attr['date']}</div>
    <div class=ip>{$attr['ip']}</div>
    <div class=agent>{$attr['user_agent']}</div>
</div>";
    }

    $logouts = "";
    if ( ! empty( $cms["config"]["logouted"] ) ) {
        // Перебираем закрытые сессии в прямом порядки
        // и очередную добавляем в начало списка,
        // чтобы получить обратный порядок отображения.
        foreach ( $cms["config"]["logouted"] as $attr ) {
            $logouts = "
<div>
    <div></div>
    <div class=date>{$attr['date']}</div>
    <div class=ip>{$attr['ip']}</div>
    <div class=agent>{$attr['user_agent']}</div>
</div>
{$logouts}";
        }
    }
    
    // Display settings
    $tr_login     = __( "login_or_password" );
    $tr_passw     = __( "password" );
    $tr_set       = __( "set_password" );
    $tr_current   = __( "current_logins" );
    $tr_history   = __( "logins_history" );
    $tr_admin_url = __( "admin_url" );
    $form = "
<form method=post>
    <div class=admin-url>{$tr_admin_url}</div>
    <input name=admin_url type=text value='{$cms['config']['admin.mod.php']['admin_url']}' autocomplete=off size=10>
    <div class=login-title>{$tr_login}</div>
    <input name=admin_login type=text value='{$cms['config']['admin.mod.php']['admin_login']}' autocomplete=off size=22>
    <div class=passwd-title>{$tr_passw}</div>
    <div class=password-widget>
        <input name=admin_password type=password value='{$cms['config']['admin.mod.php']['admin_password']}' autocomplete=off size=10>
        <div class=password-eye></div>
    </div>
    <div></div>
    <button value=save name=change_admin_login>{$tr_set}</button>
</form>

<div class=current-sess>
    <div class=table-title>{$tr_current}</div>
    <div class=sess-table>{$rows}</div>
</div>

<div class=history-sess>
    <div class=table-title>{$tr_history}</div>
    <div class=sess-table>{$logouts}</div>
</div>
";
    // Create menu item if not exists
    if ( empty( $cms["config"]["admin.mod.php"]["menu"]["auth"] ) ) {
        $cms["config"]["admin.mod.php"]["menu"]["auth"] = array(
            "title"    => "auth",
            "sort"     => 10,
            "section"  => "settings",
        );
        cms_save_config();
    }
    $cms["admin_pages"]["auth"] = $form;

    }
   
    
    $conf = $cms["config"]["admin.mod.php"]["menu"]["phpinfo"];
    if ( empty( $conf["hide"] ) && empty( $cms["config"]["admin_sections"][ $conf["section"] ]["hide"] ) ) {

    // PHP Info
    if ( empty( $cms["config"]["admin.mod.php"]["menu"]["phpinfo"]["hide"] ) ) {
        ob_start();
        phpinfo();
        preg_match( "/<body>(.+)<\/body>/us", ob_get_clean(), $m );
        $page = $m[1];
        // Create menu item if not exists
        if ( empty( $cms["config"]["admin.mod.php"]["menu"]["phpinfo"] ) ) {
            $cms["config"]["admin.mod.php"]["menu"]["phpinfo"] = array(
                "title"    => "php_info",
                "sort"     => 30,
                "section"  => "settings",
            );
            cms_save_config();
        }
        $cms["admin_pages"]["phpinfo"] = $page;
    }

    }


    // Modules List
    $conf = $cms["config"]["admin.mod.php"]["menu"]["modules"];
    if ( empty( $conf["hide"] ) && empty( $cms["config"]["admin_sections"][ $conf["section"] ]["hide"] ) ) {

        if ( empty( $cms["config"]["admin.mod.php"]["update"]["last_version"] ) ) {
            $last_version = __( "unknown" );
        } else {
            $last_version = $cms["config"]["admin.mod.php"]["update"]["last_version"];
            if ( $cms["kernel_compat"] !== $cms["config"]["admin.mod.php"]["update"]["compat"] ) {
                $last_version .= ". " . __( "incompatible_v" );
            }
        }

        if ( empty( $cms["config"]["admin.mod.php"]["last_check"] ) ) {
            $last_check = __( "never" );
        } else {
            $last_check = date( "d.m.Y H:i", $cms["config"]["admin.mod.php"]["last_check"] );
        }
        if ( file_exists( $cms["cms_dir"] . "/man/" . $cms["config"]["locale"] . "/book.html" ) ) {
            $book_href = $cms["base_path"] . "man/" . $cms["config"]["locale"] . "/book.html";
            $book_link = "<a href={$book_href} target=_blank>" . __( "book" ) . "</a>";
        } else {
            $book_link = "";
        }
        $admin_menu = "
        <div class=header>

            <div class=update-window>
                <div>" . __( "update_core" ) . "</div>
                <div class=check-answer>
                    <p>" . __( "current_v" ) . " {$cms['kernel_version']}</p>
                    <p>" . __( "last_v" ) . " {$last_version}</p>
                    <p>" . __( "checked" ) . " {$last_check}</p>
                </div>
                <div class=buttons>
                    <button data-fn=cms_check_update>" . __( "check" ) . "</button>
                    <button data-fn=cms_update>" . __( "update" ) . "</button>
                    {$book_link}
                    <button data-show-dev>" . __( "for_dev" ) . "</button>
                </div>
            </div>

            <div class='dev-window developers_only'>
                <div class=buttons>
                    <button data-fn=cms_check_dev_update>" . __( "check_dev" ) . "</button>
                    <button data-fn=cms_changed_files>" . __( "changed_files" ) . "</button>
                    <button data-fn=create_zip style='display:none'>" . __( "create_zip" ) . "</button>
                </div>
            </div>

            <div class=changed-files style='display:none'></div>

            <div class=upload_dnd>
                <input id=module-upload type=file name='myfile[]' multiple class=files>
                " . __( "mod_install" ) . "
            </div>
        </div>
        <div class=modules-grid>";

        // Сортировка модулей
        function modules_sort( $a, $b ) {
            return ( $a["name"] < $b["name"] ) ? -1 : 1;
        }
        uasort( $cms["modules"], "modules_sort" );

        $tr_on  = __( "mod_on" );
        $tr_off = __( "mod_off" );
        $tr_del = __( "mod_del" );
        foreach( $cms["modules"] as $mod => $mod_cfg ) {
            if ( ! empty( $cms["config"][$mod]["disabled"] ) ) {
                $status = "disabled";
                $tr_sw  = $tr_on;
            } else {
                $status = "enabled";
                $tr_sw  = $tr_off;
            }
            if ( empty( $mod_cfg["version"] ) ) {
                $mod_version = "";
            } else {
                $mod_version = "<div class=module-version>{$mod_cfg['version']}</div>";
            }
            // переводы имен модулей вынесены сюда чтобы не было варнингов при установке
            $admin_menu .= "
            <div class={$status} data-module={$mod}>
                <div class=module-name>" . __( $mod_cfg['name'], $mod ) . "</div>
                {$mod_version}
                <div class=module-description>" . __( $mod_cfg['description'], $mod ) . "</div>
                <a class=module-sw-btn>{$tr_sw}</a>
                <a class=module-del-btn>{$tr_del}</a>
            </div>";
        }
        $admin_menu .= "</div>";

        // Create menu item if not exists
        if ( empty( $cms["config"]["admin.mod.php"]["menu"]["modules"] ) ) {
            $cms["config"]["admin.mod.php"]["menu"]["modules"] = array(
                "title"    => "modules",
                "sort"     => 50,
                "section"  => "settings",
            );
            cms_save_config();
        }
        $cms["admin_pages"]["modules"] = $admin_menu;
    }


    // Admin

    // Create menu item if not exists
    if ( empty( $cms["config"]["admin.mod.php"]["menu"]["admin_menu"] ) ) {
        $cms["config"]["admin.mod.php"]["menu"]["admin_menu"] = array(
            "title"    => "admin_module_name",
            "sort"     => 40,
            "section"  => "settings",
        );
        cms_save_config();
    }
    
    cms_admin_menu();

    $conf = $cms["config"]["admin.mod.php"]["menu"]["admin_menu"];
    if ( empty( $conf["hide"] ) && empty( $cms["config"]["admin_sections"][ $conf["section"] ]["hide"] ) ) {

    $tr_show          = __( "show" );
    $tr_hide          = __( "hide" );
    $tr_save          = __( "save" );
    $tr_del_section   = __( "delete" );
    $tr_reset         = __( "reset" );


    $options = "";
    foreach( $cms["admin_sections"] as $section_name => $section ) {
        $options .= "<div class=option value='{$section_name}'>{$section["title"]}</div>";
    }

    // Admin Sections
    $admin_menu = "
<div class=main-main>
    <div class=am-grid>";
    foreach( $cms["admin_sections"] as $section_name => $section ) {
        if ( empty( $section["hide"] ) ) {
            $status = "showed";
            $tr_sw  = $tr_hide;
        } else {
            $status = "hidden";
            $tr_sw  = $tr_show;
        }
        $admin_menu .= "
<div>
    
    <div class={$status} data-am-type=section data-am-item='{$section_name}'>
        <input name=title type=text value='{$section['title']}'>
        <input name=sort type=text value='{$section['sort']}'>
        <div class=buttons>
            <a data-am-save>{$tr_save}</a>
            <a data-am-delete>{$tr_del_section}</a>
            <a data-am-sw>{$tr_sw}</a>
        </div>
    </div>

    <div class=items-grid data-am-childs='{$section_name}'>";

        // Admin Items
        if ( ! empty( $cms["admin_sections"][$section_name]["items"] ) )
        foreach( $cms["admin_sections"][$section_name]["items"] as $iname => $item ) {
            if ( empty( $item["hide"] ) ) {
                $status = "showed";
                $tr_sw  = $tr_hide;
            } else {
                $status = "hidden";
                $tr_sw  = $tr_show;
            }

            $admin_menu .= "
<div class={$status} data-am-type=item data-am-module={$item['module']} data-am-item={$iname}>
    <div class=item-name>" . __( $item["title"], $item["module"] ) . "</div>
    <input name=sort type=text value='{$item['sort']}'>
    <div class=section-select-grid>
        <div class=field-select data-section='{$section_name}'>
            <div class=value>{$cms['admin_sections'][$section_name]['title']}</div>
            <div class=icon></div>
        </div>
        <div class=field-options>
            {$options}
        </div>
    </div>
    <div class=buttons>
        <a data-am-save>{$tr_save}</a>
        <a data-am-save data-am-reset>{$tr_reset}</a>
        <a data-am-sw>{$tr_sw}</a>
    </div>
</div>";
        }

        $admin_menu .= "
    </div> <!-- class=status -->
</div> <!-- class= -->";
    }


    $admin_menu .= "
    </div> <!-- class=am-grid -->
</div> <!-- class=main-main -->
<div class=main-footer>
    <div class=add-section>" . __( "add_section" ) . "</div>
    <div class=reset-all>" . __( "reset_all" ) . "</div>
</div>";

    $cms["admin_pages"]["admin_menu"] = $admin_menu;

    }

}


function cms_admin_api() {
    global $cms;

    // Cookie, Logout, Admin, Modules
    if ( ! empty( $_POST["fn"] ) && is_admin() ) {
        switch ( $_POST["fn"] ) {

            case "logout":
                $cms["status"] = "200";
                if ( empty( $_POST["sess"] ) ) {
                    if ( PHP_VERSION_ID < 70300 ) {
                        setcookie( "sess", "", 365 * 24 * 60 * 60 );
                    } else {
                        setcookie( "sess", "", array( "SameSite" => "Lax", "expires" => time() + 365 * 24 * 60 * 60 ) );
                    }
                    $login  = $_COOKIE["sess"];
                    $result = "refresh";
                } else {
                    $login = $_POST["sess"];
                    if ( $_POST["sess"] === $_COOKIE["sess"] ) {
                        $result = "refresh";
                    } else {
                        $result = "ok";
                    }
                }

                if ( ! isset($cms["config"]["logouted"]) ) {
                    $cms["config"]["logouted"] = array();
                }
                // Добавляем закрытую сессию в конец массива в историю
                array_push( $cms["config"]["logouted"], $cms["config"]["logged"][$login] );
                // Оставим только последние 10 выходов в истории
                $cms["config"]["logouted"] = array_slice( $cms["config"]["logouted"], -10 );
                unset( $cms["config"]["logged"][$login] );
                cms_save_config();

                echo( json_encode( array(
                    "info_text"  => __( "logout_completed" ),
                    "info_class" => "info-success",
                    "info_time"  => 5000,
                    "result"     => $result,
                ) ) );
                return;
            break;

            case "admin_menu_save":
                switch ( $_POST["type"] ) {
                    case "section":
                        if ( empty( $_POST["title"] ) ) {
                            unset ( $cms["config"]["admin_sections"][ $_POST["item"] ]["title"] );
                        } else {
                            $cms["config"]["admin_sections"][ $_POST["item"] ]["title"] = $_POST["title"];
                        }
                        $n = (int) $_POST["sort"];
                        if ( $n ) {
                            $cms["config"]["admin_sections"][ $_POST["item"] ]["sort"]  = $n;
                        } else {
                            unset( $cms["config"]["admin_sections"][ $_POST["item"] ]["sort"] );
                        }
                    break;

                    // Настройки пунктов сохраняются в настройках модулей, а не в секции
                    case "item":
                        if ( $_POST["reset"] === "true" ) {
                            unset( $cms["config"][ $_POST["module"] ]["menu"][ $_POST["item"] ] );
                        } else {
                            $cms["config"][ $_POST["module"] ]["menu"][ $_POST["item"] ]["section"] = $_POST["section"];
                            $cms["config"][ $_POST["module"] ]["menu"][ $_POST["item"] ]["sort"]    = (int) $_POST["sort"];
                        }
                    break;
                }
                cms_save_config();
                echo( json_encode( array(
                    "ok" => "true",
                ) ) );
                return;
            break;

            case "admin_menu_hide":
                $hide = $_POST["hide"] === "true";
                switch ( $_POST["type"] ) {
                    case "section":
                        $cms["config"]["admin_sections"][ $_POST["item"] ]["hide"] = $hide;
                        break;
                    case "item":
                        $cms["config"][ $_POST["module"] ]["menu"][ $_POST["item"] ]["hide"] = $hide;
                        break;
                }
                cms_save_config();
                echo( json_encode( array(
                    "ok" => "true",
                ) ) );
                return;
            break;

            case "admin_menu_add_section":
                $n = 1;
                while ( isset( $cms["config"]["admin_sections"]["section_".$n] ) ) {
                    $n++;
                }
                $cms["config"]["admin_sections"]["section_".$n] = array(
                    "title" => __( "add_section" ),
                    "sort"  => 99,
                );
                cms_save_config();
                echo( json_encode( array(
                    "info_text"  => __( "section_added" ),
                    "info_class" => "info-success",
                    "info_time"  => 3000,
                ) ) );
                return;
            break;

            case "admin_menu_del":
                $cms["status"] = "200";
                unset( $cms["config"]["admin_sections"][ $_POST["item"] ]);
                cms_save_config();
                echo( json_encode( array(
                    "info_text"  => __( "deleted" ),
                    "info_class" => "info-success",
                    "info_time"  => 1000,
                ) ) );
                return;
            break;

            case "module_disable":
                if ( $_POST["module"] == "admin.mod.php" || $_POST["module"] == "template.mod.php" ) {
                    echo( json_encode( array(
                        "info_text"  => __( "not_disabled" ),
                        "info_class" => "info-error",
                        "info_time"  => 5000,
                    ) ) );
                    return;
                }
                if ( $_POST["disable"] === "true" ) {
                    $cms["config"][$_POST["module"]]["disabled"] = true;
                } else {
                    unset( $cms["config"][$_POST["module"]]["disabled"] );
                }
                cms_save_config();
                echo( json_encode( array(
                    "ok"  => "true",
                ) ) );
                return;
            break;

            case "module_del":
                $text = __( "delete_this_files" );
                foreach( $cms["modules"][$_POST["module"]]["files"] as $file ) {
                    $text .= "<br>{$file}";
                }
                echo( json_encode( array(
                    "info_text"  => $text,
                    "info_class" => "info-error",
                    "info_time"  => 60000,
                ) ) );
                return;
            break;

            case "install_module":
                $success = true;
                foreach ( $_FILES["myfile"]["name"] as $n => $name ) {
                    if ( $_FILES["myfile"]["error"][$n] ) {
                        $success = false;
                        $text = str_replace( "xxx", $name, __( "upload_error_xxx" ) );
                        break;
                    } else {
                        // Unpack Module
                        // Object Oriented Style for future compability with PHP 8
                        $zip = new ZipArchive;
                        $zip_file = $_FILES["myfile"]["tmp_name"][$n];
                        if ( $zip->open( $zip_file ) === TRUE ) {
                            $ok = $zip->extractTo( $cms["site_dir"] );
                            $zip->close();
                            if ( ! $ok ) {
                                $success = false;
                                $text = str_replace( "xxx", $name, __( "cant_extract_xxx" ) );
                                break;
                            } else {
                                // Load modules
                                // В одном zip может быть множество модулей
                                foreach( glob( "{$cms['cms_dir']}/mod/*.mod.php" ) as $mod_file ) {
                                    include_once( $mod_file );
                                    $module_name = str_replace( ".mod.php", "", basename( $mod_file ) );
                                    $sql_file = "{$cms['cms_dir']}/mod/{$module_name}.update.sql";
                                    if ( is_file( $sql_file ) ) {
                                        $q = file_get_contents( $sql_file );
                                        $res = mysqli_multi_query( $cms["base"], $q );
                                    }
                                }

                                do_hook( "create_tables" );

                                $text = __( "install_success" );
                            }
                        } else {
                            $success = false;
                            $text = str_replace( "xxx", $name, __( "cant_open_zip_xxx" ) );
                            break;
                        }
                    }
                }

                if ( $success ) {
                    if ( function_exists( "cms_clear_cache" ) ) {
                        cms_clear_cache();
                    }
                    $cms["config"]["admin.mod.php"]["clear_cache"] = true;
                    cms_save_config();
                    echo( json_encode( array(
                        "info_text"  => $text,
                        "info_class" => "info-success",
                        "info_time"  => 5000,
                    ) ) );
                    return;
                } else {
                    echo( json_encode( array(
                        "info_text"  => $text,
                        "info_class" => "info-error",
                        "info_time"  => 10000,
                    ) ) );
                    return;
                }
            break;

            case "no_translation":
                if ( ! empty( $cms["config"]["debug"] ) ) {
                    $new_debug = array(
                        "translate to {$cms["config"]["locale"]}" => array(
                            "{$_POST["module"]}:js" => $_POST["str"],
                        ),
                    );
                    $debug_file = $cms["cms_dir"] . "/debug.log.php";
                    if ( $handle = fopen( $debug_file, "r" ) ) {
                        flock( $handle, LOCK_SH );
                        include( $debug_file );
                        $cms["debug"] = array_replace_recursive( $cms["debug"], $new_debug );
                        flock( $handle, LOCK_UN );
                        file_put_contents( $debug_file, '<?php $cms["debug"] = ' . var_export( $cms["debug"], true) . ";\n", LOCK_EX );
                    } else {
                        file_put_contents( $debug_file, '<?php $cms["debug"] = ' . var_export( $cms["debug"], true) . ";\n", LOCK_EX );
                    }
                }
                echo( '{ "result": "ok" }' );
                return;
            break;

            case "reset_admin_menu_items":
                reset_admin_menu_items();
                echo( json_encode( array(
                    "info_text"  => __( "reset_all_ok" ),
                    "info_class" => "info-success",
                    "info_time"  => 3000,
                ) ) );
                return;
            break;

            case "create_zip":
                cms_update_create_zip();
            break;

            case "cms_check_update":
                cms_update_check( "https://coffee-cms.ru/update.json" );
            break;

            case "cms_check_dev_update":
                cms_update_check( "https://dev.coffee-cms.ru/update_dev.json" );
            break;

            case "cms_update":
                cms_update_cms_update();
            break;
            
            case "cms_changed_files":
                cms_changed_files();
            break;
        }
        
    }
}


function reset_admin_menu_items() {
    global $cms;
    unset( $cms["config"]["admin_sections"] );
    foreach( $cms["modules"] as $mod => $val ) {
        if ( isset( $cms["config"][$mod]["menu"] ) ) {
            unset( $cms["config"][$mod]["menu"] );
        }
    }
    cms_save_config();
}//unset( $cms["config"][ $_POST["module"] ]["menu"][ $_POST["item"] ] );


function cms_update_create_filelist() {
    global $cms;

    $logs = "";

    $queue = array(
        "{$cms['site_dir']}/uploads/.keep",
        "{$cms['site_dir']}/.htaccess",
        "{$cms['site_dir']}/.nginx.conf",
        "{$cms['site_dir']}/.cms/admin.cms",
        "{$cms['site_dir']}/.cms/css/base.css",
        "{$cms['site_dir']}/.cms/css/menu.css",
        "{$cms['site_dir']}/.cms/css/pages.css",
        "{$cms['site_dir']}/.cms/css/sitemap.css",
        "{$cms['site_dir']}/.cms/css/rss.css",
        "{$cms['site_dir']}/.cms/css/template.css",
        "{$cms['site_dir']}/.cms/css/admin.css",
        "{$cms['site_dir']}/.cms/filelist.php",
        "{$cms['site_dir']}/.cms/img",
        "{$cms['site_dir']}/.cms/js/admin.js",
        "{$cms['site_dir']}/.cms/js/menu.js",
        "{$cms['site_dir']}/.cms/js/pages.js",
        "{$cms['site_dir']}/.cms/js/sitemap.js",
        "{$cms['site_dir']}/.cms/js/rss.js",
        "{$cms['site_dir']}/.cms/js/template.js",
        "{$cms['site_dir']}/.cms/lang/ru_RU.UTF-8/admin.mod.php",
        "{$cms['site_dir']}/.cms/lang/ru_RU.UTF-8/base.mod.php",
        "{$cms['site_dir']}/.cms/lang/ru_RU.UTF-8/menu.mod.php",
        "{$cms['site_dir']}/.cms/lang/ru_RU.UTF-8/pages.mod.php",
        "{$cms['site_dir']}/.cms/lang/ru_RU.UTF-8/sitemap.mod.php",
        "{$cms['site_dir']}/.cms/lang/ru_RU.UTF-8/rss.mod.php",
        "{$cms['site_dir']}/.cms/lang/ru_RU.UTF-8/template.mod.php",
        "{$cms['site_dir']}/.cms/lang/ru_RU.UTF-8/translit.php",
        "{$cms['site_dir']}/.cms/lang/en_US.UTF-8/admin.mod.php",
        "{$cms['site_dir']}/.cms/lang/en_US.UTF-8/base.mod.php",
        "{$cms['site_dir']}/.cms/lang/en_US.UTF-8/menu.mod.php",
        "{$cms['site_dir']}/.cms/lang/en_US.UTF-8/pages.mod.php",
        "{$cms['site_dir']}/.cms/lang/en_US.UTF-8/sitemap.mod.php",
        "{$cms['site_dir']}/.cms/lang/en_US.UTF-8/rss.mod.php",
        "{$cms['site_dir']}/.cms/lang/en_US.UTF-8/template.mod.php",
        "{$cms['site_dir']}/.cms/lang/uk_UA.UTF-8/admin.mod.php",
        "{$cms['site_dir']}/.cms/lang/uk_UA.UTF-8/base.mod.php",
        "{$cms['site_dir']}/.cms/lang/uk_UA.UTF-8/menu.mod.php",
        "{$cms['site_dir']}/.cms/lang/uk_UA.UTF-8/pages.mod.php",
        "{$cms['site_dir']}/.cms/lang/uk_UA.UTF-8/sitemap.mod.php",
        "{$cms['site_dir']}/.cms/lang/uk_UA.UTF-8/rss.mod.php",
        "{$cms['site_dir']}/.cms/lang/uk_UA.UTF-8/template.mod.php",
        "{$cms['site_dir']}/.cms/lang/uk_UA.UTF-8/translit.php",
        
        "{$cms['site_dir']}/.cms/lib/codemirror",
        "{$cms['site_dir']}/.cms/man/ru_RU.UTF-8/book.html",
        "{$cms['site_dir']}/.cms/man/ru_RU.UTF-8/codemirror.html",
        "{$cms['site_dir']}/.cms/man/ru_RU.UTF-8/example.html",
        "{$cms['site_dir']}/.cms/man/ru_RU.UTF-8/hint_css.html",
        "{$cms['site_dir']}/.cms/man/ru_RU.UTF-8/hint_html.html",
        "{$cms['site_dir']}/.cms/man/ru_RU.UTF-8/hint_php.html",
        "{$cms['site_dir']}/.cms/man/ru_RU.UTF-8/menu.html",
        "{$cms['site_dir']}/.cms/man/css/prism.css",
        "{$cms['site_dir']}/.cms/man/css/styles.css",
        "{$cms['site_dir']}/.cms/man/js/prism.js",

        // Файлы шаблона
        "{$cms['site_dir']}/.cms/mini/html.php",
        "{$cms['site_dir']}/.cms/mini/404.en_US.UTF-8.php",
        "{$cms['site_dir']}/.cms/mini/404.ru_RU.UTF-8.php",
        "{$cms['site_dir']}/.cms/mini/404.uk_UA.UTF-8.php",
        "{$cms['site_dir']}/.cms/mini/banner.jpg",
        "{$cms['site_dir']}/.cms/mini/blog.php",
        "{$cms['site_dir']}/.cms/mini/favicon.svg",
        "{$cms['site_dir']}/.cms/mini/frontpage.php",
        "{$cms['site_dir']}/.cms/mini/instruction.ru_RU.UTF-8.html",
        "{$cms['site_dir']}/.cms/mini/page.php",
        "{$cms['site_dir']}/.cms/mini/page.teaser.php",
        "{$cms['site_dir']}/.cms/mini/post.php",
        "{$cms['site_dir']}/.cms/mini/styles.css",
        "{$cms['site_dir']}/.cms/mini/template.settings.php",
        "{$cms['site_dir']}/.cms/mini/tpl.mod.php",
        
        "{$cms['site_dir']}/.cms/mod/admin.mod.php",
        "{$cms['site_dir']}/.cms/mod/base.mod.php",
        "{$cms['site_dir']}/.cms/mod/menu.mod.php",
        "{$cms['site_dir']}/.cms/mod/pages.mod.php",
        "{$cms['site_dir']}/.cms/mod/sitemap.mod.php",
        "{$cms['site_dir']}/.cms/mod/rss.mod.php",
        "{$cms['site_dir']}/.cms/mod/template.mod.php",
        "{$cms['site_dir']}/.cms/index.fn.php",
        "{$cms['site_dir']}/.cms/index.php",
        "{$cms['site_dir']}/.cms/update.sql",
        "{$cms['site_dir']}/.cms/update.php",
        "{$cms['site_dir']}/.cms/.unlicense.txt",
    );
    $no_sha1 = array(
        "{$cms['site_dir']}/.cms/filelist.php",
    );
    
    $list  = array();

    while ( $cur = array_shift( $queue ) ) {
        if ( is_dir( $cur ) ) {
            $queue = array_merge( $queue, glob( $cur . "/*" ) );
        } elseif ( is_file( $cur ) ) {
            if ( in_array( $cur, $no_sha1 ) ) {
                $sha1 = "";
                $size = "";
            } else {
                $sha1 = sha1_file( $cur );
                $size = filesize( $cur );
            }
            $file = str_replace( "{$cms['site_dir']}/", "", $cur );
            $list[$file] = array(
                "sha1" => $sha1,
                "size" => $size,
            );
        } else {
            $logs .= "<p>" . __( "file_or_dir_not_exists" ) . " '{$cur}'</p>";
        }
    }

    // Список файлов которые разрешено редактировать.
    // И если они отредактированы, то не будут обновляться.
    $allow_change = array(
        ".htaccess",
    );
    
    file_put_contents( "{$cms['cms_dir']}/filelist.php", 
        "<?php\n\$allow_change = " . var_export( $allow_change, true ) . ";\n" .
        '$list = ' . var_export( $list, true ) . ";\n"
    );

    $logs .= "<p>".__( "files" )." " . count( $list ) . "</p>";

    return $logs;
    
}


function cms_update_check( $url ) {
    global $cms;

    // prevent warning
    if ( empty( $cms["config"]["admin.mod.php"]["last_check"] ) ) {
        $cms["config"]["admin.mod.php"]["last_check"] = 0;
    }

    $t = time();
    $dt = $t - $cms["config"]["admin.mod.php"]["last_check"];
    $time_min = 1;
    if ( $dt < $time_min * 60 ) {
        $n = ceil( ( $time_min * 60 - $dt ) / 60 );
        $msg = __( "try_later" );
        $msg = str_replace( "xxx", $n, $msg );
        echo( json_encode( array(
            "info_text"  => $msg,
            "info_class" => "info-error",
            "info_time"  => 5000,
        ) ) );
        return;
    }

    if ( ! $update = file_get_contents( $url ) ) {
        echo( json_encode( array(
            "info_text"  => __( "cant_get" ) . " " . $url,
            "info_class" => "info-error",
            "info_time"  => 5000,
        ) ) );
        return;
    }

    if ( ! $update = json_decode( $update, true ) ) {
        echo( json_encode( array(
            "info_text"  => __( "json_error" ) . " " . $url,
            "info_class" => "info-error",
            "info_time"  => 5000,
        ) ) );
        return;
    }

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

    $last_check = date( "d.m.Y H:i", $cms["config"]["admin.mod.php"]["last_check"] );

    $last_version = $cms["config"]["admin.mod.php"]["update"]["last_version"];

    if ( $cms["kernel_compat"] !== $cms["config"]["admin.mod.php"]["update"]["compat"] ) {
        $last_version .= ". " . __( "incompatible_v" );
    }

    $update_info  = "<p>" . __( "current_v" ) . " {$cms['kernel_version']}</p>";
    $update_info .= "<p>" . __( "last_v" ) . " {$last_version}</p>";
    $update_info .= "<p>" . __( "checked" ) . " {$last_check}</p>";

    echo( json_encode( array(
        "info_text"  => __( "last_v" ) . " {$last_version}",
        "info_class" => "info-success",
        "info_time"  => 5000,
        "answer"     => $update_info,
    ) ) );
    return;

}


function cms_changed_files() {
    global $cms;

    $fl = "{$cms['cms_dir']}/filelist.php";
    require( $fl );

    $errors = array();
    $max_errors = 100;

    // Проверка файлов текущей версии
    // по чексуммам и размерам
    foreach( $list as $fn => $file ) {
        $f = $cms["site_dir"] . "/" . $fn;
        if ( ! is_file( $f ) ) {
            if ( count( $errors ) >= $max_errors ) {
                break;
            } else {
                array_push( $errors, __( "file_not_exists" ) . " " . $f );
            }
        }
        $sha1 = sha1_file( $f );
        $size = filesize( $f );
        if ( ! empty( $file["sha1"] ) && ! empty( $file["size"] ) && is_file( $f ) ) {
            if ( $file["sha1"] !== $sha1 || $file["size"] !== $size ) {
                if ( ! in_array( $fn, $allow_change ) ) {
                    if ( count( $errors ) >= $max_errors ) {
                        break;
                    } else {
                        array_push( $errors, __( "file_changed" ) . " " . $f );
                    }
                }
            }
        }
    }

    // Возвращаем список недочетов
    if ( count( $errors ) ) {
        $files = implode( "<br>", $errors );
    } else {
        $files = __( "no_files_changed" );
    }
    echo( json_encode( array(
        "answer"  => $files,
    ) ) );
    return;

}

function cms_update_cms_update() {
    global $cms;

    // Не проверялись обновления вообще
    if ( empty( $cms["config"]["admin.mod.php"]["update"] ) ) {
        echo( json_encode( array(
            "info_text"  => __( "check_updates" ),
            "info_class" => "info-error",
            "info_time"  => 5000,
        ) ) );
        return;
    }

    // Отсутствует файл со списком файлов
    $fl = "{$cms['cms_dir']}/filelist.php";
    if ( ! file_exists( $fl ) ) {
        echo( json_encode( array(
            "info_text"  => __( "file_list_missing" ) . " " . $fl,
            "info_class" => "info-error",
            "info_time"  => 5000,
        ) ) );
        return;
    }
    
    require( $fl );

    $update = $cms["config"]["admin.mod.php"]["update"];

    // Может нечего обновлять
    if ( $update["last_version"] <= $cms["kernel_version"] ) {
        echo( json_encode( array(
            "info_text"  => __( "already_updated" ),
            "info_class" => "info-success",
            "info_time"  => 5000,
        ) ) );
        return;
    }

    // Может несовместимая версия
    if ( $update["compat"] !== $cms["kernel_compat"] ) {
        echo( json_encode( array(
            "info_text"  => __( "impossible" ),
            "info_class" => "info-error",
            "info_time"  => 15000,
        ) ) );
        return;
    }
    
    // Копить по 10 ошибок
    // чтобы не мучить по одной
    $errors = array();
    $max_errors = 10;
    
    // Проверка файлов текущей версии
    // по чексуммам и размерам
    foreach( $list as $fn => $file ) {
        $f = $cms["site_dir"] . "/" . $fn;
        if ( ! is_file( $f ) ) {
            if ( count( $errors ) >= $max_errors ) {
                break;
            } else {
                array_push( $errors, __( "file_not_exists" ) . " " . $f );
            }
        }
        $sha1 = sha1_file( $f );
        $size = filesize( $f );
        if ( ! empty( $file["sha1"] ) && ! empty( $file["size"] ) && is_file( $f ) ) {
            if ( $file["sha1"] !== $sha1 || $file["size"] !== $size ) {
                if ( ! in_array( $fn, $allow_change ) ) {
                    if ( count( $errors ) >= $max_errors ) {
                        break;
                    } else {
                        array_push( $errors, __( "file_changed" ) . " " . $f );
                    }
                }
            }
        }
    }

    // Возвращаем список недочетов
    if ( count( $errors ) ) {
        echo( json_encode( array(
            "info_text"  => implode( "<br>", $errors ),
            "info_class" => "info-error",
            "info_time"  => 50000,
        ) ) );
        return;
    }

    // Check Rights
    $rights = array();
    foreach( $list as $fn => $file ) {
        $file_path = $cms["site_dir"] . "/" . $fn;
        $e = ! is_file( $file_path ) || ! is_writable( $file_path );
        if ( $e ) {
            if ( count( $rights ) >= $max_errors ) {
                break;
            } else {
                array_push( $rights, $file_path );
            }
        }
    }

    // Возвращаем список недочетов
    if ( count( $rights ) ) {
        echo( json_encode( array(
            "info_text"  => __( "update_error_rights" ) . "<br>" . implode( "<br>", $rights ),
            "info_class" => "info-error",
            "info_time"  => 50000,
        ) ) );
        return;
    }

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

    $t = time();
    $dt = $t - $cms["config"]["admin.mod.php"]["last_update"];
    $time_min = 1;
    if ( $dt < $time_min * 60 ) {
        $n = ceil( ( $time_min * 60 - $dt ) / 60 );
        $msg = __( "try_later" );
        $msg = str_replace( "xxx", $n, $msg );
        echo( json_encode( array(
            "info_text"  => $msg,
            "info_class" => "info-error",
            "info_time"  => 5000,
        ) ) );
        return;
    }

    // Download New Version
    if ( ! $content = file_get_contents( $update["download"] ) ) {
        echo( json_encode( array(
            "info_text"  => __( "download_error" ),
            "info_class" => "info-error",
            "info_time"  => 5000,
        ) ) );
        return;
    }
    $tmp = $cms["site_dir"] . "/.tmp";
    if ( is_dir( $tmp ) ) {
        recurse_rm( $tmp );
    }
    if ( ! mkdir( $tmp ) ) {
        echo( json_encode( array(
            "info_text"  => __( "cant_create_tmp_dir" ) . " " . $tmp,
            "info_class" => "info-error",
            "info_time"  => 5000,
        ) ) );
        return;
    }
    $fn = preg_replace( "/.*\//u", "", $update["download"] );
    $file = $tmp . "/" . $fn;
    if ( ! file_put_contents( $file, $content ) ) {
        echo( json_encode( array(
            "info_text"  => __( "cant_write_file" ) . " " . $file,
            "info_class" => "info-error",
            "info_time"  => 50000,
        ) ) );
        return;
    }

    // Unpack New Version
    // Object Oriented Style for future compability with PHP 8
    $zip = new ZipArchive;
    if ( $zip->open( $file ) === TRUE ) {
        $ok = $zip->extractTo( $tmp );
        $zip->close();
        if ( ! $ok ) {
            echo( json_encode( array(
                "info_text"  => __( "cant_extract" ) . " " . $file,
                "info_class" => "info-error",
                "info_time"  => 5000,
            ) ) );
            return;
        }
    } else {
        echo( json_encode( array(
            "info_text"  => __( "cant_open_zip" ) . " " . $file,
            "info_class" => "info-error",
            "info_time"  => 5000,
        ) ) );
        return;
    }

    // Check New Version
    $fl = "{$tmp}/.cms/filelist.php";
    if ( ! is_file( $fl ) ) {
        echo( json_encode( array(
            "info_text"  => __( "file_list_missing" ) . " " . $fl,
            "info_class" => "info-error",
            "info_time"  => 5000,
        ) ) );
        return;
    }
    $oldlist = $list;
    require( $fl );
    foreach( $list as $fn => $file ) {
        $f = $tmp . "/" . $fn;
        if ( ! is_file( $f ) ) {
            echo( json_encode( array(
                "info_text"  => __( "file_not_exists" ) . " " . $f,
                "info_class" => "info-error",
                "info_time"  => 5000,
            ) ) );
            return;
        }
        $sha1 = sha1_file( $f );
        $size = filesize( $f );
        if ( ! empty( $file["sha1"] ) ) {
            if ( $file["sha1"] !== $sha1 ) {
                echo( json_encode( array(
                    "info_text"  => __( "file_changed" ) . " " . $f,
                    "info_class" => "info-error",
                    "info_time"  => 5000,
                ) ) );
                return;
            }
        }
        if ( ! empty( $file["size"] ) ) {
            if ( $file["size"] !== $size ) {
                echo( json_encode( array(
                    "info_text"  => __( "file_changed" ) . " " . $f,
                    "info_class" => "info-error",
                    "info_time"  => 5000,
                ) ) );
                return;
            }
        }
    }

    // Удаление файлов текущей цмс
    $removed = true;
    foreach( $oldlist as $fn => $file ) {
        $from = $cms["site_dir"] . "/" . $fn;
        
        if ( in_array( $fn, $allow_change ) and
        sha1_file( $from ) !== $oldlist[$fn]["sha1"] ) {
            // Если файл в списке разрешенных на изменение
            // и sha1 не совпадает, то оставляем его не тронутым
        } else {
            $c = unlink( $from );
            $removed = $removed && $c;
        }
        // try remove dir
        $rdir = preg_replace( "/\/[^\/]+$/u", "", $from );
        if ( is_dir_and_empty( $rdir ) ) {
            rmdir( $rdir );
        }
    }

    if ( ! $removed ) {
        echo( json_encode( array(
            "info_text"  => __( "update_error_remove" ),
            "info_class" => "info-error",
            "info_time"  => 5000,
        ) ) );
        return;
    }

    // Перемещение новой версии в текущую
    // Разрешенные измененные файлы не перезаписываются
    $moved = true; // по умолчанию нет ошибок
    foreach( $list as $file_name => $none ) {
        $old_file_path = $cms["site_dir"] . "/" . $file_name;
        if (
            is_file( $old_file_path ) and
            in_array( $file_name, $allow_change ) and
            sha1_file( $old_file_path ) !== $oldlist[$file_name]["sha1"]
        ) {
            // Если файл в списке разрешенных на изменение
            // и sha1 не совпадает, то оставляем его не тронутым
        } else {
            $from = $tmp . "/" . $file_name;
            $to   = $cms["site_dir"] . "/" . $file_name;
            $ndir = preg_replace( "/\/[^\/]+$/u", "", $to );
            if ( ! is_dir( $ndir ) ) {
                mkdir( $ndir, 0777, true );
            }
            $c = copy( $from, $to );
            $moved = $moved && $c;
        }
    }

    if ( ! $moved ) {
        echo( json_encode( array(
            "info_text"  => __( "update_error_move" ),
            "info_class" => "info-error",
            "info_time"  => 5000,
        ) ) );
        return;
    }

    recurse_rm( $tmp );

    // Выполнить запросы в БД, находящиеся в файле update.sql
    // Не будем обращать внимания на результат запроса,
    // ведь если идет многократное обновление из dev-ветки,
    // то скорее всего будут ошибки его выполнения.
    $update_sql = "{$cms['cms_dir']}/update.sql";
    $q = "";
    if ( is_file( $update_sql ) ) {
        $q = file_get_contents( $update_sql );
    }
    if ( $q ) {
        cms_base_connect();
        if ( $cms["base"] ) {
            $res = mysqli_multi_query( $cms["base"], $q );
        }
    }

    $cms["config"]["admin.mod.php"]["last_update"] = time();

    // Обновить конфиг
    $update_config = "{$cms['cms_dir']}/update.php";
    if ( is_file( $update_config ) ) {
        include( $update_config );
    }

    $cms["config"]["admin.mod.php"]["clear_cache"] = true;
    cms_save_config();

    cms_clear_cache();

    echo( json_encode( array(
        "info_text"  => __( "successfull_update" ),
        "info_class" => "info-success",
        "info_time"  => 5000,
        "reload"     => true,
    ) ) );
    return;

}


function cms_update_create_zip() {
    global $cms;
    
    // Данная функция сообщит об отсутствующих файлах
    $files = cms_update_create_filelist();
    
    require( "{$cms['cms_dir']}/filelist.php" );
    $zip = new ZipArchive();
    $v = $cms['kernel_version'] . date( "_Y.m.d_Hi" );
    $name = "coffee-cms-{$v}.zip";
    $download_url = "{$cms['url']['scheme']}://{$cms['url']['host']}/{$name}";
    $zipname = "{$cms["site_dir"]}/{$name}";

    if ( $zip->open( $zipname, ZipArchive::CREATE ) !== true ) {
        echo( json_encode( array(
            "answer" => __( "cant_create_zip" ) . " {$zipname}",
        ) ) );
        return;
    }

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

    $zip->close();

    file_put_contents( "{$cms["site_dir"]}/update_dev.json", json_encode( array( 
        "last_version" => $v,
        "compat"       => $cms["kernel_compat"],
        "download"     => $download_url,
    ) ) );

    echo( json_encode( array(
        "answer"     => $files . "<p>" . __( "archive_created" ) . ": <a href='{$download_url}'>{$download_url}</a></p>",
    ) ) );
    return;
}