Mercurial
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; +}