<?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/login.js",
        ".cms/js/admin_fn.js",
        ".cms/js/admin.js",
        ".cms/css/a.mixin.less",
        ".cms/css/admin.less",
        ".cms/css/admin.css",
        ".cms/img",
        ".cms/js/less.js",
        ".cms/.widgets",
        // Шрифты
        ".cms/fonts",
        // Для справочных страниц всех модулей
        ".cms/man/css/prism.css",
        ".cms/man/css/styles.css",
        ".cms/man/js/prism.js",
        // Переводы
        ".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",
    ),
);

// Модуль отключен?
if ( ! empty( $cms["config"]["admin.mod.php"]["disabled"] ) ) {
    return;
}

// translit
include_once( "{$cms['cms_dir']}/lang/translit.php" );

// 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();
}

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

// После обновления CMS добавляем заголовок чтобы браузер очистил кеш сайта
// и перезапросил все файлы. Помогает обновить стили админки.
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( "api", "cms_admin_api" );
}
hook_add_fn( "admin", "cms_admin_admin", 9999 );


// После обновления CMS добавляем заголовок чтобы браузер очистил кеш сайта
// и перезапросил все файлы. Помогает обновить стили админки.
function cms_admin_clear_cache() {
    global $cms;
    header( "Clear-Site-Data: \"cache\"" );
    unset( $cms["config"]["admin.mod.php"]["clear_cache"] );
    cms_save_config();
}


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


// Подготовка бокового меню к выводу
function cms_panel() {
    global $cms;
    
    // Сохраненные настройки боковой панели копируем в оперативные
    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;

                //:Локализация заголовка секции
                // Первичная установка заголовка секции, или после сброса.
                if ( ! isset( $cms["admin_sections"][$section]["title"] ) )
                {
                    $title = __ ( $section, $mod );
                    if ( $title === $section ) {
                        // Нет перевода. Это возможно если модуль входит в стандартную секцию,
                        // но очередность перебора в цикле такая что попал раньше чем нужно.
                        // Значит пробуем взять перевод из модуля админ.
                        $title = __ ( $section, "admin.mod.php" );
                    }
                    $cms["admin_sections"][$section]["title"] = $title;
                }

                // Модули, которые добавляют свои секции, сами выставляют сортировку своим секциям.
                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

    // Сортировка по умолчанию для стандартных секций (если не переопределена).
    if ( ! isset( $cms["admin_sections"]["ecs"]["sort"] ) ) {
        $cms["admin_sections"]["ecs"]["sort"] = 200;
    }
    if ( ! isset( $cms["admin_sections"]["content"]["sort"] ) ) {
        $cms["admin_sections"]["content"]["sort"] = 100;
    }
    if ( ! isset( $cms["admin_sections"]["settings"]["sort"] ) ) {
        $cms["admin_sections"]["settings"]["sort"] = 1000;
    }
    if ( ! isset( $cms["admin_sections"]["help"]["sort"] ) ) {
        $cms["admin_sections"]["help"]["sort"] = 10000;
    }

    // Сортировка секций
    cms_asort( $cms["admin_sections"] );

    // Сортировка пунктов внутри секций
    foreach( $cms["admin_sections"] as $section_name => $section ) {
        cms_asort( $cms["admin_sections"][$section_name]["items"] );
    }
}

function cms_admin_sort( $a, $b ) {
    return strcmp( $b["date"], $a["date"] );
}

function cms_admin_admin() {
    global $cms;

    // Какой-то модуль мог поставить 302 при сохранении настроек.
    // Поэтому нельзя ставить 200 без проверок.
    if ( $cms["status"] !== "404" ) {
        return;
    }

    $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["password"] );
        $timezone  = trim( $_POST["timezone"] );
        $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["config"]["timezone"] = $timezone;
        cms_save_config();
        $cms["status"] = "302";
        $cms["status_302_location"] = $cms["base_path"] . $cms["config"]["admin.mod.php"]["admin_url"];
        return;
    }

    // Сортировку оставляем тут, а не при входе перед сохранением,
    // чтобы меньше последствий ловить.
    uasort( $cms["config"]["logged"], "cms_admin_sort" );

    $rows = "";
    foreach ( $cms["config"]["logged"] as $login => $attr ) {
        if ( $login === $_COOKIE["sess"] ) {
            $current = " current";
        } else {
            $current = "";
        }
        $rows .= "
<div class='row{$current}'>
    <div class='button close icon' data-login='{$login}'><span></span></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 class=row>
    <div class=close></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   = __( "closed_sessions" );
    $tr_admin_url = __( "admin_url" );
    $tr_timezone  = __( "timezone" );
    // Экранирование уже содержится в виджете
    $password = $cms["config"]["admin.mod.php"]["admin_password"];
    $form = "
<form class=panel method=post>
    <label>
        <div class=title>{$tr_admin_url}</div>
        <input name=admin_url type=text value='{$cms['config']['admin.mod.php']['admin_url']}' autocomplete=off size=10>
    </label>
    <label>
        <div class=title>{$tr_login}</div>
        <input name=admin_login type=text value='{$cms['config']['admin.mod.php']['admin_login']}' autocomplete=off size=22>
    </label>
    <label>
        <div class=title>{$tr_passw}</div>
        " . widget( "password_widget.php", array( "password" => $password, "placeholder" => "" ) ) . "
    </label>
    <label>
        <div class=title>{$tr_timezone}</div>
        <input name=timezone type=text value='{$cms['config']['timezone']}' autocomplete=off size=13>
    </label>
    <button class='button save' value=save name=change_admin_login><span>{$tr_set}</span></button>
</form>

<div class=active>
    <div class=title>{$tr_current}</div>
    <div class='panel sessions'>{$rows}</div>
</div>

<div class=history>
    <div class=title>{$tr_history}</div>
    <div class='panel sessions'>{$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"]["fonts"];
    if ( empty( $conf["hide"] ) && empty( $cms["config"]["admin_sections"][ $conf["section"] ]["hide"] ) ) {
        // Пункт сбоку
        if ( empty( $cms["config"]["admin.mod.php"]["menu"]["fonts"] ) ) {
            $cms["config"]["admin.mod.php"]["menu"]["fonts"] = array(
                "title"    => "fonts",
                "sort"     => 45,
                "section"  => "settings",
            );
            cms_save_config();
        }
        // Настройки шрифтов
        $cms["admin_pages"]["fonts"] = "
            <div class='panel settings'>
                <div class=variative>
                    <div class=title>" . __( "font_var" ) . "</div>
                    <p>" . __( "font_name" ) . "</p>
                    <input name=font type=text>
                    <p>" . __( "font_size" ) . "</p>
                    <input name=font-size type=text>
                </div>
                <div class=monotype>
                    <div class=title>" . __( "font_mono" ) . "</div>
                    <p>" . __( "font_name" ) . "</p>
                    <input name=font-mono type=text>
                    <p>" . __( "font_size" ) . "</p>
                    <input name=font-mono-size type=text>
                </div>
                <div class=button><span>Применить</span></div>
            </div>
            <div class='panel help'>" . __( "fonts_help" ) . "</div>
        ";
    }
   
    
    // PHP Info
    $conf = $cms["config"]["admin.mod.php"]["menu"]["phpinfo"];
    if ( empty( $conf["hide"] ) && empty( $cms["config"]["admin_sections"][ $conf["section"] ]["hide"] ) ) {
        if ( empty( $cms["config"]["admin.mod.php"]["menu"]["phpinfo"]["hide"] ) ) {
            ob_start();
            phpinfo();
            preg_match( "/<body>(.+)<\/body>/us", ob_get_clean(), $m );
            $page = preg_replace( "/(<a.*?)(href=\"#.*?\")(>)/", "$1$3", $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",
                    "hide"     => true,
                );
                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 = format_date( $cms["config"]["admin.mod.php"]["last_check"] );
        }
        $admin_menu = "
        <div class=header>

            <div class='panel update'>
                <div class=title>" . __( "update_core" ) . "</div>
                <div class=info>
                    <p>" . __( "current_v" ) . " {$cms['kernel_version']}</p>
                    <p>" . __( "compat" ) . " {$cms['kernel_compat']}</p>
                    <p>" . __( "last_v" ) . " {$last_version}</p>
                    <p>" . __( "checked" ) . " {$last_check}</p>
                    <p>" . __( "site_dir" ) . " {$cms["site_dir"]}</p>
                </div>
                <div class=buttons>
                    <button class='button check' data-fn=cms_check_update>
                        <span>" . __( "check" ) . "</span>
                    </button>
                    <button class='button update' data-fn=cms_update>
                        <span>" . __( "update" ) . "</span>
                    </button>
                    <button class='button upload'>
                        <span>
                            <input type=file name='myfile[]' multiple>
                            <span>" . __( "mod_install" ) . "</span>
                        </span>
                    </button>
                    <button class='button dev'>
                        <span>" . __( "for_dev" ) . "</span>
                    </button>
                </div>
            </div>

            <div class='panel dev hidden'>
                <div class=buttons>
                    <button class=button data-fn=cms_check_dev_update>
                        <span>" . __( "check_dev" ) . "</span>
                    </button>
                    <button class=button data-fn=cms_changed_files>
                        <span>" . __( "changed_files" ) . "</span>
                    </button>
                    <button class=button data-fn=cms_untracked_files>
                        <span>" . __( "untracked_files" ) . "</span>
                    </button>
                    <button class=button data-fn=create_zip>
                        <span>" . __( "create_zip" ) . "</span>
                    </button>
                </div>
            </div>

            <div class='panel files empty'></div>

        </div>
        <div class=modules>";

        // Сортировка модулей
        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=version>{$mod_cfg['version']}</div>";
            }
            // переводы имен модулей вынесены сюда чтобы не было варнингов при установке
            $admin_menu .= "
            <div class='panel module {$status}' data-module={$mod}>
                <div class='title name'>" . __( $mod_cfg['name'], $mod ) . "</div>
                {$mod_version}
                <div class=description>" . __( $mod_cfg['description'], $mod ) . "</div>
                <div class='button switch'>
                    <span>{$tr_sw}</span>
                </div>
                <div class='button del'>
                    <span>{$tr_del}</span>
                </div>
            </div>";
        }
        $admin_menu .= "</div>";

        // Пункт "Модули"
        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;
    }

    // Пункт "Справка/Книга"
    // Ссылка может меняться динамически в зависимости от языка
    if ( file_exists( $cms["cms_dir"] . "/man/" . $cms["config"]["locale"] . "/book.html" ) ) {
        $book_link = $cms["base_path"] . "man/" . $cms["config"]["locale"] . "/book.html";
        $cms["config"]["admin.mod.php"]["menu"]["book"] = array(
            "title"    => "book",
            "sort"     => 10000,
            "section"  => "help",
            "url"      => $book_link,
        );
    }


    // Пункт "Панель"
    if ( empty( $cms["config"]["admin.mod.php"]["menu"]["admin_menu"] ) ) {
        $cms["config"]["admin.mod.php"]["menu"]["admin_menu"] = array(
            "title"    => "admin_panel",
            "sort"     => 40,
            "section"  => "settings",
        );
        cms_save_config();
    }
    
    // Формирование бокового меню в админке
    cms_panel();

    $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=sections>";
        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;
            }
            // $section['title'] не бывает пустым, заполняется - ищи "// Пустой title у секции ..."
            $admin_menu .= "
<div class=section>
    
    <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']}' size=4>
        <div class=buttons>
            <div data-am-save class='button save'>
                <span>{$tr_save}</span>
            </div>
            <div data-am-delete class='button del'>
                <span>{$tr_del_section}</span>
            </div>
            <div data-am-sw class='button toggle'>
                <span>{$tr_sw}</span>
            </div>
        </div>
    </div>

    <div class='items' 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='item {$status}' data-am-type=item data-am-module={$item['module']} data-am-item={$iname}>
    <div class='title name'>" . __( $item["title"], $item["module"] ) . "</div>
    <input name=sort type=text value='{$item['sort']}' size=4>
    <div class='orders select widget'>
        <div class=selected data-section='{$section_name}'>
            <div class=value>{$cms['admin_sections'][$section_name]['title']}</div>
            <div class='button icon down'>
                <span></span>
            </div>
        </div>
        <div class=options>
            {$options}
        </div>
    </div>
    <div class=buttons>
        <div data-am-save class='button save'>
            <span>{$tr_save}</span>
        </div>
        <div data-am-save data-am-reset class='button reset'>
            <span>{$tr_reset}</span>
        </div>
        <div data-am-sw class='button toggle'>
            <span>{$tr_sw}</span>
        </div>
    </div>
</div>";
                }
            }

            $admin_menu .= "
    </div>
</div>";
        }


        $admin_menu .= "
</div>

<div class='footer'>
    <div class='add button'>
        <span>" . __( "add_section" ) . "</span>
    </div>
    <div class='reset button'>
        <span>" . __( "reset_all" ) . "</span>
    </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";

                // Выходим сами раз не передан параметр sess
                if ( empty( $_POST["sess"] ) ) {
                    cms_set_cookie( "sess", "", -1 );
                    $sess  = $_COOKIE["sess"];
                    $refresh = true;
                } else {
                    // Завершаем указанную сессию
                    $sess = $_POST["sess"];
                    if ( $_POST["sess"] === $_COOKIE["sess"] ) {
                        $refresh = true;
                    } else {
                        $refresh = false;
                    }
                }

                if ( ! isset($cms["config"]["logouted"]) ) {
                    $cms["config"]["logouted"] = array();
                }

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

                echo( json_encode( array(
                    "result" => array(
                        "notify_html"  => __( "logout_completed" )
                        . ": {$closed_sess["ip"]}, {$closed_sess["date"]}, {$closed_sess["user_agent"]}",
                        "refresh" => $refresh,
                    )
                ) ) );
                return;
            break;

            case "admin_menu_save":
                switch ( $_POST["type"] ) {
                    case "section":
                        $item   = $_POST["item"];
                        // Почему тут сплошные unset
                        // Тут используется unset как сброс в состояние когда необходима первичная инициализация
                        // ищи //:Локализация заголовка секции
                        if ( empty( $_POST["title"] ) ) {
                            unset ( $cms["config"]["admin_sections"][$item]["title"] );
                        } else {
                            $cms["config"]["admin_sections"][$item]["title"] = $_POST["title"];
                        }
                        // Чтобы выставить сортировку по умолчанию нужно стереть число в поле ввода,
                        // и unset приведет к тому, что это будет распознано и выставлено.
                        if ( empty( $_POST["sort"] ) ) {
                            unset( $cms["config"]["admin_sections"][$item]["sort"] );
                        } else {
                            $n = (int) $_POST["sort"];
                            $cms["config"]["admin_sections"][$item]["sort"]  = $n;
                        }
                    break;

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

                $saved = cms_save_config();
                
                if ( $saved ) {
                    echo( json_encode( array(
                        "result" => true
                    ) ) );
                } else {
                    echo( json_encode( array(
                        "error" => array(
                            "notify_html" => __( "config_save_error" ),
                        )
                    ) ) );
                }
                return;
            break;

            case "admin_menu_hide":
                $module = $_POST["module"];
                $item   = $_POST["item"];
                $hide = $_POST["hide"] === "true";
                switch ( $_POST["type"] ) {
                    case "section":
                        $cms["config"]["admin_sections"][$item]["hide"] = $hide;
                        break;
                    case "item":
                        $cms["config"][$module]["menu"][$item]["hide"] = $hide;
                        break;
                }

                $saved = cms_save_config();
                
                if ( $saved ) {
                    echo( json_encode( array(
                        "result" => true
                    ) ) );
                } else {
                    echo( json_encode( array(
                        "error" => array(
                            "notify_html" => __( "config_save_error" ),
                        )
                    ) ) );
                }
                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,
                );

                $saved = cms_save_config();

                if ( $saved ) {
                    echo( json_encode( array(
                        "result" => array(
                            "notify_html"  => __( "section_added" ),
                        )
                    ) ) );
                } else {
                    echo( json_encode( array(
                        "error" => array(
                            "notify_html" => __( "config_save_error" ),
                        )
                    ) ) );
                }
                return;
            break;

            case "admin_menu_del":
                $cms["status"] = "200";
                $item = $_POST["item"];
                unset( $cms["config"]["admin_sections"][$item] );

                $saved = cms_save_config();

                if ( $saved ) {
                    echo( json_encode( array(
                        "result" => array(
                            "notify_html"  => __( "section_deleted" ),
                        )
                    ) ) );
                } else {
                    echo( json_encode( array(
                        "error" => array(
                            "notify_html" => __( "config_save_error" ),
                        )
                    ) ) );
                }
                return;
            break;

            case "module_disable":
                $module = $_POST["module"];

                if ( $module == "admin.mod.php" || $module == "template.mod.php" ) {
                    echo( json_encode( array(
                        "error" => array(
                            "notify_html"  => str_replace( "{name}", $module, __( "not_disabled" ) ),
                        )
                    ) ) );
                    return;
                }

                // Хоть и было отправлено true, но получаем строкой
                if ( $_POST["disable"] === "true" ) {
                    $cms["config"][$module]["disabled"] = true;
                    $msg = __( "module_disabled" );
                } else {
                    unset( $cms["config"][$module]["disabled"] );
                    $msg = __( "module_enabled" );
                }

                $saved = cms_save_config();

                if ( $saved ) {
                    echo( json_encode( array(
                        "result" => array(
                            "notify_html"  => str_replace( "{name}", $module, $msg ),
                        )
                    ) ) );
                } else {
                    echo( json_encode( array(
                        "error" => array(
                            "notify_html" => __( "config_save_error" ),
                        )
                    ) ) );
                }
                return;
            break;

            case "module_del":
                $text = "<div class=del_msg>" . __( "delete_this_files" ) . "</div>";

                foreach( $cms["modules"][$_POST["module"]]["files"] as $file ) {
                    $text .= "<div class=del_file>{$file}</div>";
                }

                echo( json_encode( array(
                    "result" => array(
                        "notify_html"  => $text,
                    )
                ) ) );
                return;
            break;

            case "install_module":
                // Загружать можно несколько файлов.
                // Любая ошибка сделает false.
                $success = true;

                foreach ( $_FILES["files"]["name"] as $n => $name ) {
                    if ( $_FILES["files"]["error"][$n] ) {
                        $success = false;
                        $text = str_replace( "xxx", $name, __( "upload_error_xxx" ) );
                        break;
                    } else {
                        $zip = new ZipArchive;
                        $zip_file = $_FILES["files"]["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 {
                                // Если обновляется CMS через закидывание через архив (несовместимая версия)
                                // TODO: возвращает [ "result" ]
                                cms_exec_sql_file( "{$cms['cms_dir']}/update.sql" );
                                $php_update_file = "{$cms['cms_dir']}/update.php";
                                if ( is_file( $php_update_file ) ) {
                                    include( $php_update_file );
                                }

                                // Загрузка модулей чтобы вызвать хук создания таблиц в БД
                                // В одном 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 ) );
                                    // Создать таблицы в БД. Ничего страшного от множенственного вызова.
                                    do_hook( "create_tables" );
                                    // Обновить таблицы в БД.
                                    // TODO: возвращает [ "result" ]
                                    cms_exec_sql_file( "{$cms['cms_dir']}/mod/{$module_name}.update.sql" );
                                }

                                $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();
                    }
                    
                    echo( json_encode( array(
                        "result" => array(
                            "notify_html"  => $text,
                        )
                    ) ) );
                } else {
                    echo( json_encode( array(
                        "error" => array(
                            "notify_html"  => $text,
                        )
                    ) ) );
                }
            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["site_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( json_encode( array( "result" => true ) ) );
                return;
            break;

            case "reset_panel_items":
                reset_panel_items();
                echo( json_encode( array(
                    "result" => array(
                        "notify_html"  => __( "reset_all_ok" ),
                    )
                ) ) );
                return;
            break;

            case "create_zip":
                cms_update_create_zip();
            break;

            case "cms_check_update":
                cms_update_check( "https://coffee-cms.ru/update-{$cms['kernel_compat']}.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":
                echo( json_encode( cms_changed_files() ) );
            break;
            
            case "cms_untracked_files":
                echo( json_encode( cms_untracked_files() ) );
            break;
        }
        
    }
}


function reset_panel_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 = "";

    $cms["modules"]["kernel"] = array( "files" => array(
        "LICENSE",
        ".cms/index.php",
        ".cms/index.fn.php",
        ".cms/update.sql",
        ".cms/update.php",
        "uploads/.keep",
        ".htaccess",
        ".nginx.conf",
        ".cms/filelist.php",
        ".agents/rules/coffee-cms-module-developer.md",
    ) );

    $cms["modules"]["mini"] = array( "files" => array(
        ".cms/mini/banner.jpg",
        ".cms/mini/blog.php",
        ".cms/mini/favicon.svg",
        ".cms/mini/frontpage.php",
        ".cms/mini/html.php",
        ".cms/mini/instruction.ru_RU.UTF-8.html",
        ".cms/mini/page.php",
        ".cms/mini/page.teaser.php",
        ".cms/mini/post.php",
        ".cms/mini/styles.css",
        ".cms/mini/tpl.mod.php",
        ".cms/mini/example.mixin.less",
        ".cms/mini/example.less",
    ) );

    // Добавить файлы и папки запрашивая их у модулей по списку
    $module_list = array(
        "admin.mod.php",
        "base.mod.php",
        "menu.mod.php",
        "pages.mod.php",
        "sitemap.mod.php",
        "rss.mod.php",
        "template.mod.php",
        "kernel",
        "mini",
        "ai.mod.php",
        "comments.mod.php",
        "ecs.mod.php",
    );
    $queue = array();
    foreach ( $module_list as $mod ) {
        $files = $cms["modules"][$mod]["files"];
        foreach ( $files as $file ) {
            $file_path = $cms["site_dir"] . "/" . $file;
            if ( ! in_array( $file_path, $queue ) ) {
                array_push( $queue, $file_path );
            }
        }
    }

    $no_sha256 = 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_sha256 ) ) {
                $sha256 = "";
            } else {
                $sha256 = hash_file( "sha256", $cur );
            }
            $file = str_replace( "{$cms['site_dir']}/", "", $cur );
            $list[$file] = $sha256;
        } else {
            $logs .= "<div>" . __( "file_or_dir_not_exists" ) . " '{$cur}'</div>";
        }
    }

    // Список файлов которые разрешено редактировать.
    // И если они отредактированы, то не будут обновляться.
    $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 .= "<div>" . __( "arch_files" ) . count( $list ) . "</div>";

    return $logs;
    
}


function cms_update_check( $url ) {
    global $cms;

    // Чтобы не было варнинга в логах
    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(
            "error" => array(
                "notify_html"  => $msg,
            )
        ) ) );
        return;
    }

    if ( ! $update = file_get_contents( $url ) ) {
        echo( json_encode( array(
            "error" => array(
                "notify_html"  => __( "cant_get" ) . " " . $url,
            )
        ) ) );
        return;
    }

    if ( ! $update = json_decode( $update, true ) ) {
        echo( json_encode( array(
            "error" => array(
                "notify_html"  => __( "json_error" ) . " " . $url,
            )
        ) ) );
        return;
    }

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

    $saved = cms_save_config();

    if ( ! $saved ) {
        echo( json_encode( array(
            "error" => array(
                "notify_html" => __( "config_save_error" ),
            )
        ) ) );
        return;
    }

    $last_check = format_date( $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>
        <p>" . __( "compat" ) . " {$cms['kernel_compat']}</p>
        <p>" . __( "last_v" ) . " {$last_version}</p>
        <p>" . __( "checked" ) . " {$last_check}</p>
        <p>" . __( "site_dir" ) . " {$cms["site_dir"]}</p>
    ";

    echo( json_encode( array(
        "result" => array(
            "notify_html"  => __( "last_v" ) . " {$last_version}",
            "update_info"     => $update_info,
        )
    ) ) );
    return;

}


function cms_changed_files() {
    global $cms;

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

    $errors = array();

    // Проверка файлов текущей версии
    // по чексуммам и размерам
    foreach( $list as $fname => $hash ) {
        $file = $cms["site_dir"] . "/" . $fname;
        if ( ! empty( $hash ) && ! in_array( $fname, $allow_change ) ) {
            if ( ! is_file( $file ) ) {
                $error = "<div>" . __( "file_not_exists" ) . " " . $file . "</div>";
                array_push( $errors, $error );
            } else {
                $sha256 = hash_file( "sha256", $file );
                if ( ! $sha256 ) {
                    $error = "<div>" . __( "function hash_file() error" ) . " " . $file . "</div>";
                    array_push( $errors, $error );
                } elseif ( $hash !== $sha256 ) {
                    $error = "<div>" . __( "file_changed" ) . " " . $file . "</div>";
                    array_push( $errors, $error );
                }
            }
        }
    }

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

function cms_untracked_files() {
    global $cms;

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

    $errors = array();

    // Составление списка файлов
    $skip = mb_strlen( $cms["site_dir"] ) + 1;
    $queue = array( "{$cms["site_dir"]}/.cms" );
    while ( $cur = array_shift( $queue ) ) {
        if ( is_dir( $cur ) ) {
            // Получаем все файлы, включая скрытые, исключая . и ..
            $items = array_diff(
                glob( $cur . "/{*,.*}", GLOB_BRACE ),
                [ "{$cur}/.", "{$cur}/.." ]
            );
            $queue = array_merge( $queue, $items );
        } else {
            $index = mb_substr( $cur, $skip );
            if ( ! isset( $list[$index] ) ) {
                array_push( $errors, "<div>{$cur}</div>" );
            }
        }
    }    

    // Возвращаем список недочетов
    $count = count( $errors );
    if ( $count ) {
        $files = "<div>files count: {$count}</div>"
        . implode( "", $errors );
    } else {
        $files = __( "no_files_untracked" );
    }
    return array(
        "result" => $files,
    );
}

function cms_update_cms_update() {
    global $cms;

    // Не проверялись обновления вообще
    if ( empty( $cms["config"]["admin.mod.php"]["update"] ) ) {
        echo( json_encode( array(
            "error" => array(
                "notify_html"  => __( "check_updates" ),
            )
        ) ) );
        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(
            "error" => array(
                "notify_html"  => $msg,
            )
        ) ) );
        return;
    }

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

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

    // Может нечего обновлять
    // Самообновление dev.coffee-cms.ru так же предотвращается этим кодом
    if ( $update["last_version"] <= $cms["kernel_version"] ) {
        echo( json_encode( array(
            "result" => array(
                "notify_html"  => __( "already_updated" ),
            )
        ) ) );
        return;
    }

    // Может несовместимая версия
    if ( isset( $cms["config"]["check_compat"] ) && $cms["config"]["check_compat"] === false ) {
        // Не проверяем на совместимость
    } else {
        if ( $update["compat"] !== $cms["kernel_compat"] ) {
            echo( json_encode( array(
                "error" => array(
                    "notify_html"  => __( "impossible" ),
                )
            ) ) );
            return;
        }
    }
    
    // Копить по 10 ошибок
    // чтобы не мучить по одной
    $errors = array();
    
    require( $fl );
    // Проверка файлов текущей версии
    // по чексуммам и размерам
    foreach( $list as $rel_file => $hash ) {
        $abs_file = $cms["site_dir"] . "/" . $rel_file;
        if ( ! empty( $hash ) && ! in_array( $rel_file, $allow_change ) ) {
            if ( ! is_file( $abs_file ) ) {
                array_push( $errors, __( "file_not_exists" ) . " " . $abs_file );
            } else {
                $sha256 = hash_file( "sha256", $abs_file );
                if ( ! $sha256 ) {
                    array_push( $errors, __( "function hash_file() error" ) . " " . $abs_file );
                } elseif ( $hash !== $sha256 ) {
                    array_push( $errors, __LINE__ . ": " . __( "file_changed" ) . " " . $abs_file );
                }
            }
        }
    }

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

    // Проверка прав на запись
    $writeable_errors = array();
    foreach( $list as $rel_file => $hash ) {
        $abs_file = $cms["site_dir"] . "/" . $rel_file;
        $e = ! is_file( $abs_file ) || ! is_writable( $abs_file );
        if ( $e ) {
            array_push( $writeable_errors, $abs_file );
        }
    }

    // Возвращаем список
    if ( count( $writeable_errors ) ) {
        echo( json_encode( array(
            "error" => array(
                "notify_html"  => __( "update_error_rights" ) . "<br>" . implode( "<br>", $writeable_errors ),
            )
        ) ) );
        return;
    }

    // Download New Version
    if ( ! $content = file_get_contents( $update["download"] ) ) {
        echo( json_encode( array(
            "error" => array(
                "notify_html"  => __( "download_error" ),
            )
        ) ) );
        return;
    }
    $tmp = $cms["site_dir"] . "/.tmp";
    if ( is_dir( $tmp ) ) {
        recurse_rm( $tmp );
    }
    if ( ! mkdir( $tmp ) ) {
        echo( json_encode( array(
            "error" => array(
                "notify_html"  => __( "cant_create_tmp_dir" ) . " " . $tmp,
            )
        ) ) );
        return;
    }
    $fn = preg_replace( "/.*\//u", "", $update["download"] );
    $file = $tmp . "/" . $fn;
    if ( ! file_put_contents( $file, $content ) ) {
        echo( json_encode( array(
            "error" => array(
                "notify_html"  => __( "cant_write_file" ) . " " . $file,
            )
        ) ) );
        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(
                "error" => array(
                    "notify_html"  => __( "cant_extract" ) . " " . $file,
                )
            ) ) );
            return;
        }
    } else {
        echo( json_encode( array(
            "error" => array(
                "notify_html"  => __( "cant_open_zip" ) . " " . $file,
            )
        ) ) );
        return;
    }

    // Check New Version
    $fl = "{$tmp}/.cms/filelist.php";
    if ( ! is_file( $fl ) ) {
        echo( json_encode( array(
            "error" => array(
                "notify_html"  => __( "file_list_missing" ) . " " . $fl,
            )
        ) ) );
        return;
    }
    $oldlist = $list;
    require( $fl );
    foreach( $list as $fname => $hash ) {
        $file = $tmp . "/" . $fname;
        if ( ! is_file( $file ) ) {
            echo( json_encode( array(
                "error" => array(
                    "notify_html"  => __( "file_not_exists" ) . " " . $file,
                )
            ) ) );
            return;
        }
        if ( $hash !== "" && $hash !== hash_file( "sha256", $file ) ) {
            echo( json_encode( array(
                "error" => array(
                    "notify_html"  => __LINE__ . ": " . __( "file_changed" ) . " " . $file,
                )
            ) ) );
            return;
        }
    }

    // Удаление файлов текущей цмс
    $removed = true;
    foreach( $oldlist as $fname => $hash ) {
        $from = $cms["site_dir"] . "/" . $fname;
        
        if ( in_array( $fname, $allow_change ) && hash_file( "sha256", $from ) !== $oldlist[$fname] ) {
            // Если файл в списке разрешенных на изменение
            // и sha256 не совпадает, то оставляем его не тронутым
        } 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(
            "error" => array(
                "notify_html"  => __( "update_error_remove" ),
            )
        ) ) );
        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
            hash_file( "sha256", $old_file_path ) !== $oldlist[$file_name]
        ) {
            // Если файл в списке разрешенных на изменение
            // и sha256 не совпадает, то оставляем его не тронутым
        } 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(
            "error" => array(
                "notify_html"  => __( "update_error_move" ),
            )
        ) ) );
        return;
    }

    recurse_rm( $tmp );

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

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

    // Обновить что-то еще с помощью php-кода
    $update_config = "{$cms['cms_dir']}/update.php";
    if ( is_file( $update_config ) ) {
        include( $update_config );
    }

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

    // if ( ! $saved ) {
    //     echo( json_encode( array(
    //         "error" => array(
    //             "notify_html" => __( "config_save_error" ),
    //         )
    //     ) ) );
    //     return;
    // }

    if ( function_exists( "cms_clear_cache" ) ) {
        cms_clear_cache();
    }

    echo( json_encode( array(
        "result" => array(
            "notify_html"  => __( "successfull_update" ),
            "reload"     => true,
        )
    ) ) );
    return;

}


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

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

    $lines_count = 0;
    // $list определяется в filelist.php
    foreach( $list as $file => $hash ) {
        $path = "{$cms['site_dir']}/{$file}";
        if (
            mb_strpos( $file, ".cms/lib/" ) === 0 ||
            mb_strpos( $file, ".cms/img/" ) === 0 ||
            mb_strpos( $file, ".cms/man/css/" ) === 0 ||
            mb_strpos( $file, ".cms/man/js/" ) === 0 ||
            preg_match( "|.cms/css/.*\.css|", $file )
        ) {
            // Если найдено - пропускаем
        } else {
            if (
                mb_substr( $file, -4 ) === ".php" ||
                mb_substr( $file, -4 ) === ".css" ||
                mb_substr( $file, -3 ) === ".js" ||
                mb_substr( $file, -5 ) === ".html" ||
                mb_substr( $file, -5 ) === ".less"
            ) {
                // $lines_count += count( file( $path ) );
                foreach ( file( $path ) as $line ) {
                    if ( trim( $line ) ) {
                        $lines_count += 1;
                    }
                }
            }
        }
        $zip->addFile( $path, $file );
    }

    $zip->close();

    file_put_contents( "{$cms["site_dir"]}/update_dev.json", json_encode( array(
        // Чтобы четко совпадало и не самообновлялось
        "last_version" => $cms["kernel_version"],
        "compat"       => $cms["kernel_compat"],
        "download"     => $download_url,
    ) ) );

    $created = __( "archive_created" );
    $link = "<a href='{$download_url}'>{$download_url}</a>";

    $count = __( "lines_count" );

    $html = "
        <div>{$created}: {$link}</div>
        {$files}
        <div>{$count}: {$lines_count}</div>
    ";

    echo( json_encode( array(
        "result" => array(
            "notify_html" => $html,
        )
    ) ) );
    return;
}
