diff .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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.cms/mod/admin.mod.php	Fri Oct 11 22:40:23 2024 +0000
@@ -0,0 +1,1417 @@
+<?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;
+}