first commit 2
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
define('G5_IS_ADMIN', true);
|
||||
require_once '../common.php';
|
||||
require_once G5_ADMIN_PATH . '/admin.lib.php';
|
||||
|
||||
if (isset($token)) {
|
||||
$token = @htmlspecialchars(strip_tags($token), ENT_QUOTES);
|
||||
}
|
||||
|
||||
run_event('admin_common');
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$is_use_apache = (stripos($_SERVER['SERVER_SOFTWARE'], 'apache') !== false);
|
||||
|
||||
$is_use_nginx = (stripos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false);
|
||||
|
||||
$is_use_iis = !$is_use_apache && (stripos($_SERVER['SERVER_SOFTWARE'], 'microsoft-iis') !== false);
|
||||
|
||||
$is_write_file = false;
|
||||
$is_apache_need_rules = false;
|
||||
$is_apache_rewrite = false;
|
||||
|
||||
if (!($is_use_apache || $is_use_nginx || $is_use_iis)) { // 셋다 아니면 다 출력시킨다.
|
||||
$is_use_apache = true;
|
||||
$is_use_nginx = true;
|
||||
}
|
||||
|
||||
if ($is_use_nginx) {
|
||||
$is_write_file = false;
|
||||
}
|
||||
|
||||
if ($is_use_apache) {
|
||||
$is_write_file = (is_writable(G5_PATH) || (file_exists(G5_PATH . '/.htaccess') && is_writable(G5_PATH . '/.htaccess'))) ? true : false;
|
||||
$is_apache_need_rules = check_need_rewrite_rules();
|
||||
$is_apache_rewrite = function_exists('apache_get_modules') && in_array('mod_rewrite', apache_get_modules());
|
||||
}
|
||||
|
||||
$get_path_url = parse_url(G5_URL);
|
||||
|
||||
$base_path = isset($get_path_url['path']) ? $get_path_url['path'] . '/' : '/';
|
||||
|
||||
// add_stylesheet('css 구문', 출력순서); 숫자가 작을 수록 먼저 출력됨
|
||||
add_stylesheet('<link rel="stylesheet" href="' . G5_JS_URL . '/remodal/remodal.css">', 11);
|
||||
add_stylesheet('<link rel="stylesheet" href="' . G5_JS_URL . '/remodal/remodal-default-theme.css">', 12);
|
||||
add_javascript('<script src="' . G5_JS_URL . '/remodal/remodal.js"></script>', 10);
|
||||
?>
|
||||
<section id="anc_cf_url">
|
||||
<h2 class="h2_frm">짧은 주소 설정</h2>
|
||||
<?php echo $pg_anchor ?>
|
||||
<div class="local_desc02 local_desc">
|
||||
<p>
|
||||
게시판과 컨텐츠 페이지에 짧은 URL 을 사용합니다. <a href="https://sir.kr/manual/g5/286" class="btn btn_03" target="_blank" style="margin-left:10px">설정 관련 메뉴얼 보기</a>
|
||||
<?php if ($is_use_apache && !$is_use_nginx) { ?>
|
||||
<?php if (!$is_apache_rewrite) { ?>
|
||||
<br><strong>Apache 서버인 경우 rewrite_module 이 비활성화 되어 있으면 짧은 주소를 사용할수 없습니다.</strong>
|
||||
<?php } elseif (!$is_write_file && $is_apache_need_rules) { // apache인 경우 ?>
|
||||
<br><strong>짧은 주소 사용시 아래 Apache 설정 코드를 참고하여 설정해 주세요.</strong>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="server_config_views">
|
||||
<?php if ($is_use_apache) { ?>
|
||||
<button type="button" data-remodal-target="modal_apache" class="btn btn_03">Apache 설정 코드 보기</button>
|
||||
<?php } ?>
|
||||
<?php if ($is_use_nginx) { ?>
|
||||
<button type="button" data-remodal-target="modal_nginx" class="btn btn_03">Nginx 설정 코드 보기</button>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<div class="tbl_frm01 tbl_wrap">
|
||||
<table>
|
||||
<caption>짧은주소 설정</caption>
|
||||
<colgroup>
|
||||
<col class="grid_4">
|
||||
<col>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<?php
|
||||
$short_url_arrs = array(
|
||||
'0' => array('label' => '사용안함', 'url' => G5_URL . '/board.php?bo_table=free&wr_id=123'),
|
||||
'1' => array('label' => '숫자', 'url' => G5_URL . '/free/123'),
|
||||
'2' => array('label' => '글 이름', 'url' => G5_URL . '/free/안녕하세요/'),
|
||||
);
|
||||
foreach ($short_url_arrs as $k => $v) {
|
||||
$checked = ((int) $config['cf_bbs_rewrite'] === (int) $k) ? 'checked' : '';
|
||||
?>
|
||||
<tr>
|
||||
<td><input name="cf_bbs_rewrite" id="cf_bbs_rewrite_<?php echo $k; ?>" type="radio" value="<?php echo $k; ?>" <?php echo $checked; ?>><label for="cf_bbs_rewrite_<?php echo $k; ?>" class="rules_label"><?php echo $v['label']; ?></label></td>
|
||||
<td><?php echo $v['url']; ?></td>
|
||||
</tr>
|
||||
<?php } //end foreach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="server_rewrite_info">
|
||||
<div class="is_rewrite remodal" data-remodal-id="modal_apache" role="dialog" aria-labelledby="modalApache" aria-describedby="modal1Desc">
|
||||
|
||||
<button type="button" class="connect-close" data-remodal-action="close">
|
||||
<i class="fa fa-close"></i>
|
||||
<span class="txt">닫기</span>
|
||||
</button>
|
||||
|
||||
<h4 class="copy_title">.htaccess 파일에 적용할 코드입니다.
|
||||
<?php if (!$is_apache_rewrite) { ?>
|
||||
<br><span class="info-warning">Apache 서버인 경우 rewrite_module 이 비활성화 되어 있으면 짧은 주소를 사용할수 없습니다.</span>
|
||||
<?php } elseif (!$is_write_file && $is_apache_need_rules) { ?>
|
||||
<br><span class="info-warning">자동으로 .htaccess 파일을 수정 할수 있는 권한이 없습니다.<br>.htaccess 파일이 없다면 생성 후에, 아래 코드가 없으면 코드를 복사하여 붙여넣기 해 주세요.</span>
|
||||
<?php } elseif (!$is_apache_need_rules) { ?>
|
||||
<br><span class="info-success">정상적으로 적용된 상태입니다.</span>
|
||||
<?php } ?>
|
||||
</h4>
|
||||
<textarea readonly="readonly" rows="10"><?php echo get_mod_rewrite_rules(true); ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="is_rewrite remodal" data-remodal-id="modal_nginx" role="dialog" aria-labelledby="modalNginx" aria-describedby="modal2Desc">
|
||||
|
||||
<button type="button" class="connect-close" data-remodal-action="close">
|
||||
<i class="fa fa-close"></i>
|
||||
<span class="txt">닫기</span>
|
||||
</button>
|
||||
<h4 class="copy_title">아래 코드를 복사하여 nginx 설정 파일에 적용해 주세요.</h4>
|
||||
<textarea readonly="readonly" rows="10"><?php echo get_nginx_conf_rules(true); ?></textarea>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$g5_debug['php']['begin_time'] = $begin_time = get_microtime();
|
||||
|
||||
$files = glob(G5_ADMIN_PATH . '/css/admin_extend_*');
|
||||
if (is_array($files)) {
|
||||
foreach ((array) $files as $k => $css_file) {
|
||||
|
||||
$fileinfo = pathinfo($css_file);
|
||||
$ext = $fileinfo['extension'];
|
||||
|
||||
if ($ext !== 'css') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$css_file = str_replace(G5_ADMIN_PATH, G5_ADMIN_URL, $css_file);
|
||||
add_stylesheet('<link rel="stylesheet" href="' . $css_file . '">', $k);
|
||||
}
|
||||
}
|
||||
|
||||
require_once G5_PATH . '/head.sub.php';
|
||||
|
||||
function print_menu1($key, $no = '')
|
||||
{
|
||||
global $menu;
|
||||
|
||||
$str = print_menu2($key, $no);
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
function print_menu2($key, $no = '')
|
||||
{
|
||||
global $menu, $auth_menu, $is_admin, $auth, $g5, $sub_menu;
|
||||
|
||||
$str = "<ul>";
|
||||
for ($i = 1; $i < count($menu[$key]); $i++) {
|
||||
if (!isset($menu[$key][$i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($is_admin != 'super' && (!array_key_exists($menu[$key][$i][0], $auth) || !strstr($auth[$menu[$key][$i][0]], 'r'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$gnb_grp_div = $gnb_grp_style = '';
|
||||
|
||||
if (isset($menu[$key][$i][4])) {
|
||||
if (($menu[$key][$i][4] == 1 && $gnb_grp_style == false) || ($menu[$key][$i][4] != 1 && $gnb_grp_style == true)) {
|
||||
$gnb_grp_div = 'gnb_grp_div';
|
||||
}
|
||||
|
||||
if ($menu[$key][$i][4] == 1) {
|
||||
$gnb_grp_style = 'gnb_grp_style';
|
||||
}
|
||||
}
|
||||
|
||||
$current_class = '';
|
||||
|
||||
if ($menu[$key][$i][0] == $sub_menu) {
|
||||
$current_class = ' on';
|
||||
}
|
||||
|
||||
$str .= '<li data-menu="' . $menu[$key][$i][0] . '"><a href="' . $menu[$key][$i][2] . '" class="gnb_2da ' . $gnb_grp_style . ' ' . $gnb_grp_div . $current_class . '">' . $menu[$key][$i][1] . '</a></li>';
|
||||
|
||||
$auth_menu[$menu[$key][$i][0]] = $menu[$key][$i][1];
|
||||
}
|
||||
$str .= "</ul>";
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
$adm_menu_cookie = array(
|
||||
'container' => '',
|
||||
'gnb' => '',
|
||||
'btn_gnb' => '',
|
||||
);
|
||||
|
||||
if (!empty($_COOKIE['g5_admin_btn_gnb'])) {
|
||||
$adm_menu_cookie['container'] = 'container-small';
|
||||
$adm_menu_cookie['gnb'] = 'gnb_small';
|
||||
$adm_menu_cookie['btn_gnb'] = 'btn_gnb_open';
|
||||
}
|
||||
?>
|
||||
|
||||
<script>
|
||||
var g5_admin_csrf_token_key = "<?php echo (function_exists('admin_csrf_token_key')) ? admin_csrf_token_key() : ''; ?>";
|
||||
var tempX = 0;
|
||||
var tempY = 0;
|
||||
|
||||
function imageview(id, w, h) {
|
||||
|
||||
menu(id);
|
||||
|
||||
var el_id = document.getElementById(id);
|
||||
|
||||
//submenu = eval(name+".style");
|
||||
submenu = el_id.style;
|
||||
submenu.left = tempX - (w + 11);
|
||||
submenu.top = tempY - (h / 2);
|
||||
|
||||
selectBoxVisible();
|
||||
|
||||
if (el_id.style.display != 'none')
|
||||
selectBoxHidden(id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="to_content"><a href="#container">본문 바로가기</a></div>
|
||||
|
||||
<header id="hd">
|
||||
<h1><?php echo $config['cf_title'] ?></h1>
|
||||
<div id="hd_top">
|
||||
<button type="button" id="btn_gnb" class="btn_gnb_close <?php echo $adm_menu_cookie['btn_gnb']; ?>">메뉴</button>
|
||||
<div id="logo"><a href="<?php echo correct_goto_url(G5_ADMIN_URL); ?>"><img src="<?php echo G5_ADMIN_URL ?>/img/logo.png" alt="<?php echo get_text($config['cf_title']); ?> 관리자"></a></div>
|
||||
|
||||
<div id="tnb">
|
||||
<ul>
|
||||
<?php if (defined('G5_USE_SHOP') && G5_USE_SHOP) { ?>
|
||||
<li class="tnb_li"><a href="<?php echo G5_SHOP_URL ?>/" class="tnb_shop" target="_blank" title="쇼핑몰 바로가기">쇼핑몰 바로가기</a></li>
|
||||
<?php } ?>
|
||||
<li class="tnb_li"><a href="<?php echo G5_URL ?>/" class="tnb_community" target="_blank" title="커뮤니티 바로가기">커뮤니티 바로가기</a></li>
|
||||
<li class="tnb_li"><a href="<?php echo G5_ADMIN_URL ?>/service.php" class="tnb_service">부가서비스</a></li>
|
||||
<li class="tnb_li"><button type="button" class="tnb_mb_btn">관리자<span class="./img/btn_gnb.png">메뉴열기</span></button>
|
||||
<ul class="tnb_mb_area">
|
||||
<li><a href="<?php echo G5_ADMIN_URL ?>/member_form.php?w=u&mb_id=<?php echo $member['mb_id'] ?>">관리자정보</a></li>
|
||||
<li id="tnb_logout"><a href="<?php echo G5_BBS_URL ?>/logout.php">로그아웃</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<nav id="gnb" class="gnb_large <?php echo $adm_menu_cookie['gnb']; ?>">
|
||||
<h2>관리자 주메뉴</h2>
|
||||
<ul class="gnb_ul">
|
||||
<?php
|
||||
$jj = 1;
|
||||
foreach ($amenu as $key => $value) {
|
||||
$href1 = $href2 = '';
|
||||
|
||||
if (isset($menu['menu' . $key][0][2]) && $menu['menu' . $key][0][2]) {
|
||||
$href1 = '<a href="' . $menu['menu' . $key][0][2] . '" class="gnb_1da">';
|
||||
$href2 = '</a>';
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
$current_class = "";
|
||||
if (isset($sub_menu) && (substr($sub_menu, 0, 3) == substr($menu['menu' . $key][0][0], 0, 3))) {
|
||||
$current_class = " on";
|
||||
}
|
||||
|
||||
$button_title = $menu['menu' . $key][0][1];
|
||||
?>
|
||||
<li class="gnb_li<?php echo $current_class; ?>">
|
||||
<button type="button" class="btn_op menu-<?php echo $key; ?> menu-order-<?php echo $jj; ?>" title="<?php echo $button_title; ?>"><?php echo $button_title; ?></button>
|
||||
<div class="gnb_oparea_wr">
|
||||
<div class="gnb_oparea">
|
||||
<h3><?php echo $menu['menu' . $key][0][1]; ?></h3>
|
||||
<?php echo print_menu1('menu' . $key, 1); ?>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<?php
|
||||
$jj++;
|
||||
} //end foreach
|
||||
?>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</header>
|
||||
<script>
|
||||
jQuery(function($) {
|
||||
|
||||
var menu_cookie_key = 'g5_admin_btn_gnb';
|
||||
|
||||
$(".tnb_mb_btn").click(function() {
|
||||
$(".tnb_mb_area").toggle();
|
||||
});
|
||||
|
||||
$("#btn_gnb").click(function() {
|
||||
|
||||
var $this = $(this);
|
||||
|
||||
try {
|
||||
if (!$this.hasClass("btn_gnb_open")) {
|
||||
set_cookie(menu_cookie_key, 1, 60 * 60 * 24 * 365);
|
||||
} else {
|
||||
delete_cookie(menu_cookie_key);
|
||||
}
|
||||
} catch (err) {}
|
||||
|
||||
$("#container").toggleClass("container-small");
|
||||
$("#gnb").toggleClass("gnb_small");
|
||||
$this.toggleClass("btn_gnb_open");
|
||||
|
||||
});
|
||||
|
||||
$(".gnb_ul li .btn_op").click(function() {
|
||||
$(this).parent().addClass("on").siblings().removeClass("on");
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<div id="wrapper">
|
||||
|
||||
<div id="container" class="<?php echo $adm_menu_cookie['container']; ?>">
|
||||
|
||||
<h1 id="container_title"><?php echo $g5['title'] ?></h1>
|
||||
<div class="container_wr">
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
function check_all(f)
|
||||
{
|
||||
var chk = document.getElementsByName("chk[]");
|
||||
|
||||
for (i=0; i<chk.length; i++)
|
||||
chk[i].checked = f.chkall.checked;
|
||||
}
|
||||
|
||||
function btn_check(f, act)
|
||||
{
|
||||
if (act == "update") // 선택수정
|
||||
{
|
||||
f.action = list_update_php;
|
||||
str = "수정";
|
||||
}
|
||||
else if (act == "delete") // 선택삭제
|
||||
{
|
||||
f.action = list_delete_php;
|
||||
str = "삭제";
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
var chk = document.getElementsByName("chk[]");
|
||||
var bchk = false;
|
||||
|
||||
for (i=0; i<chk.length; i++)
|
||||
{
|
||||
if (chk[i].checked)
|
||||
bchk = true;
|
||||
}
|
||||
|
||||
if (!bchk)
|
||||
{
|
||||
alert(str + "할 자료를 하나 이상 선택하세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (act == "delete")
|
||||
{
|
||||
if (!confirm("선택한 자료를 정말 삭제 하시겠습니까?"))
|
||||
return;
|
||||
}
|
||||
|
||||
f.submit();
|
||||
}
|
||||
|
||||
function is_checked(elements_name)
|
||||
{
|
||||
var checked = false;
|
||||
var chk = document.getElementsByName(elements_name);
|
||||
for (var i=0; i<chk.length; i++) {
|
||||
if (chk[i].checked) {
|
||||
checked = true;
|
||||
}
|
||||
}
|
||||
return checked;
|
||||
}
|
||||
|
||||
function delete_confirm(el)
|
||||
{
|
||||
if(confirm("한번 삭제한 자료는 복구할 방법이 없습니다.\n\n정말 삭제하시겠습니까?")) {
|
||||
var token = get_ajax_token();
|
||||
var href = el.href.replace(/&token=.+$/g, "");
|
||||
if(!token) {
|
||||
alert("토큰 정보가 올바르지 않습니다.");
|
||||
return false;
|
||||
}
|
||||
el.href = href+"&token="+token;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function delete_confirm2(msg)
|
||||
{
|
||||
if(confirm(msg))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
function get_ajax_token()
|
||||
{
|
||||
var token = "",
|
||||
admin_csrf_token_key = (typeof g5_admin_csrf_token_key !== "undefined") ? g5_admin_csrf_token_key : "";
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: g5_admin_url+"/ajax.token.php",
|
||||
data : {admin_csrf_token_key:admin_csrf_token_key},
|
||||
cache: false,
|
||||
async: false,
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
if(data.error) {
|
||||
alert(data.error);
|
||||
if(data.url)
|
||||
document.location.href = data.url;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
token = data.token;
|
||||
}
|
||||
});
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$(document).on("click", "form input:submit, form button:submit", function() {
|
||||
var f = this.form;
|
||||
var token = get_ajax_token();
|
||||
|
||||
if(!token) {
|
||||
alert("토큰 정보가 올바르지 않습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var $f = $(f);
|
||||
|
||||
if(typeof f.token === "undefined")
|
||||
$f.prepend('<input type="hidden" name="token" value="">');
|
||||
|
||||
$f.find("input[name=token]").val(token);
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,702 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/*
|
||||
// 081022 : CSRF 방지를 위해 코드를 작성했으나 효과가 없어 주석처리 함
|
||||
if (!get_session('ss_admin')) {
|
||||
set_session('ss_admin', true);
|
||||
goto_url('.');
|
||||
}
|
||||
*/
|
||||
|
||||
// 스킨디렉토리를 SELECT 형식으로 얻음
|
||||
function get_skin_select($skin_gubun, $id, $name, $selected = '', $event = '')
|
||||
{
|
||||
global $config;
|
||||
|
||||
$skins = array();
|
||||
|
||||
if (defined('G5_THEME_PATH') && $config['cf_theme']) {
|
||||
$dirs = get_skin_dir($skin_gubun, G5_THEME_PATH . '/' . G5_SKIN_DIR);
|
||||
if (!empty($dirs)) {
|
||||
foreach ($dirs as $dir) {
|
||||
$skins[] = 'theme/' . $dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$skins = array_merge($skins, get_skin_dir($skin_gubun));
|
||||
|
||||
$str = "<select id=\"$id\" name=\"$name\" $event>\n";
|
||||
for ($i = 0; $i < count($skins); $i++) {
|
||||
if ($i == 0) {
|
||||
$str .= "<option value=\"\">선택</option>";
|
||||
}
|
||||
if (preg_match('#^theme/(.+)$#', $skins[$i], $match)) {
|
||||
$text = '(테마) ' . $match[1];
|
||||
} else {
|
||||
$text = $skins[$i];
|
||||
}
|
||||
|
||||
$str .= option_selected($skins[$i], $selected, $text);
|
||||
}
|
||||
$str .= "</select>";
|
||||
return $str;
|
||||
}
|
||||
|
||||
// 모바일 스킨디렉토리를 SELECT 형식으로 얻음
|
||||
function get_mobile_skin_select($skin_gubun, $id, $name, $selected = '', $event = '')
|
||||
{
|
||||
global $config;
|
||||
|
||||
$skins = array();
|
||||
|
||||
if (defined('G5_THEME_PATH') && $config['cf_theme']) {
|
||||
$dirs = get_skin_dir($skin_gubun, G5_THEME_MOBILE_PATH . '/' . G5_SKIN_DIR);
|
||||
if (!empty($dirs)) {
|
||||
foreach ($dirs as $dir) {
|
||||
$skins[] = 'theme/' . $dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$skins = array_merge($skins, get_skin_dir($skin_gubun, G5_MOBILE_PATH . '/' . G5_SKIN_DIR));
|
||||
|
||||
$str = "<select id=\"$id\" name=\"$name\" $event>\n";
|
||||
for ($i = 0; $i < count($skins); $i++) {
|
||||
if ($i == 0) {
|
||||
$str .= "<option value=\"\">선택</option>";
|
||||
}
|
||||
if (preg_match('#^theme/(.+)$#', $skins[$i], $match)) {
|
||||
$text = '(테마) ' . $match[1];
|
||||
} else {
|
||||
$text = $skins[$i];
|
||||
}
|
||||
|
||||
$str .= option_selected($skins[$i], $selected, $text);
|
||||
}
|
||||
$str .= "</select>";
|
||||
return $str;
|
||||
}
|
||||
|
||||
|
||||
// 스킨경로를 얻는다
|
||||
function get_skin_dir($skin, $skin_path = G5_SKIN_PATH)
|
||||
{
|
||||
global $g5;
|
||||
|
||||
$result_array = array();
|
||||
|
||||
$dirname = $skin_path . '/' . $skin . '/';
|
||||
if (!is_dir($dirname)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$handle = opendir($dirname);
|
||||
while ($file = readdir($handle)) {
|
||||
if ($file == '.' || $file == '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_dir($dirname . $file)) {
|
||||
$result_array[] = $file;
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
sort($result_array);
|
||||
|
||||
return $result_array;
|
||||
}
|
||||
|
||||
|
||||
// 테마
|
||||
function get_theme_dir()
|
||||
{
|
||||
$result_array = array();
|
||||
|
||||
$dirname = G5_PATH . '/' . G5_THEME_DIR . '/';
|
||||
$handle = opendir($dirname);
|
||||
while ($file = readdir($handle)) {
|
||||
if ($file == '.' || $file == '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_dir($dirname . $file)) {
|
||||
$theme_path = $dirname . $file;
|
||||
if (is_file($theme_path . '/index.php') && is_file($theme_path . '/head.php') && is_file($theme_path . '/tail.php')) {
|
||||
$result_array[] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
natsort($result_array);
|
||||
|
||||
return $result_array;
|
||||
}
|
||||
|
||||
|
||||
// 테마정보
|
||||
function get_theme_info($dir)
|
||||
{
|
||||
$info = array();
|
||||
$path = G5_PATH . '/' . G5_THEME_DIR . '/' . $dir;
|
||||
|
||||
if (is_dir($path)) {
|
||||
$screenshot = $path . '/screenshot.png';
|
||||
$screenshot_url = '';
|
||||
if (is_file($screenshot)) {
|
||||
$size = @getimagesize($screenshot);
|
||||
|
||||
if (isset($size[2]) && $size[2] == 3) { // PNG
|
||||
$screenshot_url = str_replace(G5_PATH, G5_URL, $screenshot);
|
||||
}
|
||||
}
|
||||
|
||||
$info['screenshot'] = $screenshot_url;
|
||||
|
||||
$text = $path . '/readme.txt';
|
||||
if (is_file($text)) {
|
||||
$content = file($text, false);
|
||||
$content = array_map('trim', $content);
|
||||
|
||||
// 💡 [핵심 수정] readme.txt 파일의 각 줄에 정보가 있는지 확인하고, 있을 경우에만 값을 할당하도록 변경
|
||||
preg_match('#^Theme Name:(.+)$#i', (isset($content[0]) ? $content[0] : ''), $m0);
|
||||
preg_match('#^Theme URI:(.+)$#i', (isset($content[1]) ? $content[1] : ''), $m1);
|
||||
preg_match('#^Maker:(.+)$#i', (isset($content[2]) ? $content[2] : ''), $m2);
|
||||
preg_match('#^Maker URI:(.+)$#i', (isset($content[3]) ? $content[3] : ''), $m3);
|
||||
preg_match('#^Version:(.+)$#i', (isset($content[4]) ? $content[4] : ''), $m4);
|
||||
preg_match('#^Detail:(.+)$#i', (isset($content[5]) ? $content[5] : ''), $m5);
|
||||
preg_match('#^License:(.+)$#i', (isset($content[6]) ? $content[6] : ''), $m6);
|
||||
preg_match('#^License URI:(.+)$#i', (isset($content[7]) ? $content[7] : ''), $m7);
|
||||
|
||||
$info['theme_name'] = isset($m0[1]) ? trim($m0[1]) : '';
|
||||
$info['theme_uri'] = isset($m1[1]) ? trim($m1[1]) : '';
|
||||
$info['maker'] = isset($m2[1]) ? trim($m2[1]) : '';
|
||||
$info['maker_uri'] = isset($m3[1]) ? trim($m3[1]) : '';
|
||||
$info['version'] = isset($m4[1]) ? trim($m4[1]) : '';
|
||||
$info['detail'] = isset($m5[1]) ? trim($m5[1]) : '';
|
||||
$info['license'] = isset($m6[1]) ? trim($m6[1]) : '';
|
||||
$info['license_uri'] = isset($m7[1]) ? trim($m7[1]) : '';
|
||||
}
|
||||
|
||||
if (empty($info['theme_name'])) {
|
||||
$info['theme_name'] = $dir;
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
|
||||
// 테마설정 정보
|
||||
function get_theme_config_value($dir, $key = '*')
|
||||
{
|
||||
$tconfig = array();
|
||||
|
||||
$theme_config_file = G5_PATH . '/' . G5_THEME_DIR . '/' . $dir . '/theme.config.php';
|
||||
if (is_file($theme_config_file)) {
|
||||
include $theme_config_file;
|
||||
// 22.05.26 Undefined Variable $theme_config;
|
||||
if ($key == '*') {
|
||||
$tconfig = $theme_config;
|
||||
} else {
|
||||
$keys = array_map('trim', explode(',', $key));
|
||||
foreach ($keys as $v) {
|
||||
$tconfig[$v] = isset($theme_config[$v]) ? trim($theme_config[$v]) : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $tconfig;
|
||||
}
|
||||
|
||||
|
||||
// 회원권한을 SELECT 형식으로 얻음
|
||||
function get_member_level_select($name, $start_id = 0, $end_id = 10, $selected = "", $event = "")
|
||||
{
|
||||
global $g5;
|
||||
|
||||
$str = "\n<select id=\"{$name}\" name=\"{$name}\"";
|
||||
if ($event) {
|
||||
$str .= " $event";
|
||||
}
|
||||
$str .= ">\n";
|
||||
for ($i = $start_id; $i <= $end_id; $i++) {
|
||||
$str .= '<option value="' . $i . '"';
|
||||
if ($i == $selected) {
|
||||
$str .= ' selected="selected"';
|
||||
}
|
||||
$str .= ">{$i}</option>\n";
|
||||
}
|
||||
$str .= "</select>\n";
|
||||
return $str;
|
||||
}
|
||||
|
||||
|
||||
// 회원아이디를 SELECT 형식으로 얻음
|
||||
function get_member_id_select($name, $level, $selected = "", $event = "")
|
||||
{
|
||||
global $g5;
|
||||
|
||||
$sql = " select mb_id from {$g5['member_table']} where mb_level >= '{$level}' ";
|
||||
$result = sql_query($sql);
|
||||
$str = '<select id="' . $name . '" name="' . $name . '" ' . $event . '><option value="">선택안함</option>';
|
||||
for ($i = 0; $row = sql_fetch_array($result); $i++) {
|
||||
$str .= '<option value="' . $row['mb_id'] . '"';
|
||||
if ($row['mb_id'] == $selected) {
|
||||
$str .= ' selected';
|
||||
}
|
||||
$str .= '>' . $row['mb_id'] . '</option>';
|
||||
}
|
||||
$str .= '</select>';
|
||||
return $str;
|
||||
}
|
||||
|
||||
// php8 버전 호환 권한 검사 함수
|
||||
function auth_check_menu($auth, $sub_menu, $attr, $return = false)
|
||||
{
|
||||
|
||||
$check_auth = isset($auth[$sub_menu]) ? $auth[$sub_menu] : '';
|
||||
return auth_check($check_auth, $attr, $return);
|
||||
}
|
||||
|
||||
// 권한 검사
|
||||
function auth_check($auth, $attr, $return = false)
|
||||
{
|
||||
global $is_admin;
|
||||
|
||||
if ($is_admin == 'super') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!trim($auth)) {
|
||||
$msg = '이 메뉴에는 접근 권한이 없습니다.\\n\\n접근 권한은 최고관리자만 부여할 수 있습니다.';
|
||||
if ($return) {
|
||||
return $msg;
|
||||
} else {
|
||||
alert($msg);
|
||||
}
|
||||
}
|
||||
|
||||
$attr = strtolower($attr);
|
||||
|
||||
if (!strstr($auth, $attr)) {
|
||||
if ($attr == 'r') {
|
||||
$msg = '읽을 권한이 없습니다.';
|
||||
if ($return) {
|
||||
return $msg;
|
||||
} else {
|
||||
alert($msg);
|
||||
}
|
||||
} else if ($attr == 'w') {
|
||||
$msg = '입력, 추가, 생성, 수정 권한이 없습니다.';
|
||||
if ($return) {
|
||||
return $msg;
|
||||
} else {
|
||||
alert($msg);
|
||||
}
|
||||
} else if ($attr == 'd') {
|
||||
$msg = '삭제 권한이 없습니다.';
|
||||
if ($return) {
|
||||
return $msg;
|
||||
} else {
|
||||
alert($msg);
|
||||
}
|
||||
} else {
|
||||
$msg = '속성이 잘못 되었습니다.';
|
||||
if ($return) {
|
||||
return $msg;
|
||||
} else {
|
||||
alert($msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 작업아이콘 출력
|
||||
function icon($act, $link = '', $target = '_parent')
|
||||
{
|
||||
global $g5;
|
||||
|
||||
$img = array('입력' => 'insert', '추가' => 'insert', '생성' => 'insert', '수정' => 'modify', '삭제' => 'delete', '이동' => 'move', '그룹' => 'move', '보기' => 'view', '미리보기' => 'view', '복사' => 'copy');
|
||||
$icon = '<img src="' . G5_ADMIN_PATH . '/img/icon_' . $img[$act] . '.gif" title="' . $act . '">';
|
||||
if ($link) {
|
||||
$s = '<a href="' . $link . '">' . $icon . '</a>';
|
||||
} else {
|
||||
$s = $icon;
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
// rm -rf 옵션 : exec(), system() 함수를 사용할 수 없는 서버 또는 win32용 대체
|
||||
// www.php.net 참고 : pal at degerstrom dot com
|
||||
function rm_rf($file)
|
||||
{
|
||||
if (file_exists($file)) {
|
||||
if (is_dir($file)) {
|
||||
$handle = opendir($file);
|
||||
while ($filename = readdir($handle)) {
|
||||
if ($filename != '.' && $filename != '..') {
|
||||
rm_rf($file . '/' . $filename);
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
|
||||
@chmod($file, G5_DIR_PERMISSION);
|
||||
@rmdir($file);
|
||||
} else {
|
||||
@chmod($file, G5_FILE_PERMISSION);
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 입력 폼 안내문
|
||||
function help($help = "")
|
||||
{
|
||||
global $g5;
|
||||
|
||||
$str = '<span class="frm_info">' . str_replace("\n", "<br>", $help) . '</span>';
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
// 출력순서
|
||||
function order_select($fld, $sel = '')
|
||||
{
|
||||
$s = '<select name="' . $fld . '" id="' . $fld . '">';
|
||||
for ($i = 1; $i <= 100; $i++) {
|
||||
$s .= '<option value="' . $i . '" ';
|
||||
if ($sel) {
|
||||
if ($i == $sel) {
|
||||
$s .= 'selected';
|
||||
}
|
||||
} else {
|
||||
if ($i == 50) {
|
||||
$s .= 'selected';
|
||||
}
|
||||
}
|
||||
$s .= '>' . $i . '</option>';
|
||||
}
|
||||
$s .= '</select>';
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
// 불법접근을 막도록 토큰을 생성하면서 토큰값을 리턴
|
||||
function get_admin_token()
|
||||
{
|
||||
$token = md5(uniqid(rand(), true));
|
||||
set_session('ss_admin_token', $token);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
// 관리자가 자동등록방지를 사용해야 할 경우
|
||||
function get_admin_captcha_by($type = 'get')
|
||||
{
|
||||
|
||||
$captcha_name = 'ss_admin_use_captcha';
|
||||
|
||||
if ($type === 'remove') {
|
||||
set_session($captcha_name, '');
|
||||
}
|
||||
|
||||
return get_session($captcha_name);
|
||||
}
|
||||
|
||||
//input value 에서 xss 공격 filter 역할을 함 ( 반드시 input value='' 타입에만 사용할것 )
|
||||
function get_sanitize_input($s, $is_html = false)
|
||||
{
|
||||
|
||||
if (!$is_html) {
|
||||
$s = strip_tags($s);
|
||||
}
|
||||
|
||||
$s = htmlspecialchars($s, ENT_QUOTES, 'utf-8');
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
function domain_mail_host($is_at=true){
|
||||
list($domain_host,) = explode(':', $_SERVER['HTTP_HOST']);
|
||||
|
||||
if ('www.' === substr($domain_host, 0, 4)) {
|
||||
$domain_host = substr($domain_host, 4);
|
||||
}
|
||||
|
||||
return $is_at ? '@'.$domain_host : $domain_host;
|
||||
}
|
||||
|
||||
function check_log_folder($log_path, $is_delete = true)
|
||||
{
|
||||
|
||||
if (is_writable($log_path)) {
|
||||
|
||||
// 아파치 서버인 경우 웹에서 해당 폴더 접근 막기
|
||||
$htaccess_file = $log_path . '/.htaccess';
|
||||
if (!file_exists($htaccess_file)) {
|
||||
if ($handle = @fopen($htaccess_file, 'w')) {
|
||||
fwrite($handle, 'Order deny,allow' . "\n");
|
||||
fwrite($handle, 'Deny from all' . "\n");
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
||||
|
||||
// 아파치 서버인 경우 해당 디렉토리 파일 목록 안보이게 하기
|
||||
$index_file = $log_path . '/index.php';
|
||||
if (!file_exists($index_file)) {
|
||||
if ($handle = @fopen($index_file, 'w')) {
|
||||
fwrite($handle, '');
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_delete) {
|
||||
try {
|
||||
// txt 파일과 log 파일을 조회하여 30일이 지난 파일은 삭제합니다.
|
||||
$txt_files = glob($log_path . '/*.txt');
|
||||
$log_files = glob($log_path . '/*.log');
|
||||
|
||||
$del_files = array_merge($txt_files, $log_files);
|
||||
|
||||
if ($del_files && is_array($del_files)) {
|
||||
foreach ($del_files as $del_file) {
|
||||
$filetime = filemtime($del_file);
|
||||
// 30일이 지난 파일을 삭제
|
||||
if ($filetime && $filetime < (G5_SERVER_TIME - 2592000)) {
|
||||
@unlink($del_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// POST로 넘어온 토큰과 세션에 저장된 토큰 비교
|
||||
function check_admin_token()
|
||||
{
|
||||
$token = get_session('ss_admin_token');
|
||||
set_session('ss_admin_token', '');
|
||||
|
||||
if (!$token || !$_REQUEST['token'] || $token != $_REQUEST['token']) {
|
||||
alert('올바른 방법으로 이용해 주십시오.', G5_URL);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function admin_csrf_token_key($is_must=0){
|
||||
global $member;
|
||||
|
||||
$key = '';
|
||||
|
||||
if($is_must || !((isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'))){
|
||||
$key = md5((isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '').(defined('G5_TOKEN_ENCRYPTION_KEY') ? G5_TOKEN_ENCRYPTION_KEY : '').$member['mb_id'].$_SERVER['DOCUMENT_ROOT']);
|
||||
}
|
||||
|
||||
return run_replace('admin_csrf_token_key', $key, $is_must);
|
||||
}
|
||||
|
||||
// 관리자 페이지 referer 체크
|
||||
function admin_referer_check($return = false)
|
||||
{
|
||||
$referer = isset($_SERVER['HTTP_REFERER']) ? trim($_SERVER['HTTP_REFERER']) : '';
|
||||
if (!$referer) {
|
||||
$msg = '정보가 올바르지 않습니다.';
|
||||
|
||||
if ($return) {
|
||||
return $msg;
|
||||
} else {
|
||||
alert($msg, G5_URL);
|
||||
}
|
||||
}
|
||||
|
||||
$p = @parse_url($referer);
|
||||
|
||||
$host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']);
|
||||
$msg = '';
|
||||
|
||||
if ($host != $p['host']) {
|
||||
$msg = '올바른 방법으로 이용해 주십시오.';
|
||||
}
|
||||
|
||||
if ($p['path'] && !preg_match('/\/' . preg_quote(G5_ADMIN_DIR) . '\//i', $p['path'])) {
|
||||
$msg = '올바른 방법으로 이용해 주십시오';
|
||||
}
|
||||
|
||||
if ($msg) {
|
||||
if ($return) {
|
||||
return $msg;
|
||||
} else {
|
||||
alert($msg, G5_URL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function admin_check_xss_params($params)
|
||||
{
|
||||
|
||||
if (!$params) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
|
||||
if (empty($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
admin_check_xss_params($value);
|
||||
} else if (
|
||||
(preg_match('/<\s?[^\>]*\/?\s?>/i', $value) && (preg_match('/script.*?\/script/ius', $value) || preg_match('/on[a-z]+=*/ius', $value))) || preg_match('/^(?=.*token\()(?=.*xmlhttprequest\()(?=.*send\().*$/im', $value) ||
|
||||
(preg_match('/(on[a-z]+|focus)=.*/ius', $value) && preg_match('/(eval|atob|fetch|expression|exec|prompt)(\s*)\((.*)\)/ius', $value))) {
|
||||
alert('요청 쿼리에 잘못된 스크립트문장이 있습니다.\\nXSS 공격일수도 있습니다.', G5_URL);
|
||||
die();
|
||||
} else if (preg_match('/atob\s*\(\s*[\'"]?([a-zA-Z0-9+\/=]+)[\'"]?\s*\)/ius', $value, $matches)) {
|
||||
$decoded = base64_decode($matches[1], true);
|
||||
if ($decoded && preg_match('/(eval|fetch|script|alert|settimeout|setinterval)/ius', $decoded)) {
|
||||
// error_log("Base64 XSS 시도 감지: key=$key, decoded=$decoded, IP=" . $_SERVER['REMOTE_ADDR']);
|
||||
alert('Base64로 인코딩된 위험한 스크립트가 발견되었습니다.', G5_URL);
|
||||
die();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function admin_menu_find_by($call, $search_key)
|
||||
{
|
||||
global $menu;
|
||||
|
||||
static $cache_menu = array();
|
||||
|
||||
if (empty($cache_menu)) {
|
||||
foreach ($menu as $k1 => $arr1) {
|
||||
|
||||
if (empty($arr1)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($arr1 as $k2 => $arr2) {
|
||||
if (empty($arr2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$menu_key = isset($arr2[3]) ? $arr2[3] : '';
|
||||
if (empty($menu_key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cache_menu[$menu_key] = array(
|
||||
'sub_menu' => $arr2[0],
|
||||
'title' => $arr2[1],
|
||||
'link' => $arr2[2],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($cache_menu[$call]) && isset($cache_menu[$call][$search_key])) {
|
||||
return $cache_menu[$call][$search_key];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// 접근 권한 검사
|
||||
if (!$member['mb_id']) {
|
||||
alert('로그인 하십시오.', G5_BBS_URL . '/login.php?url=' . urlencode(correct_goto_url(G5_ADMIN_URL)));
|
||||
} else if ($is_admin != 'super') {
|
||||
$auth = array();
|
||||
$sql = " select au_menu, au_auth from {$g5['auth_table']} where mb_id = '{$member['mb_id']}' ";
|
||||
$result = sql_query($sql);
|
||||
for ($i = 0; $row = sql_fetch_array($result); $i++) {
|
||||
$auth[$row['au_menu']] = $row['au_auth'];
|
||||
}
|
||||
|
||||
if (!$i) {
|
||||
alert('최고관리자 또는 관리권한이 있는 회원만 접근 가능합니다.', G5_URL);
|
||||
}
|
||||
}
|
||||
|
||||
// 관리자의 클라이언트를 검증하여 일치하지 않으면 세션을 끊고 관리자에게 메일을 보낸다.
|
||||
if (!verify_mb_key($member)) {
|
||||
session_destroy();
|
||||
|
||||
include_once G5_LIB_PATH . '/mailer.lib.php';
|
||||
|
||||
// 메일 알림
|
||||
mailer($member['mb_nick'], $member['mb_email'], $member['mb_email'], 'XSS 공격 알림', $_SERVER['REMOTE_ADDR'] . ' 아이피로 XSS 공격이 있었습니다.<br><br>관리자 권한을 탈취하려는 접근이므로 주의하시기 바랍니다.<br><br>해당 아이피는 차단하시고 의심되는 게시물이 있는지 확인하시기 바랍니다.' . G5_URL, 0);
|
||||
|
||||
alert_close('정상적으로 로그인하여 접근하시기 바랍니다.');
|
||||
}
|
||||
|
||||
if (isset($auth) && is_array($auth)) {
|
||||
@ksort($auth);
|
||||
} else {
|
||||
$auth = array();
|
||||
}
|
||||
|
||||
// 가변 메뉴
|
||||
unset($auth_menu);
|
||||
unset($menu);
|
||||
unset($amenu);
|
||||
$tmp = dir(G5_ADMIN_PATH);
|
||||
$menu_files = array();
|
||||
while ($entry = $tmp->read()) {
|
||||
if (!preg_match('/^admin.menu([0-9]{3}).*\.php$/', $entry, $m)) {
|
||||
continue; // 파일명이 menu 으로 시작하지 않으면 무시한다.
|
||||
}
|
||||
|
||||
$amenu[$m[1]] = $entry;
|
||||
$menu_files[] = G5_ADMIN_PATH . '/' . $entry;
|
||||
}
|
||||
@asort($menu_files);
|
||||
foreach ($menu_files as $file) {
|
||||
include_once $file;
|
||||
}
|
||||
@ksort($amenu);
|
||||
|
||||
$amenu = run_replace('admin_amenu', $amenu);
|
||||
if (isset($menu) && $menu) {
|
||||
$menu = run_replace('admin_menu', $menu);
|
||||
}
|
||||
|
||||
$arr_query = array();
|
||||
if (isset($sst)) {
|
||||
$arr_query[] = 'sst=' . $sst;
|
||||
}
|
||||
if (isset($sod)) {
|
||||
$arr_query[] = 'sod=' . $sod;
|
||||
}
|
||||
if (isset($sfl)) {
|
||||
$arr_query[] = 'sfl=' . $sfl;
|
||||
}
|
||||
if (isset($stx)) {
|
||||
$arr_query[] = 'stx=' . $stx;
|
||||
}
|
||||
if (isset($page)) {
|
||||
$arr_query[] = 'page=' . $page;
|
||||
}
|
||||
$qstr = implode("&", $arr_query);
|
||||
|
||||
if (isset($_REQUEST) && $_REQUEST) {
|
||||
if (admin_referer_check(true)) {
|
||||
admin_check_xss_params($_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
// 관리자에서는 추가 스크립트와 추가 매타태그, 방문자분석 스크립트가 실행되지 않게 빈값으로 합니다.
|
||||
if (run_replace('safe_admin_add_script_boolean', false) === false) {
|
||||
$config['cf_analytics'] = '';
|
||||
$config['cf_add_script'] = '';
|
||||
$config['cf_add_meta'] = '';
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
$menu['menu000'] = array(
|
||||
array('000000', '빌더설정', G5_ADMIN_URL . '/rb/rb_form.php', 'rb_config'),
|
||||
array('000000', '빌더설정', G5_ADMIN_URL . '/rb/rb_form.php', 'rb_config'),
|
||||
array('000100', '환경설정', G5_ADMIN_URL . '/rb/config_form.php', 'rb_config'),
|
||||
array('000200', '모듈관리', G5_ADMIN_URL . '/rb/module_list.php', 'rb_config'),
|
||||
array('000000', ' ', G5_ADMIN_URL . '', 'rb_config'),
|
||||
array('000300', '배너 관리', G5_ADMIN_URL . '/rb/banner_list.php', 'rb_config'),
|
||||
array('000400', '게시물 관리', G5_ADMIN_URL . '/rb/bbs_list.php', 'rb_config'),
|
||||
array('000500', 'SEO 관리 ', G5_ADMIN_URL . '/rb/seo_form.php', 'rb_config'),
|
||||
array('000000', ' ', G5_ADMIN_URL . '', 'rb_config'),
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
$menu['menu100'] = array(
|
||||
array('100000', '환경설정', G5_ADMIN_URL . '/config_form.php', 'config'),
|
||||
array('100100', '기본환경설정', G5_ADMIN_URL . '/config_form.php', 'cf_basic'),
|
||||
array('100200', '관리권한설정', G5_ADMIN_URL . '/auth_list.php', 'cf_auth'),
|
||||
array('100280', '테마설정', G5_ADMIN_URL . '/theme.php', 'cf_theme', 1),
|
||||
array('100290', '메뉴설정', G5_ADMIN_URL . '/menu_list.php', 'cf_menu', 1),
|
||||
array('100300', '메일 테스트', G5_ADMIN_URL . '/sendmail_test.php', 'cf_mailtest'),
|
||||
array('100310', '팝업레이어관리', G5_ADMIN_URL . '/newwinlist.php', 'scf_poplayer'),
|
||||
array('100800', '세션파일 일괄삭제', G5_ADMIN_URL . '/session_file_delete.php', 'cf_session', 1),
|
||||
array('100900', '캐시파일 일괄삭제', G5_ADMIN_URL . '/cache_file_delete.php', 'cf_cache', 1),
|
||||
array('100910', '캡챠파일 일괄삭제', G5_ADMIN_URL . '/captcha_file_delete.php', 'cf_captcha', 1),
|
||||
array('100920', '썸네일파일 일괄삭제', G5_ADMIN_URL . '/thumbnail_file_delete.php', 'cf_thumbnail', 1),
|
||||
array('100500', 'phpinfo()', G5_ADMIN_URL . '/phpinfo.php', 'cf_phpinfo')
|
||||
);
|
||||
|
||||
if (version_compare(phpversion(), '5.3.0', '>=') && defined('G5_BROWSCAP_USE') && G5_BROWSCAP_USE) {
|
||||
$menu['menu100'][] = array('100510', 'Browscap 업데이트', G5_ADMIN_URL . '/browscap.php', 'cf_browscap');
|
||||
$menu['menu100'][] = array('100520', '접속로그 변환', G5_ADMIN_URL . '/browscap_convert.php', 'cf_visit_cnvrt');
|
||||
}
|
||||
|
||||
$menu['menu100'][] = array('100410', 'DB업그레이드', G5_ADMIN_URL . '/dbupgrade.php', 'db_upgrade');
|
||||
$menu['menu100'][] = array('100400', '부가서비스', G5_ADMIN_URL . '/service.php', 'cf_service');
|
||||
$menu['menu100'][] = array('100420', '모듈 설치', G5_ADMIN_URL . '/reinstall_update/install.php', 'module_install');
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
$menu['menu200'] = array(
|
||||
array('200000', '회원관리', G5_ADMIN_URL . '/member_list.php', 'member'),
|
||||
array('200100', '회원관리', G5_ADMIN_URL . '/member_list.php', 'mb_list'),
|
||||
array('200300', '회원메일발송', G5_ADMIN_URL . '/mail_list.php', 'mb_mail'),
|
||||
array('200800', '접속자집계', G5_ADMIN_URL . '/visit_list.php', 'mb_visit', 1),
|
||||
array('200810', '접속자검색', G5_ADMIN_URL . '/visit_search.php', 'mb_search', 1),
|
||||
array('200820', '접속자로그삭제', G5_ADMIN_URL . '/visit_delete.php', 'mb_delete', 1),
|
||||
array('200200', '포인트관리', G5_ADMIN_URL . '/point_list.php', 'mb_point'),
|
||||
array('200900', '투표관리', G5_ADMIN_URL . '/poll_list.php', 'mb_poll'),
|
||||
|
||||
// 💡 [추가] 휴지통 메뉴
|
||||
array('200950', '휴지통', G5_ADMIN_URL . '/trash_list.php', 'mb_trash'),
|
||||
);
|
||||
?>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
$menu['menu300'] = array(
|
||||
array('300000', '게시판관리', '' . G5_ADMIN_URL . '/board_list.php', 'board'),
|
||||
array('300100', '게시판관리', '' . G5_ADMIN_URL . '/board_list.php', 'bbs_board'),
|
||||
array('300200', '게시판그룹관리', '' . G5_ADMIN_URL . '/boardgroup_list.php', 'bbs_group'),
|
||||
array('300300', '인기검색어관리', '' . G5_ADMIN_URL . '/popular_list.php', 'bbs_poplist', 1),
|
||||
array('300400', '인기검색어순위', '' . G5_ADMIN_URL . '/popular_rank.php', 'bbs_poprank', 1),
|
||||
array('300500', '1:1문의설정', '' . G5_ADMIN_URL . '/qa_config.php', 'qa'),
|
||||
array('300600', '내용관리', G5_ADMIN_URL . '/contentlist.php', 'scf_contents', 1),
|
||||
array('300700', 'FAQ관리', G5_ADMIN_URL . '/faqmasterlist.php', 'scf_faq', 1),
|
||||
array('300820', '글,댓글 현황', G5_ADMIN_URL . '/write_count.php', 'scf_write_count'),
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
if (!defined('G5_USE_SHOP') || !G5_USE_SHOP) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menu['menu400'] = array(
|
||||
array('400000', '쇼핑몰관리', G5_ADMIN_URL . '/shop_admin/', 'shop_config'),
|
||||
array('400010', '쇼핑몰현황', G5_ADMIN_URL . '/shop_admin/', 'shop_index'),
|
||||
array('400100', '쇼핑몰설정', G5_ADMIN_URL . '/shop_admin/configform.php', 'scf_config'),
|
||||
array('400400', '주문내역', G5_ADMIN_URL . '/shop_admin/orderlist.php', 'scf_order', 1),
|
||||
array('400440', '개인결제관리', G5_ADMIN_URL . '/shop_admin/personalpaylist.php', 'scf_personalpay', 1),
|
||||
array('400200', '분류관리', G5_ADMIN_URL . '/shop_admin/categorylist.php', 'scf_cate'),
|
||||
array('400300', '상품관리', G5_ADMIN_URL . '/shop_admin/itemlist.php', 'scf_item'),
|
||||
array('400660', '상품문의', G5_ADMIN_URL . '/shop_admin/itemqalist.php', 'scf_item_qna'),
|
||||
array('400650', '사용후기', G5_ADMIN_URL . '/shop_admin/itemuselist.php', 'scf_ps'),
|
||||
array('400620', '상품재고관리', G5_ADMIN_URL . '/shop_admin/itemstocklist.php', 'scf_item_stock'),
|
||||
array('400610', '상품유형관리', G5_ADMIN_URL . '/shop_admin/itemtypelist.php', 'scf_item_type'),
|
||||
array('400500', '상품옵션재고관리', G5_ADMIN_URL . '/shop_admin/optionstocklist.php', 'scf_item_option'),
|
||||
array('400800', '쿠폰관리', G5_ADMIN_URL . '/shop_admin/couponlist.php', 'scf_coupon'),
|
||||
array('400810', '쿠폰존관리', G5_ADMIN_URL . '/shop_admin/couponzonelist.php', 'scf_coupon_zone'),
|
||||
array('400750', '추가배송비관리', G5_ADMIN_URL . '/shop_admin/sendcostlist.php', 'scf_sendcost', 1),
|
||||
array('400410', '미완료주문', G5_ADMIN_URL . '/shop_admin/inorderlist.php', 'scf_inorder', 1),
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
if (!defined('G5_USE_SHOP') || !G5_USE_SHOP) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menu['menu500'] = array(
|
||||
array('500000', '쇼핑몰현황/기타', G5_ADMIN_URL . '/shop_admin/itemsellrank.php', 'shop_stats'),
|
||||
array('500110', '매출현황', G5_ADMIN_URL . '/shop_admin/sale1.php', 'sst_order_stats'),
|
||||
array('500100', '상품판매순위', G5_ADMIN_URL . '/shop_admin/itemsellrank.php', 'sst_rank'),
|
||||
array('500120', '주문내역출력', G5_ADMIN_URL . '/shop_admin/orderprint.php', 'sst_print_order', 1),
|
||||
array('500400', '재입고SMS알림', G5_ADMIN_URL . '/shop_admin/itemstocksms.php', 'sst_stock_sms', 1),
|
||||
array('500300', '이벤트관리', G5_ADMIN_URL . '/shop_admin/itemevent.php', 'scf_event'),
|
||||
array('500310', '이벤트일괄처리', G5_ADMIN_URL . '/shop_admin/itemeventlist.php', 'scf_event_mng'),
|
||||
array('500500', '배너관리', G5_ADMIN_URL . '/shop_admin/bannerlist.php', 'scf_banner', 1),
|
||||
array('500140', '보관함현황', G5_ADMIN_URL . '/shop_admin/wishlist.php', 'sst_wish'),
|
||||
array('500210', '가격비교사이트', G5_ADMIN_URL . '/shop_admin/price.php', 'sst_compare', 1)
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
// 💡 [수정] 600번대 최상위 메뉴 배열에 아이콘 클래스('fa-envelope')를 추가합니다.
|
||||
$menu['menu600'][] = array('600000', '메일 관리', G5_ADMIN_URL.'/mail_manage/smtp_config.php', 'mail_manager', 'fa-envelope');
|
||||
|
||||
// '메일 관리'의 하위 메뉴들을 정의합니다.
|
||||
$menu['menu600'][] = array('600100', 'SMTP 설정', G5_ADMIN_URL.'/mail_manage/smtp_config.php', 'mail_smtp_config');
|
||||
$menu['menu600'][] = array('600200', '메일 템플릿 관리', G5_ADMIN_URL.'/mail_manage/template.php', 'mail_template');
|
||||
$menu['menu600'][] = array('600300', '메일 발송 이력', G5_ADMIN_URL.'/mail_manage/send_log.php', 'mail_send_log');
|
||||
$menu['menu600'][] = array('600900', '솔루션 설치', G5_ADMIN_URL.'/mail_manage/install.php', 'mail_solution_install');
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_'))
|
||||
exit;
|
||||
|
||||
// 800번대 최상위 메뉴 '견적 관리'를 정의합니다.
|
||||
$menu['menu800'][] = array('800000', '견적 관리', G5_ADMIN_URL . '/order_manage/statistics.php', 'order_manage', 'fa-file-text-o');
|
||||
|
||||
// '견적 관리'의 하위 메뉴들을 정의합니다.
|
||||
$menu['menu800'][] = array('800100', '견적 목록', G5_BBS_URL . '/board.php?bo_table=order', 'order_list');
|
||||
$menu['menu800'][] = array('800200', '견적 통계', G5_ADMIN_URL . '/order_manage/statistics.php', 'order_statistics');
|
||||
$menu['menu800'][] = array('800300', '시스템 설정', G5_ADMIN_URL . '/order_manage/config_manager.php', 'order_config');
|
||||
$menu['menu800'][] = array('800400', '전문가 방문', G5_ADMIN_URL . '/order_manage/expert_visits.php', 'expert_visits');
|
||||
$menu['menu800'][] = array('800500', '메일 템플릿', G5_ADMIN_URL . '/order_manage/mail_templates.php', 'mail_templates');
|
||||
$menu['menu800'][] = array('800600', 'SMS 템플릿', G5_ADMIN_URL . '/order_manage/sms_templates.php', 'sms_templates');
|
||||
$menu['menu800'][] = array('800700', '알림 로그 관리', G5_ADMIN_URL.'/order_manage/notification_log.php', 'notification_log');
|
||||
$menu['menu800'][] = array('800800', '전문가 방문 예약', G5_ADMIN_URL.'/order_manage/expert_visit_reservations.php', 'expert_visit_reservations');
|
||||
$menu['menu800'][] = array('800850', '전문가 방문 스케줄', G5_ADMIN_URL.'/order_manage/expert_visit_schedule.php', 'expert_visit_schedule');
|
||||
$menu['menu800'][] = array('800860', '윈도우 창호 관리', G5_ADMIN_URL.'/order_manage/brand_manager.php', 'brand_manager');
|
||||
$menu['menu800'][] = array('800900', '솔루션 설치', G5_ADMIN_URL . '/order_manage/install.php', 'order_solution_install');
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
// Contact Inquiry 메뉴
|
||||
if (file_exists(__DIR__ . '/contact_inquiry/admin.menu810.contact.php')) {
|
||||
include_once(__DIR__ . '/contact_inquiry/admin.menu810.contact.php');
|
||||
} else if (file_exists(__DIR__ . '/admin.menu810.contact.php')) {
|
||||
include_once(__DIR__ . '/admin.menu810.contact.php');
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 관리 시스템 메뉴
|
||||
*/
|
||||
|
||||
if (!defined('_GNUBOARD_'))
|
||||
exit;
|
||||
|
||||
// 메뉴 구조: array('메뉴코드', '메뉴명', '링크', '메뉴 ID', '아이콘 클래스 (옵션)')
|
||||
// 💡 [개선] 다른 모듈과의 호환성을 위해 배열을 덮어쓰지 않고 추가/병합합니다.
|
||||
if (!isset($menu['menu850'])) $menu['menu850'] = array();
|
||||
|
||||
$menu['menu850'] = array_merge($menu['menu850'], array(
|
||||
// 💡 [개선] 메뉴 그룹 대표 아이콘 추가 (예: fa-comments)
|
||||
array('850000', '상담관리', G5_ADMIN_URL . '/consultant_manage/dashboard.php', 'consultant_manage', 'fa-comments'),
|
||||
array('850100', '대시보드', G5_ADMIN_URL . '/consultant_manage/dashboard.php', 'consultant_dashboard'),
|
||||
array('850200', '예약 현황', G5_ADMIN_URL . '/consultant_manage/reservations.php', 'consultant_reservations'),
|
||||
array('850300', '빠른 스케줄 관리', G5_ADMIN_URL . '/consultant_manage/schedule_generate.php', 'consultant_schedule_quick'),
|
||||
array('850400', '통계 분석', G5_ADMIN_URL . '/consultant_manage/statistics.php', 'consultant_statistics'),
|
||||
// 💡 [개선] 설정 관련 메뉴를 '환경설정' 그룹으로 통합
|
||||
array('850500', '팝업 샘플', G5_ADMIN_URL . '/consultant_manage/sample_page.php', 'consultant_sample'),
|
||||
array('850600', '환경설정', G5_ADMIN_URL . '/consultant_manage/settings.php', 'consultant_settings_group', 'fa-cogs'),
|
||||
array('850610', '기본/운영 설정', G5_ADMIN_URL . '/consultant_manage/settings.php', 'consultant_settings'),
|
||||
array('850615', '리소스(상담사) 관리', G5_ADMIN_URL . '/consultant_manage/resources.php', 'consultant_resources'), // 💡 [추가] 리소스 관리 메뉴
|
||||
array('850620', '알림 템플릿', G5_ADMIN_URL . '/consultant_manage/templates.php', 'consultant_templates'),
|
||||
array('850630', '시스템 로그', G5_ADMIN_URL . '/consultant_manage/log_view.php', 'consultant_log_view'),
|
||||
array('850640', '설치/업데이트', G5_ADMIN_URL . '/consultant_manage/install.php', 'consultant_install'),
|
||||
// array('850900', '시스템 테스트', G5_ADMIN_URL . '/consultant_manage/test_system.php', 'consultant_test') // 개발용 메뉴는 주석 처리
|
||||
));
|
||||
?>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
// Consultant Manage 메뉴
|
||||
if (file_exists(__DIR__ . '/admin.menu850.consultant_manage.php')) {
|
||||
include_once(__DIR__ . '/admin.menu850.consultant_manage.php');
|
||||
}
|
||||
|
||||
// Contact Inquiry 메뉴
|
||||
if (file_exists(__DIR__ . '/admin.menu850.contact.php')) {
|
||||
include_once(__DIR__ . '/admin.menu850.contact.php');
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
$menu["menu900"] = array(
|
||||
array('900000', 'SMS 관리', '' . G5_SMS5_ADMIN_URL . '/config.php', 'sms5'),
|
||||
array('900100', 'SMS 기본설정', '' . G5_SMS5_ADMIN_URL . '/config.php', 'sms5_config'),
|
||||
array('900200', '회원정보업데이트', '' . G5_SMS5_ADMIN_URL . '/member_update.php', 'sms5_mb_update'),
|
||||
array('900300', '문자 보내기', '' . G5_SMS5_ADMIN_URL . '/sms_write.php', 'sms_write'),
|
||||
array('900400', '전송내역-건별', '' . G5_SMS5_ADMIN_URL . '/history_list.php', 'sms_history', 1),
|
||||
array('900410', '전송내역-번호별', '' . G5_SMS5_ADMIN_URL . '/history_num.php', 'sms_history_num', 1),
|
||||
array('900500', '이모티콘 그룹', '' . G5_SMS5_ADMIN_URL . '/form_group.php', 'emoticon_group'),
|
||||
array('900600', '이모티콘 관리', '' . G5_SMS5_ADMIN_URL . '/form_list.php', 'emoticon_list'),
|
||||
array('900700', '휴대폰번호 그룹', '' . G5_SMS5_ADMIN_URL . '/num_group.php', 'hp_group', 1),
|
||||
array('900800', '휴대폰번호 관리', '' . G5_SMS5_ADMIN_URL . '/num_book.php', 'hp_manage', 1),
|
||||
array('900900', '휴대폰번호 파일', '' . G5_SMS5_ADMIN_URL . '/num_book_file.php', 'hp_file', 1)
|
||||
);
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// 그누보드5.4.5.5 버전과 영카트5.4.5.5.1 버전이 통합됨에 따라 그누보드 버전만 표시
|
||||
// $print_version = defined('G5_YOUNGCART_VER') ? 'YoungCart Version '.G5_YOUNGCART_VER : 'Version '.G5_GNUBOARD_VER;
|
||||
$print_version = ($is_admin == 'super') ? 'Version ' . G5_GNUBOARD_VER : '';
|
||||
?>
|
||||
|
||||
<noscript>
|
||||
<p>
|
||||
귀하께서 사용하시는 브라우저는 현재 <strong>자바스크립트를 사용하지 않음</strong>으로 설정되어 있습니다.<br>
|
||||
<strong>자바스크립트를 사용하지 않음</strong>으로 설정하신 경우는 수정이나 삭제시 별도의 경고창이 나오지 않으므로 이점 주의하시기 바랍니다.
|
||||
</p>
|
||||
</noscript>
|
||||
|
||||
</div>
|
||||
<footer id="ft">
|
||||
<p>
|
||||
Copyright © <?php echo $_SERVER['HTTP_HOST']; ?>. All rights reserved. <?php echo $print_version; ?><br>
|
||||
<button type="button" class="scroll_top"><span class="top_img"></span><span class="top_txt">TOP</span></button>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(".scroll_top").click(function() {
|
||||
$("body,html").animate({
|
||||
scrollTop: 0
|
||||
}, 400);
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- <p>실행시간 : <?php echo get_microtime() - $begin_time; ?> -->
|
||||
|
||||
<script src="<?php echo G5_ADMIN_URL ?>/admin.js?ver=<?php echo G5_JS_VER; ?>"></script>
|
||||
<script src="<?php echo G5_JS_URL ?>/jquery.anchorScroll.js?ver=<?php echo G5_JS_VER; ?>"></script>
|
||||
<script>
|
||||
$(function() {
|
||||
|
||||
var admin_head_height = $("#hd_top").height() + $("#container_title").height() + 5;
|
||||
|
||||
$("a[href^='#']").anchorScroll({
|
||||
scrollSpeed: 0, // scroll speed
|
||||
offsetTop: admin_head_height, // offset for fixed top bars (defaults to 0)
|
||||
onScroll: function() {
|
||||
// callback on scroll start
|
||||
},
|
||||
scrollEnd: function() {
|
||||
// callback on scroll end
|
||||
}
|
||||
});
|
||||
|
||||
var hide_menu = false;
|
||||
var mouse_event = false;
|
||||
var oldX = oldY = 0;
|
||||
|
||||
$(document).mousemove(function(e) {
|
||||
if (oldX == 0) {
|
||||
oldX = e.pageX;
|
||||
oldY = e.pageY;
|
||||
}
|
||||
|
||||
if (oldX != e.pageX || oldY != e.pageY) {
|
||||
mouse_event = true;
|
||||
}
|
||||
});
|
||||
|
||||
// 주메뉴
|
||||
var $gnb = $(".gnb_1dli > a");
|
||||
$gnb.mouseover(function() {
|
||||
if (mouse_event) {
|
||||
$(".gnb_1dli").removeClass("gnb_1dli_over gnb_1dli_over2 gnb_1dli_on");
|
||||
$(this).parent().addClass("gnb_1dli_over gnb_1dli_on");
|
||||
menu_rearrange($(this).parent());
|
||||
hide_menu = false;
|
||||
}
|
||||
});
|
||||
|
||||
$gnb.mouseout(function() {
|
||||
hide_menu = true;
|
||||
});
|
||||
|
||||
$(".gnb_2dli").mouseover(function() {
|
||||
hide_menu = false;
|
||||
});
|
||||
|
||||
$(".gnb_2dli").mouseout(function() {
|
||||
hide_menu = true;
|
||||
});
|
||||
|
||||
$gnb.focusin(function() {
|
||||
$(".gnb_1dli").removeClass("gnb_1dli_over gnb_1dli_over2 gnb_1dli_on");
|
||||
$(this).parent().addClass("gnb_1dli_over gnb_1dli_on");
|
||||
menu_rearrange($(this).parent());
|
||||
hide_menu = false;
|
||||
});
|
||||
|
||||
$gnb.focusout(function() {
|
||||
hide_menu = true;
|
||||
});
|
||||
|
||||
$(".gnb_2da").focusin(function() {
|
||||
$(".gnb_1dli").removeClass("gnb_1dli_over gnb_1dli_over2 gnb_1dli_on");
|
||||
var $gnb_li = $(this).closest(".gnb_1dli").addClass("gnb_1dli_over gnb_1dli_on");
|
||||
menu_rearrange($(this).closest(".gnb_1dli"));
|
||||
hide_menu = false;
|
||||
});
|
||||
|
||||
$(".gnb_2da").focusout(function() {
|
||||
hide_menu = true;
|
||||
});
|
||||
|
||||
$('#gnb_1dul>li').bind('mouseleave', function() {
|
||||
submenu_hide();
|
||||
});
|
||||
|
||||
$(document).bind('click focusin', function() {
|
||||
if (hide_menu) {
|
||||
submenu_hide();
|
||||
}
|
||||
});
|
||||
|
||||
// 폰트 리사이즈 쿠키있으면 실행
|
||||
var font_resize_act = get_cookie("ck_font_resize_act");
|
||||
if (font_resize_act != "") {
|
||||
font_resize("container", font_resize_act);
|
||||
}
|
||||
});
|
||||
|
||||
function submenu_hide() {
|
||||
$(".gnb_1dli").removeClass("gnb_1dli_over gnb_1dli_over2 gnb_1dli_on");
|
||||
}
|
||||
|
||||
function menu_rearrange(el) {
|
||||
var width = $("#gnb_1dul").width();
|
||||
var left = w1 = w2 = 0;
|
||||
var idx = $(".gnb_1dli").index(el);
|
||||
|
||||
for (i = 0; i <= idx; i++) {
|
||||
w1 = $(".gnb_1dli:eq(" + i + ")").outerWidth();
|
||||
w2 = $(".gnb_2dli > a:eq(" + i + ")").outerWidth(true);
|
||||
|
||||
if ((left + w2) > width) {
|
||||
el.removeClass("gnb_1dli_over").addClass("gnb_1dli_over2");
|
||||
}
|
||||
|
||||
left += w1;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once G5_PATH . '/tail.sub.php';
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
require_once './_common.php';
|
||||
|
||||
set_session('ss_admin_token', '');
|
||||
|
||||
$admin_csrf_token_key = isset($_POST['admin_csrf_token_key']) ? $_POST['admin_csrf_token_key'] : '';
|
||||
|
||||
if(function_exists('admin_csrf_token_key') && $admin_csrf_token_key !== admin_csrf_token_key(1)){
|
||||
die(json_encode(array('error' => '토큰키 에러!', 'url' => G5_URL)));
|
||||
}
|
||||
|
||||
$error = admin_referer_check(true);
|
||||
if ($error) {
|
||||
die(json_encode(array('error' => $error, 'url' => G5_URL)));
|
||||
}
|
||||
|
||||
$token = get_admin_token();
|
||||
|
||||
die(json_encode(array('error' => '', 'token' => $token, 'url' => '')));
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
require_once './_common.php';
|
||||
|
||||
if (isset($_POST['admin_use_captcha'])) {
|
||||
set_session('ss_admin_use_captcha', true);
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
$sub_menu = "100200";
|
||||
require_once './_common.php';
|
||||
|
||||
if ($is_admin != 'super') {
|
||||
alert('최고관리자만 접근 가능합니다.');
|
||||
}
|
||||
|
||||
$sql_common = " from {$g5['auth_table']} a left join {$g5['member_table']} b on (a.mb_id=b.mb_id) ";
|
||||
|
||||
$sql_search = " where (1) ";
|
||||
if ($stx) {
|
||||
$sql_search .= " and ( ";
|
||||
switch ($sfl) {
|
||||
default:
|
||||
$sql_search .= " ({$sfl} like '%{$stx}%') ";
|
||||
break;
|
||||
}
|
||||
$sql_search .= " ) ";
|
||||
}
|
||||
|
||||
if (!$sst) {
|
||||
$sst = "a.mb_id, au_menu";
|
||||
$sod = "";
|
||||
}
|
||||
$sql_order = " order by $sst $sod ";
|
||||
|
||||
$sql = " select count(*) as cnt
|
||||
{$sql_common}
|
||||
{$sql_search}
|
||||
{$sql_order} ";
|
||||
$row = sql_fetch($sql);
|
||||
$total_count = $row['cnt'];
|
||||
|
||||
$rows = $config['cf_page_rows'];
|
||||
$total_page = ceil($total_count / $rows); // 전체 페이지 계산
|
||||
if ($page < 1) {
|
||||
$page = 1; // 페이지가 없으면 첫 페이지 (1 페이지)
|
||||
}
|
||||
$from_record = ($page - 1) * $rows; // 시작 열을 구함
|
||||
|
||||
$sql = " select *
|
||||
{$sql_common}
|
||||
{$sql_search}
|
||||
{$sql_order}
|
||||
limit {$from_record}, {$rows} ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
$listall = '<a href="' . $_SERVER['SCRIPT_NAME'] . '" class="ov_listall btn_ov02">전체목록</a>';
|
||||
|
||||
$g5['title'] = "관리권한설정";
|
||||
require_once './admin.head.php';
|
||||
|
||||
$colspan = 5;
|
||||
?>
|
||||
|
||||
<div class="local_ov01 local_ov">
|
||||
<?php echo $listall ?>
|
||||
<span class="btn_ov01"><span class="ov_txt">설정된 관리권한</span><span class="ov_num"><?php echo number_format($total_count) ?>건</span></span>
|
||||
</div>
|
||||
|
||||
<form name="fsearch" id="fsearch" class="local_sch01 local_sch" method="get">
|
||||
<input type="hidden" name="sfl" value="a.mb_id" id="sfl">
|
||||
|
||||
<label for="stx" class="sound_only">회원아이디<strong class="sound_only"> 필수</strong></label>
|
||||
<input type="text" name="stx" value="<?php echo $stx ?>" id="stx" required class="required frm_input">
|
||||
<input type="submit" value="검색" id="fsearch_submit" class="btn_submit">
|
||||
|
||||
</form>
|
||||
|
||||
<form name="fauthlist" id="fauthlist" method="post" action="./auth_list_delete.php" onsubmit="return fauthlist_submit(this);">
|
||||
<input type="hidden" name="sst" value="<?php echo $sst ?>">
|
||||
<input type="hidden" name="sod" value="<?php echo $sod ?>">
|
||||
<input type="hidden" name="sfl" value="<?php echo $sfl ?>">
|
||||
<input type="hidden" name="stx" value="<?php echo $stx ?>">
|
||||
<input type="hidden" name="page" value="<?php echo $page ?>">
|
||||
<input type="hidden" name="token" value="">
|
||||
|
||||
<div class="tbl_head01 tbl_wrap">
|
||||
<table>
|
||||
<caption><?php echo $g5['title']; ?> 목록</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<label for="chkall" class="sound_only">현재 페이지 회원 전체</label>
|
||||
<input type="checkbox" name="chkall" value="1" id="chkall" onclick="check_all(this.form)">
|
||||
</th>
|
||||
<th scope="col"><?php echo subject_sort_link('a.mb_id') ?>회원아이디</a></th>
|
||||
<th scope="col"><?php echo subject_sort_link('mb_nick') ?>닉네임</a></th>
|
||||
<th scope="col">메뉴</th>
|
||||
<th scope="col">권한</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$count = 0;
|
||||
for ($i = 0; $row = sql_fetch_array($result); $i++) {
|
||||
$is_continue = false;
|
||||
// 회원아이디가 없는 메뉴는 삭제함
|
||||
if ($row['mb_id'] == '' && $row['mb_nick'] == '') {
|
||||
sql_query(" delete from {$g5['auth_table']} where au_menu = '{$row['au_menu']}' ");
|
||||
$is_continue = true;
|
||||
}
|
||||
|
||||
// 메뉴번호가 바뀌는 경우에 현재 없는 저장된 메뉴는 삭제함
|
||||
if (!isset($auth_menu[$row['au_menu']])) {
|
||||
sql_query(" delete from {$g5['auth_table']} where au_menu = '{$row['au_menu']}' ");
|
||||
$is_continue = true;
|
||||
}
|
||||
|
||||
if ($is_continue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mb_nick = get_sideview($row['mb_id'], $row['mb_nick'], $row['mb_email'], $row['mb_homepage']);
|
||||
|
||||
$bg = 'bg' . ($i % 2);
|
||||
?>
|
||||
<tr class="<?php echo $bg; ?>">
|
||||
<td class="td_chk">
|
||||
<input type="hidden" name="au_menu[<?php echo $i ?>]" value="<?php echo $row['au_menu'] ?>">
|
||||
<input type="hidden" name="mb_id[<?php echo $i ?>]" value="<?php echo $row['mb_id'] ?>">
|
||||
<label for="chk_<?php echo $i; ?>" class="sound_only"><?php echo $row['mb_nick'] ?>님 권한</label>
|
||||
<input type="checkbox" name="chk[]" value="<?php echo $i ?>" id="chk_<?php echo $i ?>">
|
||||
</td>
|
||||
<td class="td_mbid"><a href="?sfl=a.mb_id&stx=<?php echo $row['mb_id'] ?>"><?php echo $row['mb_id'] ?></a></td>
|
||||
<td class="td_auth_mbnick"><?php echo $mb_nick ?></td>
|
||||
<td class="td_menu">
|
||||
<?php echo $row['au_menu'] ?>
|
||||
<?php echo $auth_menu[$row['au_menu']] ?>
|
||||
</td>
|
||||
<td class="td_auth"><?php echo $row['au_auth'] ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
$count++;
|
||||
}
|
||||
|
||||
if ($count == 0) {
|
||||
echo '<tr><td colspan="' . $colspan . '" class="empty_table">자료가 없습니다.</td></tr>';
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="btn_list01 btn_list">
|
||||
<input type="submit" name="act_button" value="선택삭제" onclick="document.pressed=this.value" class="btn btn_02">
|
||||
</div>
|
||||
|
||||
<?php
|
||||
//if (isset($stx))
|
||||
// echo '<script>document.fsearch.sfl.value = "'.$sfl.'";</script>'."\n";
|
||||
|
||||
if (strstr($sfl, 'mb_id')) {
|
||||
$mb_id = $stx;
|
||||
} else {
|
||||
$mb_id = '';
|
||||
}
|
||||
?>
|
||||
</form>
|
||||
|
||||
<?php
|
||||
$pagelist = get_paging(G5_IS_MOBILE ? $config['cf_mobile_pages'] : $config['cf_write_pages'], $page, $total_page, $_SERVER['SCRIPT_NAME'] . '?' . $qstr . '&page=');
|
||||
echo $pagelist;
|
||||
?>
|
||||
|
||||
<form name="fauthlist2" id="fauthlist2" action="./auth_update.php" method="post" autocomplete="off" onsubmit="return fauth_add_submit(this);">
|
||||
<input type="hidden" name="sfl" value="<?php echo $sfl ?>">
|
||||
<input type="hidden" name="stx" value="<?php echo $stx ?>">
|
||||
<input type="hidden" name="sst" value="<?php echo $sst ?>">
|
||||
<input type="hidden" name="sod" value="<?php echo $sod ?>">
|
||||
<input type="hidden" name="page" value="<?php echo $page ?>">
|
||||
<input type="hidden" name="token" value="">
|
||||
|
||||
<section id="add_admin">
|
||||
<h2 class="h2_frm">관리권한 추가</h2>
|
||||
|
||||
<div class="local_desc01 local_desc">
|
||||
<p>
|
||||
다음 양식에서 회원에게 관리권한을 부여하실 수 있습니다.<br>
|
||||
권한 <strong>r</strong>은 읽기권한, <strong>w</strong>는 쓰기권한, <strong>d</strong>는 삭제권한입니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="tbl_frm01 tbl_wrap">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col class="grid_4">
|
||||
<col>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row"><label for="mb_id">회원아이디<strong class="sound_only">필수</strong></label></th>
|
||||
<td>
|
||||
<strong id="msg_mb_id" class="msg_sound_only"></strong>
|
||||
<input type="text" name="mb_id" value="<?php echo $mb_id ?>" id="mb_id" required class="required frm_input">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="au_menu">접근가능메뉴<strong class="sound_only">필수</strong></label></th>
|
||||
<td>
|
||||
<select id="au_menu" name="au_menu" required class="required">
|
||||
<option value=''>선택하세요</option>
|
||||
<?php
|
||||
foreach ($auth_menu as $key => $value) {
|
||||
if (!(substr($key, -3) == '000' || $key == '-' || !$key)) {
|
||||
echo '<option value="' . $key . '">' . $key . ' ' . $value . '</option>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">권한지정</th>
|
||||
<td>
|
||||
<input type="checkbox" name="r" value="r" id="r" checked>
|
||||
<label for="r">r (읽기)</label>
|
||||
<input type="checkbox" name="w" value="w" id="w">
|
||||
<label for="w">w (쓰기)</label>
|
||||
<input type="checkbox" name="d" value="d" id="d">
|
||||
<label for="d">d (삭제)</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">자동등록방지</th>
|
||||
<td>
|
||||
<?php
|
||||
require_once G5_CAPTCHA_PATH . '/captcha.lib.php';
|
||||
$captcha_html = captcha_html();
|
||||
$captcha_js = chk_captcha_js();
|
||||
echo $captcha_html;
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="btn_confirm01 btn_confirm">
|
||||
<input type="submit" value="추가" class="btn_submit btn">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function fauth_add_submit(f) {
|
||||
|
||||
<?php echo $captcha_js; // 캡챠 사용시 자바스크립트에서 입력된 캡챠를 검사함 ?>
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function fauthlist_submit(f) {
|
||||
if (!is_checked("chk[]")) {
|
||||
alert(document.pressed + " 하실 항목을 하나 이상 선택하세요.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.pressed == "선택삭제") {
|
||||
if (!confirm("선택한 자료를 정말 삭제하시겠습니까?")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once './admin.tail.php';
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
$sub_menu = "100200";
|
||||
require_once './_common.php';
|
||||
|
||||
check_demo();
|
||||
|
||||
if ($is_admin != 'super') {
|
||||
alert('최고관리자만 접근 가능합니다.');
|
||||
}
|
||||
|
||||
check_admin_token();
|
||||
|
||||
$count = (isset($_POST['chk']) && is_array($_POST['chk'])) ? count($_POST['chk']) : 0;
|
||||
$post_act_button = isset($_POST['act_button']) ? clean_xss_tags($_POST['act_button'], 1, 1) : '';
|
||||
|
||||
if (!$count) {
|
||||
alert($_POST['act_button'] . " 하실 항목을 하나 이상 체크하세요.");
|
||||
}
|
||||
|
||||
if ((isset($_POST['mb_id']) && !is_array($_POST['mb_id'])) || (isset($_POST['au_menu']) && !is_array($_POST['au_menu']))) {
|
||||
alert("잘못된 요청입니다.");
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
// 실제 번호를 넘김
|
||||
$k = isset($_POST['chk'][$i]) ? (int) $_POST['chk'][$i] : 0;
|
||||
|
||||
$mb_id = isset($_POST['mb_id'][$k]) ? preg_replace('/[^a-zA-Z0-9_]/', '', $_POST['mb_id'][$k]) : '';
|
||||
$au_menu = isset($_POST['au_menu'][$k]) ? preg_replace('/[^a-zA-Z0-9_]/', '', $_POST['au_menu'][$k]) : '';
|
||||
|
||||
$sql = " delete from {$g5['auth_table']} where mb_id = '" . $mb_id . "' and au_menu = '" . $au_menu . "' ";
|
||||
sql_query($sql);
|
||||
|
||||
run_event('adm_auth_delete_member', $mb_id, $au_menu);
|
||||
}
|
||||
|
||||
goto_url('./auth_list.php?' . $qstr);
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
$sub_menu = "100200";
|
||||
require_once './_common.php';
|
||||
require_once G5_LIB_PATH . '/mailer.lib.php';
|
||||
|
||||
$au_menu = isset($_POST['au_menu']) ? preg_replace('/[^0-9a-z_]/i', '', $_POST['au_menu']) : '';
|
||||
$post_r = isset($_POST['r']) ? preg_replace('/[^0-9a-z_]/i', '', $_POST['r']) : '';
|
||||
$post_w = isset($_POST['w']) ? preg_replace('/[^0-9a-z_]/i', '', $_POST['w']) : '';
|
||||
$post_d = isset($_POST['d']) ? preg_replace('/[^0-9a-z_]/i', '', $_POST['d']) : '';
|
||||
|
||||
if ($is_admin != 'super') {
|
||||
alert('최고관리자만 접근 가능합니다.');
|
||||
}
|
||||
|
||||
$mb = get_member($mb_id);
|
||||
if (!$mb['mb_id']) {
|
||||
alert('존재하는 회원아이디가 아닙니다.');
|
||||
}
|
||||
|
||||
check_admin_token();
|
||||
|
||||
require_once G5_CAPTCHA_PATH . '/captcha.lib.php';
|
||||
|
||||
if (!chk_captcha()) {
|
||||
alert('자동등록방지 숫자가 틀렸습니다.');
|
||||
}
|
||||
|
||||
$sql = " insert into {$g5['auth_table']}
|
||||
set mb_id = '$mb_id',
|
||||
au_menu = '$au_menu',
|
||||
au_auth = '{$post_r},{$post_w},{$post_d}' ";
|
||||
$result = sql_query($sql, false);
|
||||
if (!$result) {
|
||||
$sql = " update {$g5['auth_table']}
|
||||
set au_auth = '{$post_r},{$post_w},{$post_d}'
|
||||
where mb_id = '$mb_id'
|
||||
and au_menu = '$au_menu' ";
|
||||
sql_query($sql);
|
||||
}
|
||||
|
||||
//sql_query(" OPTIMIZE TABLE `$g5['auth_table']` ");
|
||||
|
||||
// 세션을 체크하여 하루에 한번만 메일알림이 가게 합니다.
|
||||
if (str_replace('-', '', G5_TIME_YMD) !== get_session('adm_auth_update')) {
|
||||
$site_url = preg_replace('/^www\./', '', strtolower($_SERVER['SERVER_NAME']));
|
||||
$to_email = 'gnuboard@' . $site_url;
|
||||
|
||||
mailer($config['cf_admin_email_name'], $to_email, $config['cf_admin_email'], '[' . $config['cf_title'] . '] 관리권한설정 알림', '<p><b>[' . $config['cf_title'] . '] 관리권한설정 변경 안내</b></p><p style="padding-top:1em">회원 아이디 ' . $mb['mb_id'] . ' 에 관리권한이 추가 되었습니다.</p><p style="padding-top:1em">' . G5_TIME_YMDHIS . '</p><p style="padding-top:1em"><a href="' . G5_URL . '" target="_blank">' . $config['cf_title'] . '</a></p>', 1);
|
||||
|
||||
set_session('adm_auth_update', str_replace('-', '', G5_TIME_YMD));
|
||||
}
|
||||
|
||||
run_event('adm_auth_update', $mb);
|
||||
|
||||
goto_url('./auth_list.php?' . $qstr);
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
$sub_menu = '000300';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "w");
|
||||
|
||||
check_admin_token();
|
||||
|
||||
$bn_id = isset($_POST['bn_id']) ? (int)$_POST['bn_id'] : 0;
|
||||
$w = isset($_POST['w']) ? $_POST['w'] : '';
|
||||
|
||||
$sql_common = "
|
||||
bn_alt = '{$_POST['bn_alt']}',
|
||||
bn_url = '{$_POST['bn_url']}',
|
||||
bn_device = '{$_POST['bn_device']}',
|
||||
bn_position = '{$_POST['bn_position']}',
|
||||
bn_group = '{$_POST['bn_group']}',
|
||||
bn_border = '{$_POST['bn_border']}',
|
||||
bn_radius = '{$_POST['bn_radius']}',
|
||||
bn_ad_ico = '{$_POST['bn_ad_ico']}',
|
||||
bn_new_win = '{$_POST['bn_new_win']}',
|
||||
bn_begin_time = '{$_POST['bn_begin_time']}',
|
||||
bn_end_time = '{$_POST['bn_end_time']}',
|
||||
bn_order = '{$_POST['bn_order']}'
|
||||
";
|
||||
|
||||
if ($w == "") {
|
||||
$sql_common .= ", bn_time = '" . G5_TIME_YMDHIS . "' ";
|
||||
$sql = " INSERT INTO `rb_banner` SET {$sql_common} ";
|
||||
sql_query($sql);
|
||||
$bn_id = sql_insert_id();
|
||||
} else if ($w == "u") {
|
||||
$sql = " UPDATE `rb_banner` SET {$sql_common} WHERE bn_id = '{$bn_id}' ";
|
||||
sql_query($sql);
|
||||
} else {
|
||||
alert('제대로 된 값이 넘어오지 않았습니다.');
|
||||
}
|
||||
|
||||
// 이미지 업로드
|
||||
if (isset($_FILES['bn_bimg']) && $_FILES['bn_bimg']['name'] != '') {
|
||||
$bimg_dir = G5_DATA_PATH . '/banners';
|
||||
@mkdir($bimg_dir, G5_DIR_PERMISSION);
|
||||
@chmod($bimg_dir, G5_DIR_PERMISSION);
|
||||
|
||||
$dest_path = $bimg_dir . '/' . $bn_id;
|
||||
move_uploaded_file($_FILES['bn_bimg']['tmp_name'], $dest_path);
|
||||
chmod($dest_path, G5_FILE_PERMISSION);
|
||||
}
|
||||
|
||||
// 이미지 삭제
|
||||
if (isset($_POST['bn_bimg_del']) && $_POST['bn_bimg_del']) {
|
||||
@unlink(G5_DATA_PATH . '/banners/' . $bn_id);
|
||||
}
|
||||
|
||||
goto_url('./banner_list.php');
|
||||
?>
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
include_once('./_common.php');
|
||||
|
||||
if (!$is_admin) {
|
||||
alert('관리자만 접근 가능합니다.');
|
||||
}
|
||||
|
||||
// curl을 사용하여 이미지를 다운로드하는 함수
|
||||
function download_image_with_curl($url, $save_path) {
|
||||
$ch = curl_init($url);
|
||||
$fp = fopen($save_path, 'wb');
|
||||
|
||||
curl_setopt($ch, CURLOPT_FILE, $fp);
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
// Referer 설정 (필요시)
|
||||
curl_setopt($ch, CURLOPT_REFERER, 'https://www.laser.or.kr/');
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
|
||||
$result = curl_exec($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($ch);
|
||||
fclose($fp);
|
||||
|
||||
if ($result && $http_code == 200 && filesize($save_path) > 0) {
|
||||
return true;
|
||||
} else {
|
||||
@unlink($save_path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$banners = [
|
||||
['url' => 'http://www.lg.co.kr/', 'img' => 'foot-logo1.png', 'alt' => 'LG'],
|
||||
['url' => '#', 'img' => 'foot-logo2.png', 'alt' => 'Partner'],
|
||||
['url' => 'http://www.swhitech.com/main/', 'img' => 'foot-logo3.png', 'alt' => 'SWHitech'],
|
||||
['url' => 'http://www.hblaser.co.kr', 'img' => 'foot-logo4.png', 'alt' => 'HBLaser'],
|
||||
['url' => 'http://www.lasersystem.co.kr/', 'img' => 'foot-logo5.png', 'alt' => 'LaserSystem'],
|
||||
['url' => 'http://www.eotechnics.com/page/main/main.php', 'img' => 'foot-logo6.png', 'alt' => 'EO Technics'],
|
||||
['url' => 'https://www.lpkf.com/de/', 'img' => 'foot-logo7.jpg', 'alt' => 'LPKF'],
|
||||
['url' => 'https://amplitude-laser.com/', 'img' => 'foot-logo8.png', 'alt' => 'Amplitude'],
|
||||
['url' => 'http://www.mutechkorea.co.kr/', 'img' => 'foot-logo9.png', 'alt' => 'Mutech'],
|
||||
['url' => 'http://www.ainnotech.com/', 'img' => 'foot-logo10.png', 'alt' => 'Ainnotech'],
|
||||
['url' => 'https://www.edmundoptics.co.kr/', 'img' => 'foot-logo11.png', 'alt' => 'Edmund Optics'],
|
||||
['url' => 'https://www.coherent.com/', 'img' => 'foot-logo12_new.png', 'alt' => 'Coherent'],
|
||||
['url' => 'https://www.mksinst.com/', 'img' => 'foot-logo13.png', 'alt' => 'MKS'],
|
||||
['url' => 'http://daekhon.co.kr/', 'img' => 'foot-logo14.png', 'alt' => 'Daekhon'],
|
||||
['url' => 'https://lightrun.co.kr/', 'img' => 'foot-logo15.png', 'alt' => 'Lightrun'],
|
||||
['url' => 'https://www.uniotech.kr/', 'img' => 'foot-logo16.png', 'alt' => 'Uniotech'],
|
||||
['url' => 'http://www.hls-scansonic.kr/main/index.html', 'img' => 'foot-logo17.png', 'alt' => 'HLS'],
|
||||
['url' => 'http://coslaser.co.kr/', 'img' => 'Wooyang_Logo.png', 'alt' => 'Wooyang'],
|
||||
['url' => 'https://www.excelitas.com/', 'img' => 'foot-logo18.png', 'alt' => 'Excelitas'],
|
||||
['url' => 'http://www.evlaser.co.kr/', 'img' => 'foot-logo19.jpg', 'alt' => 'EV Laser'],
|
||||
['url' => 'https://www.precitec.com/kr/', 'img' => 'foot-logo20.jpg', 'alt' => 'Precitec'],
|
||||
['url' => 'https://www.amadaweldtech.co.kr/', 'img' => 'foot-logo21.png', 'alt' => 'Amada']
|
||||
];
|
||||
|
||||
// 💡 [핵심 수정] 이미지 경로에 /KOR/ 추가
|
||||
$base_img_url = 'https://www.laser.or.kr/KOR/images/comm/';
|
||||
$save_path = G5_DATA_PATH . '/banners/';
|
||||
|
||||
if (!is_dir($save_path)) {
|
||||
@mkdir($save_path, G5_DIR_PERMISSION);
|
||||
@chmod($save_path, G5_DIR_PERMISSION);
|
||||
}
|
||||
|
||||
echo "<h1>배너 자동 등록 시작 (경로 수정됨)</h1>";
|
||||
echo "<ul>";
|
||||
|
||||
$success_count = 0;
|
||||
|
||||
foreach ($banners as $idx => $item) {
|
||||
$row = sql_fetch(" SELECT bn_id FROM rb_banner WHERE bn_alt = '{$item['alt']}' AND bn_position = 'rolling_footer' ");
|
||||
|
||||
if ($row['bn_id']) {
|
||||
$bn_id = $row['bn_id'];
|
||||
echo "<li>[중복] ID: {$bn_id} - {$item['alt']} 이미 등록됨. 이미지 다운로드만 재시도합니다.</li>";
|
||||
} else {
|
||||
$sql = " INSERT INTO rb_banner
|
||||
SET bn_alt = '{$item['alt']}',
|
||||
bn_url = '{$item['url']}',
|
||||
bn_device = 'both',
|
||||
bn_position = 'rolling_footer',
|
||||
bn_border = '0',
|
||||
bn_new_win = '1',
|
||||
bn_begin_time = '" . date('Y-m-d H:i:s') . "',
|
||||
bn_end_time = '" . date('Y-m-d H:i:s', strtotime('+10 years')) . "',
|
||||
bn_time = '" . date('Y-m-d H:i:s') . "',
|
||||
bn_hit = 0,
|
||||
bn_order = " . ($idx + 1);
|
||||
|
||||
sql_query($sql);
|
||||
$bn_id = sql_insert_id();
|
||||
}
|
||||
|
||||
if ($bn_id) {
|
||||
$remote_img = $base_img_url . $item['img'];
|
||||
$local_img = $save_path . $bn_id;
|
||||
|
||||
if (download_image_with_curl($remote_img, $local_img)) {
|
||||
echo "<li>[성공] ID: {$bn_id} - {$item['alt']} 이미지 저장 완료</li>";
|
||||
$success_count++;
|
||||
} else {
|
||||
echo "<li style='color:red;'>[실패] ID: {$bn_id} - 이미지 다운로드 실패 ({$remote_img})</li>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo "</ul>";
|
||||
echo "<h2>총 {$success_count}개의 배너 이미지가 저장되었습니다.</h2>";
|
||||
echo "<p><a href='" . G5_ADMIN_URL . "/rb/banner_list.php'>[배너 관리 페이지로 이동]</a></p>";
|
||||
?>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
$sub_menu = "300100";
|
||||
require_once "./_common.php";
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
$g5['title'] = '게시판 복사';
|
||||
require_once G5_PATH . '/head.sub.php';
|
||||
|
||||
if (empty($bo_table)) {
|
||||
alert_close("정상적인 방법으로 이용해주세요.");
|
||||
}
|
||||
?>
|
||||
<script>
|
||||
var g5_admin_csrf_token_key = "<?php echo (function_exists('admin_csrf_token_key')) ? admin_csrf_token_key() : ''; ?>";
|
||||
</script>
|
||||
<script src="<?php echo G5_ADMIN_URL ?>/admin.js?ver=<?php echo G5_JS_VER; ?>"></script>
|
||||
|
||||
<div class="new_win">
|
||||
<h1><?php echo $g5['title']; ?></h1>
|
||||
|
||||
<form name="fboardcopy" id="fboardcopy" action="./board_copy_update.php" onsubmit="return fboardcopy_check(this);" method="post">
|
||||
<input type="hidden" name="bo_table" value="<?php echo $bo_table ?>" id="bo_table">
|
||||
<input type="hidden" name="token" value="">
|
||||
<div class=" new_win_con">
|
||||
<div class="tbl_frm01 tbl_wrap">
|
||||
<table>
|
||||
<caption><?php echo $g5['title']; ?></caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="col">원본 테이블명</th>
|
||||
<td><?php echo $bo_table ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col"><label for="target_table">복사 테이블명<strong class="sound_only">필수</strong></label></th>
|
||||
<td><input type="text" name="target_table" id="target_table" required class="required alnum_ frm_input" maxlength="20">영문자, 숫자, _ 만 가능 (공백없이)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col"><label for="target_subject">게시판 제목<strong class="sound_only">필수</strong></label></th>
|
||||
<td><input type="text" name="target_subject" value="[복사본] <?php echo get_sanitize_input($board['bo_subject']); ?>" id="target_subject" required class="required frm_input" maxlength="120"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col">복사 유형</th>
|
||||
<td>
|
||||
<input type="radio" name="copy_case" value="schema_only" id="copy_case" checked>
|
||||
<label for="copy_case">구조만</label>
|
||||
<input type="radio" name="copy_case" value="schema_data_both" id="copy_case2">
|
||||
<label for="copy_case2">구조와 데이터</label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="win_btn ">
|
||||
<input type="submit" class="btn_submit btn" value="복사">
|
||||
<input type="button" class="btn_close btn" value="창닫기" onclick="window.close();">
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function fboardcopy_check(f) {
|
||||
<?php
|
||||
|
||||
if (!$w) {
|
||||
$js_array = get_bo_table_banned_word();
|
||||
echo "var banned_array = " . json_encode($js_array) . ";\n";
|
||||
}
|
||||
?>
|
||||
|
||||
// 게시판명이 금지된 단어로 되어 있으면
|
||||
if ((typeof banned_array != 'undefined') && jQuery.inArray(f.target_table.value, banned_array) !== -1) {
|
||||
alert("입력한 게시판 TABLE명을 사용할수 없습니다. 다른 이름으로 입력해 주세요.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (f.bo_table.value == f.target_table.value) {
|
||||
alert("원본 테이블명과 복사할 테이블명이 달라야 합니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<?php
|
||||
require_once G5_PATH . '/tail.sub.php';
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
$sub_menu = '300100';
|
||||
require_once './_common.php';
|
||||
|
||||
check_demo();
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
check_admin_token();
|
||||
|
||||
$bo_table = isset($_POST['bo_table']) ? substr(preg_replace('/[^a-z0-9_]/i', '', $_POST['bo_table']), 0, 20) : null;
|
||||
$target_table = isset($_POST['target_table']) ? trim($_POST['target_table']) : '';
|
||||
$target_subject = isset($_POST['target_subject']) ? trim($_POST['target_subject']) : '';
|
||||
|
||||
$target_subject = strip_tags(clean_xss_attributes($target_subject));
|
||||
|
||||
$file_copy = array();
|
||||
|
||||
if (empty($bo_table)) {
|
||||
alert("원본 테이블 정보가 없습니다.");
|
||||
}
|
||||
|
||||
if (!preg_match('/[A-Za-z0-9_]{1,20}/', $target_table)) {
|
||||
alert('게시판 TABLE명은 공백없이 영문자, 숫자, _ 만 사용 가능합니다. (20자 이내)');
|
||||
}
|
||||
|
||||
$target_table = substr(preg_replace('/[^a-z0-9_]/i', '', $target_table), 0, 20);
|
||||
|
||||
// 게시판명이 금지된 단어로 되어 있으면
|
||||
if ($w == '' && in_array($target_table, get_bo_table_banned_word())) {
|
||||
alert('입력한 게시판 TABLE명을 사용할수 없습니다. 다른 이름으로 입력해 주세요.');
|
||||
}
|
||||
|
||||
$row = sql_fetch(" select count(*) as cnt from {$g5['board_table']} where bo_table = '$target_table' ");
|
||||
if ($row['cnt']) {
|
||||
alert($target_table . '은(는) 이미 존재하는 게시판 테이블명 입니다.\\n복사할 테이블명으로 사용할 수 없습니다.');
|
||||
}
|
||||
|
||||
// 게시판 테이블 생성
|
||||
$sql = get_table_define($g5['write_prefix'] . $bo_table);
|
||||
$sql = str_replace($g5['write_prefix'] . $bo_table, $g5['write_prefix'] . $target_table, $sql);
|
||||
sql_query($sql, false);
|
||||
|
||||
// 구조만 복사시에는 공지사항 번호는 복사하지 않는다.
|
||||
if ($copy_case == 'schema_only') {
|
||||
$board['bo_notice'] = '';
|
||||
}
|
||||
|
||||
// 게시판 정보
|
||||
$sql = " insert into {$g5['board_table']}
|
||||
set bo_table = '$target_table',
|
||||
gr_id = '{$board['gr_id']}',
|
||||
bo_subject = '$target_subject',
|
||||
bo_device = '{$board['bo_device']}',
|
||||
bo_admin = '{$board['bo_admin']}',
|
||||
bo_list_level = '{$board['bo_list_level']}',
|
||||
bo_read_level = '{$board['bo_read_level']}',
|
||||
bo_write_level = '{$board['bo_write_level']}',
|
||||
bo_reply_level = '{$board['bo_reply_level']}',
|
||||
bo_comment_level = '{$board['bo_comment_level']}',
|
||||
bo_upload_level = '{$board['bo_upload_level']}',
|
||||
bo_download_level = '{$board['bo_download_level']}',
|
||||
bo_html_level = '{$board['bo_html_level']}',
|
||||
bo_link_level = '{$board['bo_link_level']}',
|
||||
bo_count_modify = '{$board['bo_count_modify']}',
|
||||
bo_count_delete = '{$board['bo_count_delete']}',
|
||||
bo_read_point = '{$board['bo_read_point']}',
|
||||
bo_write_point = '{$board['bo_write_point']}',
|
||||
bo_comment_point = '{$board['bo_comment_point']}',
|
||||
bo_download_point = '{$board['bo_download_point']}',
|
||||
bo_use_category = '{$board['bo_use_category']}',
|
||||
bo_category_list = '{$board['bo_category_list']}',
|
||||
bo_use_sideview = '{$board['bo_use_sideview']}',
|
||||
bo_use_file_content = '{$board['bo_use_file_content']}',
|
||||
bo_use_secret = '{$board['bo_use_secret']}',
|
||||
bo_use_dhtml_editor = '{$board['bo_use_dhtml_editor']}',
|
||||
bo_use_rss_view = '{$board['bo_use_rss_view']}',
|
||||
bo_use_good = '{$board['bo_use_good']}',
|
||||
bo_use_nogood = '{$board['bo_use_nogood']}',
|
||||
bo_use_name = '{$board['bo_use_name']}',
|
||||
bo_use_signature = '{$board['bo_use_signature']}',
|
||||
bo_use_ip_view = '{$board['bo_use_ip_view']}',
|
||||
bo_use_list_view = '{$board['bo_use_list_view']}',
|
||||
bo_use_list_content = '{$board['bo_use_list_content']}',
|
||||
bo_use_list_file = '{$board['bo_use_list_file']}',
|
||||
bo_table_width = '{$board['bo_table_width']}',
|
||||
bo_subject_len = '{$board['bo_subject_len']}',
|
||||
bo_mobile_subject_len = '{$board['bo_mobile_subject_len']}',
|
||||
bo_page_rows = '{$board['bo_page_rows']}',
|
||||
bo_mobile_page_rows = '{$board['bo_mobile_page_rows']}',
|
||||
bo_new = '{$board['bo_new']}',
|
||||
bo_hot = '{$board['bo_hot']}',
|
||||
bo_image_width = '{$board['bo_image_width']}',
|
||||
bo_skin = '" . sql_real_escape_string($board['bo_skin']). "',
|
||||
bo_mobile_skin = '" . sql_real_escape_string($board['bo_mobile_skin']). "',
|
||||
bo_include_head = '" . sql_real_escape_string($board['bo_include_head']). "',
|
||||
bo_include_tail = '" . sql_real_escape_string($board['bo_include_tail']). "',
|
||||
bo_content_head = '" . addslashes($board['bo_content_head']) . "',
|
||||
bo_content_tail = '" . addslashes($board['bo_content_tail']) . "',
|
||||
bo_mobile_content_head = '" . addslashes($board['bo_mobile_content_head']) . "',
|
||||
bo_mobile_content_tail = '" . addslashes($board['bo_mobile_content_tail']) . "',
|
||||
bo_insert_content = '" . addslashes($board['bo_insert_content']) . "',
|
||||
bo_gallery_cols = '{$board['bo_gallery_cols']}',
|
||||
bo_gallery_width = '{$board['bo_gallery_width']}',
|
||||
bo_gallery_height = '{$board['bo_gallery_height']}',
|
||||
bo_mobile_gallery_width = '{$board['bo_mobile_gallery_width']}',
|
||||
bo_mobile_gallery_height = '{$board['bo_mobile_gallery_height']}',
|
||||
bo_upload_size = '{$board['bo_upload_size']}',
|
||||
bo_reply_order = '{$board['bo_reply_order']}',
|
||||
bo_use_search = '{$board['bo_use_search']}',
|
||||
bo_order = '{$board['bo_order']}',
|
||||
bo_notice = '{$board['bo_notice']}',
|
||||
bo_upload_count = '{$board['bo_upload_count']}',
|
||||
bo_use_email = '{$board['bo_use_email']}',
|
||||
bo_use_cert = '{$board['bo_use_cert']}',
|
||||
bo_use_sns = '{$board['bo_use_sns']}',
|
||||
bo_use_captcha = '{$board['bo_use_captcha']}',
|
||||
bo_sort_field = '{$board['bo_sort_field']}',
|
||||
bo_1_subj = '" . addslashes($board['bo_1_subj']) . "',
|
||||
bo_2_subj = '" . addslashes($board['bo_2_subj']) . "',
|
||||
bo_3_subj = '" . addslashes($board['bo_3_subj']) . "',
|
||||
bo_4_subj = '" . addslashes($board['bo_4_subj']) . "',
|
||||
bo_5_subj = '" . addslashes($board['bo_5_subj']) . "',
|
||||
bo_6_subj = '" . addslashes($board['bo_6_subj']) . "',
|
||||
bo_7_subj = '" . addslashes($board['bo_7_subj']) . "',
|
||||
bo_8_subj = '" . addslashes($board['bo_8_subj']) . "',
|
||||
bo_9_subj = '" . addslashes($board['bo_9_subj']) . "',
|
||||
bo_10_subj = '" . addslashes($board['bo_10_subj']) . "',
|
||||
bo_1 = '" . addslashes($board['bo_1']) . "',
|
||||
bo_2 = '" . addslashes($board['bo_2']) . "',
|
||||
bo_3 = '" . addslashes($board['bo_3']) . "',
|
||||
bo_4 = '" . addslashes($board['bo_4']) . "',
|
||||
bo_5 = '" . addslashes($board['bo_5']) . "',
|
||||
bo_6 = '" . addslashes($board['bo_6']) . "',
|
||||
bo_7 = '" . addslashes($board['bo_7']) . "',
|
||||
bo_8 = '" . addslashes($board['bo_8']) . "',
|
||||
bo_9 = '" . addslashes($board['bo_9']) . "',
|
||||
bo_10 = '" . addslashes($board['bo_10']) . "' ";
|
||||
sql_query($sql, false);
|
||||
|
||||
// 게시판 폴더 생성
|
||||
@mkdir(G5_DATA_PATH . '/file/' . $target_table, G5_DIR_PERMISSION);
|
||||
@chmod(G5_DATA_PATH . '/file/' . $target_table, G5_DIR_PERMISSION);
|
||||
|
||||
// 디렉토리에 있는 파일의 목록을 보이지 않게 한다.
|
||||
$board_path = G5_DATA_PATH . '/file/' . $target_table;
|
||||
$file = $board_path . '/index.php';
|
||||
$f = @fopen($file, 'w');
|
||||
@fwrite($f, '');
|
||||
@fclose($f);
|
||||
@chmod($file, G5_FILE_PERMISSION);
|
||||
|
||||
$copy_file = 0;
|
||||
if ($copy_case == 'schema_data_both') {
|
||||
$d = dir(G5_DATA_PATH . '/file/' . $bo_table);
|
||||
while ($entry = $d->read()) {
|
||||
if ($entry == '.' || $entry == '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 김선용 201007 :
|
||||
if (is_dir(G5_DATA_PATH . '/file/' . $bo_table . '/' . $entry)) {
|
||||
$dd = dir(G5_DATA_PATH . '/file/' . $bo_table . '/' . $entry);
|
||||
@mkdir(G5_DATA_PATH . '/file/' . $target_table . '/' . $entry, G5_DIR_PERMISSION);
|
||||
@chmod(G5_DATA_PATH . '/file/' . $target_table . '/' . $entry, G5_DIR_PERMISSION);
|
||||
while ($entry2 = $dd->read()) {
|
||||
if ($entry2 == '.' || $entry2 == '..') {
|
||||
continue;
|
||||
}
|
||||
@copy(G5_DATA_PATH . '/file/' . $bo_table . '/' . $entry . '/' . $entry2, G5_DATA_PATH . '/file/' . $target_table . '/' . $entry . '/' . $entry2);
|
||||
@chmod(G5_DATA_PATH . '/file/' . $target_table . '/' . $entry . '/' . $entry2, G5_DIR_PERMISSION);
|
||||
$copy_file++;
|
||||
}
|
||||
$dd->close();
|
||||
} else {
|
||||
@copy(G5_DATA_PATH . '/file/' . $bo_table . '/' . $entry, G5_DATA_PATH . '/file/' . $target_table . '/' . $entry);
|
||||
@chmod(G5_DATA_PATH . '/file/' . $target_table . '/' . $entry, G5_DIR_PERMISSION);
|
||||
$copy_file++;
|
||||
}
|
||||
}
|
||||
$d->close();
|
||||
|
||||
run_event('admin_board_copy_file', $bo_table, $target_table);
|
||||
|
||||
// 글복사
|
||||
$sql = " insert into {$g5['write_prefix']}$target_table select * from {$g5['write_prefix']}$bo_table ";
|
||||
sql_query($sql, false);
|
||||
|
||||
// 게시글수 저장
|
||||
$sql = " select bo_count_write, bo_count_comment from {$g5['board_table']} where bo_table = '$bo_table' ";
|
||||
$row = sql_fetch($sql);
|
||||
$sql = " update {$g5['board_table']} set bo_count_write = '{$row['bo_count_write']}', bo_count_comment = '{$row['bo_count_comment']}' where bo_table = '$target_table' ";
|
||||
sql_query($sql, false);
|
||||
|
||||
// 4.00.01
|
||||
$sql = " select * from {$g5['board_file_table']} where bo_table = '$bo_table' ";
|
||||
$result = sql_query($sql, false);
|
||||
for ($i = 0; $row = sql_fetch_array($result); $i++) {
|
||||
$file_copy[$i] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($file_copy)) {
|
||||
for ($i = 0; $i < count($file_copy); $i++) {
|
||||
$file_copy[$i] = run_replace('admin_copy_update_file', $file_copy[$i], $file_copy[$i]['bf_file'], $bo_table, $target_table);
|
||||
|
||||
$sql = " insert into {$g5['board_file_table']}
|
||||
set bo_table = '$target_table',
|
||||
wr_id = '{$file_copy[$i]['wr_id']}',
|
||||
bf_no = '{$file_copy[$i]['bf_no']}',
|
||||
bf_source = '" . addslashes($file_copy[$i]['bf_source']) . "',
|
||||
bf_file = '{$file_copy[$i]['bf_file']}',
|
||||
bf_download = '{$file_copy[$i]['bf_download']}',
|
||||
bf_content = '" . addslashes($file_copy[$i]['bf_content']) . "',
|
||||
bf_fileurl = '" . addslashes($file_copy[$i]['bf_fileurl']) . "',
|
||||
bf_thumburl = '" . addslashes($file_copy[$i]['bf_thumburl']) . "',
|
||||
bf_storage = '" . addslashes($file_copy[$i]['bf_storage']) . "',
|
||||
bf_filesize = '{$file_copy[$i]['bf_filesize']}',
|
||||
bf_width = '{$file_copy[$i]['bf_width']}',
|
||||
bf_height = '{$file_copy[$i]['bf_height']}',
|
||||
bf_type = '{$file_copy[$i]['bf_type']}',
|
||||
bf_datetime = '{$file_copy[$i]['bf_datetime']}' ";
|
||||
|
||||
sql_query($sql, false);
|
||||
}
|
||||
}
|
||||
|
||||
delete_cache_latest($bo_table);
|
||||
delete_cache_latest($target_table);
|
||||
|
||||
echo "<script>opener.document.location.reload();</script>";
|
||||
|
||||
alert("복사에 성공 했습니다.", './board_copy.php?bo_table=' . $bo_table . '&' . $qstr);
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
// board_delete.php , boardgroup_delete.php 에서 include 하는 파일
|
||||
|
||||
// 개별 페이지 접근 불가
|
||||
if (!defined('_GNUBOARD_')) {
|
||||
exit;
|
||||
}
|
||||
if (!defined('_BOARD_DELETE_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// $tmp_bo_table 에는 $bo_table 값을 넘겨주어야 함
|
||||
if (!$tmp_bo_table) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 게시판 1개는 삭제 불가 (게시판 복사를 위해서)
|
||||
//$row = sql_fetch(" select count(*) as cnt from $g5['board_table'] ");
|
||||
//if ($row['cnt'] <= 1) { return; }
|
||||
|
||||
// 게시판 설정 삭제
|
||||
sql_query(" delete from {$g5['board_table']} where bo_table = '{$tmp_bo_table}' ");
|
||||
|
||||
// 최신글 삭제
|
||||
sql_query(" delete from {$g5['board_new_table']} where bo_table = '{$tmp_bo_table}' ");
|
||||
|
||||
// 스크랩 삭제
|
||||
sql_query(" delete from {$g5['scrap_table']} where bo_table = '{$tmp_bo_table}' ");
|
||||
|
||||
// 파일 삭제
|
||||
sql_query(" delete from {$g5['board_file_table']} where bo_table = '{$tmp_bo_table}' ");
|
||||
|
||||
// 게시판 테이블 DROP
|
||||
sql_query(" drop table {$g5['write_prefix']}{$tmp_bo_table} ", false);
|
||||
|
||||
// 좋아요 테이블에서 기록 삭제
|
||||
sql_query(" delete from {$g5['board_good_table']} where bo_table = '{$tmp_bo_table}' ");
|
||||
|
||||
delete_cache_latest($tmp_bo_table);
|
||||
|
||||
// 게시판 폴더 전체 삭제
|
||||
rm_rf(G5_DATA_PATH . '/file/' . $tmp_bo_table);
|
||||
+1537
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,554 @@
|
||||
<?php
|
||||
$sub_menu = "300100";
|
||||
include_once('./_common.php');
|
||||
|
||||
if ($w == 'u') {
|
||||
check_demo();
|
||||
}
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
check_admin_token();
|
||||
|
||||
$gr_id = isset($_POST['gr_id']) ? preg_replace('/[^a-z0-9_]/i', '', (string)$_POST['gr_id']) : '';
|
||||
$bo_admin = isset($_POST['bo_admin']) ? preg_replace('/[^a-z0-9_\, \|\#]/i', '', $_POST['bo_admin']) : '';
|
||||
$bo_subject = isset($_POST['bo_subject']) ? strip_tags(clean_xss_attributes($_POST['bo_subject'])) : '';
|
||||
$bo_mobile_subject = isset($_POST['bo_mobile_subject']) ? strip_tags(clean_xss_attributes($_POST['bo_mobile_subject'])) : '';
|
||||
|
||||
if (!$gr_id) {
|
||||
alert('그룹 ID는 반드시 선택하세요.');
|
||||
}
|
||||
if (!$bo_table) {
|
||||
alert('게시판 TABLE명은 반드시 입력하세요.');
|
||||
}
|
||||
if (!preg_match("/^([A-Za-z0-9_]{1,20})$/", $bo_table)) {
|
||||
alert('게시판 TABLE명은 공백없이 영문자, 숫자, _ 만 사용 가능합니다. (20자 이내)');
|
||||
}
|
||||
if (!$bo_subject) {
|
||||
alert('게시판 제목을 입력하세요.');
|
||||
}
|
||||
|
||||
// 게시판명이 금지된 단어로 되어 있으면
|
||||
if ($w == '' && in_array($bo_table, get_bo_table_banned_word())) {
|
||||
alert('입력한 게시판 TABLE명을 사용할수 없습니다. 다른 이름으로 입력해 주세요.');
|
||||
}
|
||||
|
||||
$bo_include_head = isset($_POST['bo_include_head']) ? preg_replace(array("#[\\\]+$#", "#(<\?php|<\?)#i"), "", substr($_POST['bo_include_head'], 0, 255)) : '';
|
||||
$bo_include_tail = isset($_POST['bo_include_tail']) ? preg_replace(array("#[\\\]+$#", "#(<\?php|<\?)#i"), "", substr($_POST['bo_include_tail'], 0, 255)) : '';
|
||||
|
||||
// 관리자가 자동등록방지를 사용해야 할 경우
|
||||
if ($board && (isset($board['bo_include_head']) && $board['bo_include_head'] !== $bo_include_head || $board['bo_include_tail'] !== $bo_include_tail) && function_exists('get_admin_captcha_by') && get_admin_captcha_by()) {
|
||||
include_once(G5_CAPTCHA_PATH . '/captcha.lib.php');
|
||||
|
||||
if (!chk_captcha()) {
|
||||
alert('자동등록방지 숫자가 틀렸습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($file = $bo_include_head) {
|
||||
$file_ext = pathinfo($file, PATHINFO_EXTENSION);
|
||||
|
||||
if (!$file_ext || !in_array($file_ext, array('php', 'htm', 'html')) || !preg_match('/^.*\.(php|htm|html)$/i', $file)) {
|
||||
alert('상단 파일 경로의 확장자는 php, htm, html 만 허용합니다.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($file = $bo_include_tail) {
|
||||
$file_ext = pathinfo($file, PATHINFO_EXTENSION);
|
||||
|
||||
if (!$file_ext || !in_array($file_ext, array('php', 'htm', 'html')) || !preg_match('/^.*\.(php|htm|html)$/i', $file)) {
|
||||
alert('하단 파일 경로의 확장자는 php, htm, html 만 허용합니다.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_include_path_check($bo_include_head, 1)) {
|
||||
alert('상단 파일 경로에 포함시킬수 없는 문자열이 있습니다.');
|
||||
}
|
||||
|
||||
if (!is_include_path_check($bo_include_tail, 1)) {
|
||||
alert('하단 파일 경로에 포함시킬수 없는 문자열이 있습니다.');
|
||||
}
|
||||
|
||||
if (function_exists('filter_input_include_path')) {
|
||||
$bo_include_head = filter_input_include_path($bo_include_head);
|
||||
$bo_include_tail = filter_input_include_path($bo_include_tail);
|
||||
}
|
||||
|
||||
$board_path = G5_DATA_PATH . '/file/' . $bo_table;
|
||||
|
||||
// 게시판 디렉토리 생성
|
||||
@mkdir($board_path, G5_DIR_PERMISSION);
|
||||
@chmod($board_path, G5_DIR_PERMISSION);
|
||||
|
||||
// 디렉토리에 있는 파일의 목록을 보이지 않게 한다.
|
||||
$file = $board_path . '/index.php';
|
||||
if ($f = @fopen($file, 'w')) {
|
||||
@fwrite($f, '');
|
||||
@fclose($f);
|
||||
@chmod($file, G5_FILE_PERMISSION);
|
||||
}
|
||||
|
||||
// 분류에 & 나 = 는 사용이 불가하므로 2바이트로 바꾼다.
|
||||
$src_char = array('&', '=');
|
||||
$dst_char = array('&', '〓');
|
||||
$bo_category_list = isset($_POST['bo_category_list']) ? str_replace($src_char, $dst_char, $_POST['bo_category_list']) : '';
|
||||
//https://github.com/gnuboard/gnuboard5/commit/f5f4925d4eb28ba1af728e1065fc2bdd9ce1da58 에 따른 조치
|
||||
$str_bo_category_list = preg_replace("/[\<\>\'\"\\\'\\\"\%\=\(\)\/\^\*]/", "", (string)$bo_category_list);
|
||||
|
||||
$bo_use_category = isset($_POST['bo_use_category']) ? (int) $_POST['bo_use_category'] : 0;
|
||||
$bo_use_sideview = isset($_POST['bo_use_sideview']) ? (int) $_POST['bo_use_sideview'] : 0;
|
||||
$bo_use_dhtml_editor = isset($_POST['bo_use_dhtml_editor']) ? (int) $_POST['bo_use_dhtml_editor'] : 0;
|
||||
$bo_use_good = isset($_POST['bo_use_good']) ? (int) $_POST['bo_use_good'] : 0;
|
||||
$bo_use_nogood = isset($_POST['bo_use_nogood']) ? (int) $_POST['bo_use_nogood'] : 0;
|
||||
$bo_use_name = isset($_POST['bo_use_name']) ? (int) $_POST['bo_use_name'] : 0;
|
||||
$bo_use_signature = isset($_POST['bo_use_signature']) ? (int) $_POST['bo_use_signature'] : 0;
|
||||
$bo_use_ip_view = isset($_POST['bo_use_ip_view']) ? (int) $_POST['bo_use_ip_view'] : 0;
|
||||
$bo_use_list_view = isset($_POST['bo_use_list_view']) ? (int) $_POST['bo_use_list_view'] : 0;
|
||||
$bo_use_list_file = isset($_POST['bo_use_list_file']) ? (int) $_POST['bo_use_list_file'] : 0;
|
||||
$bo_use_list_content = isset($_POST['bo_use_list_content']) ? (int) $_POST['bo_use_list_content'] : 0;
|
||||
$bo_use_email = isset($_POST['bo_use_email']) ? (int) $_POST['bo_use_email'] : 0;
|
||||
$bo_use_sns = isset($_POST['bo_use_sns']) ? (int) $_POST['bo_use_sns'] : 0;
|
||||
$bo_use_captcha = isset($_POST['bo_use_captcha']) ? (int) $_POST['bo_use_captcha'] : 0;
|
||||
$bo_table_width = isset($_POST['bo_table_width']) ? (int) $_POST['bo_table_width'] : 0;
|
||||
$bo_subject_len = isset($_POST['bo_subject_len']) ? (int) $_POST['bo_subject_len'] : 0;
|
||||
$bo_mobile_subject_len = isset($_POST['bo_mobile_subject_len']) ? (int) $_POST['bo_mobile_subject_len'] : 0;
|
||||
$bo_page_rows = isset($_POST['bo_page_rows']) ? (int) $_POST['bo_page_rows'] : 0;
|
||||
$bo_mobile_page_rows = isset($_POST['bo_mobile_page_rows']) ? (int) $_POST['bo_mobile_page_rows'] : 0;
|
||||
$bo_use_rss_view = isset($_POST['bo_use_rss_view']) ? (int) $_POST['bo_use_rss_view'] : 0;
|
||||
$bo_use_secret = isset($_POST['bo_use_secret']) ? (int) $_POST['bo_use_secret'] : 0;
|
||||
$bo_use_file_content = isset($_POST['bo_use_file_content']) ? (int) $_POST['bo_use_file_content'] : 0;
|
||||
$bo_new = isset($_POST['bo_new']) ? (int) $_POST['bo_new'] : 0;
|
||||
$bo_hot = isset($_POST['bo_hot']) ? (int) $_POST['bo_hot'] : 0;
|
||||
$bo_image_width = isset($_POST['bo_image_width']) ? (int) $_POST['bo_image_width'] : 0;
|
||||
$bo_use_search = isset($_POST['bo_use_search']) ? (int) $_POST['bo_use_search'] : 0;
|
||||
$bo_use_cert = isset($_POST['bo_use_cert']) ? preg_replace('/[^0-9a-z_]/i', '', $_POST['bo_use_cert']) : '';
|
||||
$bo_device = isset($_POST['bo_device']) ? clean_xss_tags($_POST['bo_device'], 1, 1) : '';
|
||||
$bo_list_level = isset($_POST['bo_list_level']) ? (int) $_POST['bo_list_level'] : 0;
|
||||
$bo_read_level = isset($_POST['bo_read_level']) ? (int) $_POST['bo_read_level'] : 0;
|
||||
$bo_write_level = isset($_POST['bo_write_level']) ? (int) $_POST['bo_write_level'] : 0;
|
||||
$bo_reply_level = isset($_POST['bo_reply_level']) ? (int) $_POST['bo_reply_level'] : 0;
|
||||
$bo_comment_level = isset($_POST['bo_comment_level']) ? (int) $_POST['bo_comment_level'] : 0;
|
||||
$bo_html_level = isset($_POST['bo_html_level']) ? (int) $_POST['bo_html_level'] : 0;
|
||||
$bo_link_level = isset($_POST['bo_link_level']) ? (int) $_POST['bo_link_level'] : 0;
|
||||
$bo_count_modify = isset($_POST['bo_count_modify']) ? (int) $_POST['bo_count_modify'] : 0;
|
||||
$bo_count_delete = isset($_POST['bo_count_delete']) ? (int) $_POST['bo_count_delete'] : 0;
|
||||
$bo_upload_level = isset($_POST['bo_upload_level']) ? (int) $_POST['bo_upload_level'] : 0;
|
||||
$bo_download_level = isset($_POST['bo_download_level']) ? (int) $_POST['bo_download_level'] : 0;
|
||||
$bo_read_point = isset($_POST['bo_read_point']) ? (int) $_POST['bo_read_point'] : 0;
|
||||
$bo_write_point = isset($_POST['bo_write_point']) ? (int) $_POST['bo_write_point'] : 0;
|
||||
$bo_comment_point = isset($_POST['bo_comment_point']) ? (int) $_POST['bo_comment_point'] : 0;
|
||||
$bo_download_point = isset($_POST['bo_download_point']) ? (int) $_POST['bo_download_point'] : 0;
|
||||
$bo_select_editor = isset($_POST['bo_select_editor']) ? clean_xss_tags($_POST['bo_select_editor'], 1, 1) : '';
|
||||
$bo_skin = isset($_POST['bo_skin']) ? clean_xss_tags($_POST['bo_skin'], 1, 1) : '';
|
||||
$bo_mobile_skin = isset($_POST['bo_mobile_skin']) ? clean_xss_tags($_POST['bo_mobile_skin'], 1, 1) : '';
|
||||
$bo_content_head = isset($_POST['bo_content_head']) ? $_POST['bo_content_head'] : '';
|
||||
$bo_content_tail = isset($_POST['bo_content_tail']) ? $_POST['bo_content_tail'] : '';
|
||||
$bo_mobile_content_head = isset($_POST['bo_mobile_content_head']) ? $_POST['bo_mobile_content_head'] : '';
|
||||
$bo_mobile_content_tail = isset($_POST['bo_mobile_content_tail']) ? $_POST['bo_mobile_content_tail'] : '';
|
||||
$bo_insert_content = isset($_POST['bo_insert_content']) ? $_POST['bo_insert_content'] : '';
|
||||
$bo_gallery_cols = isset($_POST['bo_gallery_cols']) ? (int) $_POST['bo_gallery_cols'] : 0;
|
||||
$bo_gallery_width = isset($_POST['bo_gallery_width']) ? (int) $_POST['bo_gallery_width'] : 0;
|
||||
$bo_gallery_height = isset($_POST['bo_gallery_height']) ? (int) $_POST['bo_gallery_height'] : 0;
|
||||
$bo_mobile_gallery_width = isset($_POST['bo_mobile_gallery_width']) ? (int) $_POST['bo_mobile_gallery_width'] : 0;
|
||||
$bo_mobile_gallery_height = isset($_POST['bo_mobile_gallery_height']) ? (int) $_POST['bo_mobile_gallery_height'] : 0;
|
||||
$bo_upload_count = isset($_POST['bo_upload_count']) ? (int) $_POST['bo_upload_count'] : 0;
|
||||
$bo_upload_size = isset($_POST['bo_upload_size']) ? (int) $_POST['bo_upload_size'] : 0;
|
||||
$bo_reply_order = isset($_POST['bo_reply_order']) ? (int) $_POST['bo_reply_order'] : 0;
|
||||
$bo_order = isset($_POST['bo_order']) ? (int) $_POST['bo_order'] : 0;
|
||||
$bo_write_min = isset($_POST['bo_write_min']) ? (int) $_POST['bo_write_min'] : 0;
|
||||
$bo_write_max = isset($_POST['bo_write_max']) ? (int) $_POST['bo_write_max'] : 0;
|
||||
$bo_comment_min = isset($_POST['bo_comment_min']) ? (int) $_POST['bo_comment_min'] : 0;
|
||||
$bo_comment_max = isset($_POST['bo_comment_max']) ? (int) $_POST['bo_comment_max'] : 0;
|
||||
$bo_sort_field = isset($_POST['bo_sort_field']) ? clean_xss_tags($_POST['bo_sort_field'], 1, 1) : '';
|
||||
// 💡 [추가] 예약 중복 검사 설정값
|
||||
$bo_use_reservation_overlap = isset($_POST['bo_use_reservation_overlap']) ? (int) $_POST['bo_use_reservation_overlap'] : 0;
|
||||
$bo_reservation_overlap_count = isset($_POST['bo_reservation_overlap_count']) ? (int) $_POST['bo_reservation_overlap_count'] : 0;
|
||||
|
||||
if (strpbrk($bo_skin.$bo_mobile_skin, "?%*:|\"<>") !== false) {
|
||||
alert('스킨 디렉토리명 오류!');
|
||||
}
|
||||
|
||||
$etcs = array();
|
||||
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$etcs['bo_' . $i . '_subj'] = ${'bo_' . $i . '_subj'} = isset($_POST['bo_' . $i . '_subj']) ? $_POST['bo_' . $i . '_subj'] : '';
|
||||
$etcs['bo_' . $i] = ${'bo_' . $i} = isset($_POST['bo_' . $i]) ? $_POST['bo_' . $i] : '';
|
||||
}
|
||||
|
||||
$sql_common = " gr_id = '{$gr_id}',
|
||||
bo_subject = '{$bo_subject}',
|
||||
bo_mobile_subject = '{$bo_mobile_subject}',
|
||||
bo_device = '{$bo_device}',
|
||||
bo_admin = '{$bo_admin}',
|
||||
bo_list_level = '{$bo_list_level}',
|
||||
bo_read_level = '{$bo_read_level}',
|
||||
bo_write_level = '{$bo_write_level}',
|
||||
bo_reply_level = '{$bo_reply_level}',
|
||||
bo_comment_level = '{$bo_comment_level}',
|
||||
bo_html_level = '{$bo_html_level}',
|
||||
bo_link_level = '{$bo_link_level}',
|
||||
bo_count_modify = '{$bo_count_modify}',
|
||||
bo_count_delete = '{$bo_count_delete}',
|
||||
bo_upload_level = '{$bo_upload_level}',
|
||||
bo_download_level = '{$bo_download_level}',
|
||||
bo_read_point = '{$bo_read_point}',
|
||||
bo_write_point = '{$bo_write_point}',
|
||||
bo_comment_point = '{$bo_comment_point}',
|
||||
bo_download_point = '{$bo_download_point}',
|
||||
bo_use_category = '{$bo_use_category}',
|
||||
bo_category_list = '{$str_bo_category_list}',
|
||||
bo_use_sideview = '{$bo_use_sideview}',
|
||||
bo_use_file_content = '{$bo_use_file_content}',
|
||||
bo_use_secret = '{$bo_use_secret}',
|
||||
bo_use_dhtml_editor = '{$bo_use_dhtml_editor}',
|
||||
bo_select_editor = '{$bo_select_editor}',
|
||||
bo_use_rss_view = '{$bo_use_rss_view}',
|
||||
bo_use_good = '{$bo_use_good}',
|
||||
bo_use_nogood = '{$bo_use_nogood}',
|
||||
bo_use_name = '{$bo_use_name}',
|
||||
bo_use_signature = '{$bo_use_signature}',
|
||||
bo_use_ip_view = '{$bo_use_ip_view}',
|
||||
bo_use_list_view = '{$bo_use_list_view}',
|
||||
bo_use_list_file = '{$bo_use_list_file}',
|
||||
bo_use_list_content = '{$bo_use_list_content}',
|
||||
bo_use_email = '{$bo_use_email}',
|
||||
bo_use_cert = '{$bo_use_cert}',
|
||||
bo_use_sns = '{$bo_use_sns}',
|
||||
bo_use_captcha = '{$bo_use_captcha}',
|
||||
bo_table_width = '{$bo_table_width}',
|
||||
bo_subject_len = '{$bo_subject_len}',
|
||||
bo_mobile_subject_len = '{$bo_mobile_subject_len}',
|
||||
bo_page_rows = '{$bo_page_rows}',
|
||||
bo_mobile_page_rows = '{$bo_mobile_page_rows}',
|
||||
bo_new = '{$bo_new}',
|
||||
bo_hot = '{$bo_hot}',
|
||||
bo_image_width = '{$bo_image_width}',
|
||||
bo_skin = '{$bo_skin}',
|
||||
bo_mobile_skin = '{$bo_mobile_skin}',
|
||||
bo_use_reservation_overlap = '{$bo_use_reservation_overlap}',
|
||||
bo_reservation_overlap_count = '{$bo_reservation_overlap_count}',
|
||||
";
|
||||
|
||||
// 최고 관리자인 경우에만 수정가능
|
||||
if ($is_admin === 'super') {
|
||||
$sql_common .= " bo_include_head = '" . $bo_include_head . "',
|
||||
bo_include_tail = '" . $bo_include_tail . "',
|
||||
bo_content_head = '{$bo_content_head}',
|
||||
bo_content_tail = '{$bo_content_tail}',
|
||||
bo_mobile_content_head = '{$bo_mobile_content_head}',
|
||||
bo_mobile_content_tail = '{$bo_mobile_content_tail}',
|
||||
";
|
||||
}
|
||||
|
||||
$sql_common .= " bo_insert_content = '{$bo_insert_content}',
|
||||
bo_gallery_cols = '{$bo_gallery_cols}',
|
||||
bo_gallery_width = '{$bo_gallery_width}',
|
||||
bo_gallery_height = '{$bo_gallery_height}',
|
||||
bo_mobile_gallery_width = '{$bo_mobile_gallery_width}',
|
||||
bo_mobile_gallery_height= '{$bo_mobile_gallery_height}',
|
||||
bo_upload_count = '{$bo_upload_count}',
|
||||
bo_upload_size = '{$bo_upload_size}',
|
||||
bo_reply_order = '{$bo_reply_order}',
|
||||
bo_use_search = '{$bo_use_search}',
|
||||
bo_order = '{$bo_order}',
|
||||
bo_write_min = '{$bo_write_min}',
|
||||
bo_write_max = '{$bo_write_max}',
|
||||
bo_comment_min = '{$bo_comment_min}',
|
||||
bo_comment_max = '{$bo_comment_max}',
|
||||
bo_sort_field = '{$bo_sort_field}',
|
||||
bo_1_subj = '{$bo_1_subj}',
|
||||
bo_2_subj = '{$bo_2_subj}',
|
||||
bo_3_subj = '{$bo_3_subj}',
|
||||
bo_4_subj = '{$bo_4_subj}',
|
||||
bo_5_subj = '{$bo_5_subj}',
|
||||
bo_6_subj = '{$bo_6_subj}',
|
||||
bo_7_subj = '{$bo_7_subj}',
|
||||
bo_8_subj = '{$bo_8_subj}',
|
||||
bo_9_subj = '{$bo_9_subj}',
|
||||
bo_10_subj = '{$bo_10_subj}',
|
||||
bo_1 = '{$bo_1}',
|
||||
bo_2 = '{$bo_2}',
|
||||
bo_3 = '{$bo_3}',
|
||||
bo_4 = '{$bo_4}',
|
||||
bo_5 = '{$bo_5}',
|
||||
bo_6 = '{$bo_6}',
|
||||
bo_7 = '{$bo_7}',
|
||||
bo_8 = '{$bo_8}',
|
||||
bo_9 = '{$bo_9}',
|
||||
bo_10 = '{$bo_10}' ";
|
||||
|
||||
if ($w == '') {
|
||||
$row = sql_fetch(" select count(*) as cnt from {$g5['board_table']} where bo_table = '{$bo_table}' ");
|
||||
if ($row['cnt']) {
|
||||
alert($bo_table . ' 은(는) 이미 존재하는 TABLE 입니다.');
|
||||
}
|
||||
|
||||
$sql = " insert into {$g5['board_table']}
|
||||
set bo_table = '{$bo_table}',
|
||||
bo_count_write = '0',
|
||||
bo_count_comment = '0',
|
||||
$sql_common ";
|
||||
sql_query($sql);
|
||||
|
||||
// 게시판 테이블 생성
|
||||
$file = file('./sql_write.sql');
|
||||
$file = get_db_create_replace($file);
|
||||
|
||||
$sql = implode("\n", $file);
|
||||
|
||||
$create_table = $g5['write_prefix'] . $bo_table;
|
||||
|
||||
// sql_board.sql 파일의 테이블명을 변환
|
||||
$source = array('/__TABLE_NAME__/', '/;/');
|
||||
$target = array($create_table, '');
|
||||
$sql = preg_replace($source, $target, $sql);
|
||||
sql_query($sql, false);
|
||||
} elseif ($w == 'u') {
|
||||
// 게시판의 글 수
|
||||
$sql = " select count(*) as cnt from {$g5['write_prefix']}{$bo_table} where wr_is_comment = 0 ";
|
||||
$row = sql_fetch($sql);
|
||||
$bo_count_write = $row['cnt'];
|
||||
|
||||
// 게시판의 코멘트 수
|
||||
$sql = " select count(*) as cnt from {$g5['write_prefix']}{$bo_table} where wr_is_comment = 1 ";
|
||||
$row = sql_fetch($sql);
|
||||
$bo_count_comment = $row['cnt'];
|
||||
|
||||
// 글수 조정
|
||||
/*
|
||||
엔피씨님의 팁으로 교체합니다. 130308
|
||||
http://sir.kr/g5_tiptech/27207
|
||||
*/
|
||||
if (isset($_POST['proc_count'])) {
|
||||
// 원글을 얻습니다.
|
||||
//$sql = " select wr_id from {$g5['write_prefix']}{$bo_table} where wr_is_comment = 0 ";
|
||||
$sql = " select a.wr_id, (count(b.wr_parent) - 1) as cnt from {$g5['write_prefix']}{$bo_table} a, {$g5['write_prefix']}{$bo_table} b where a.wr_id=b.wr_parent and a.wr_is_comment=0 group by a.wr_id ";
|
||||
$result = sql_query($sql);
|
||||
for ($i = 0; $row = sql_fetch_array($result); $i++) {
|
||||
/*
|
||||
// 코멘트수를 얻습니다.
|
||||
$sql2 = " select count(*) as cnt from {$g5['write_prefix']}$bo_table where wr_parent = '{$row['wr_id']}' and wr_is_comment = 1 ";
|
||||
$row2 = sql_fetch($sql2);
|
||||
*/
|
||||
|
||||
sql_query(" update {$g5['write_prefix']}{$bo_table} set wr_comment = '{$row['cnt']}' where wr_id = '{$row['wr_id']}' ");
|
||||
}
|
||||
}
|
||||
|
||||
// 공지사항에는 등록되어 있지만 실제 존재하지 않는 글 아이디는 삭제합니다.
|
||||
$bo_notice = "";
|
||||
$lf = "";
|
||||
if ($board['bo_notice']) {
|
||||
$tmp_array = explode(",", $board['bo_notice']);
|
||||
for ($i = 0; $i < count($tmp_array); $i++) {
|
||||
$tmp_wr_id = trim($tmp_array[$i]);
|
||||
$row = sql_fetch(" select count(*) as cnt from {$g5['write_prefix']}{$bo_table} where wr_id = '{$tmp_wr_id}' ");
|
||||
if ($row['cnt']) {
|
||||
$bo_notice .= $lf . $tmp_wr_id;
|
||||
$lf = ",";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sql = " update {$g5['board_table']}
|
||||
set bo_notice = '{$bo_notice}',
|
||||
bo_count_write = '{$bo_count_write}',
|
||||
bo_count_comment = '{$bo_count_comment}',
|
||||
{$sql_common}
|
||||
where bo_table = '{$bo_table}' ";
|
||||
sql_query($sql);
|
||||
}
|
||||
|
||||
|
||||
// 같은 그룹내 게시판 동일 옵션 적용
|
||||
$grp_fields = '';
|
||||
if (is_checked('chk_grp_device')) $grp_fields .= " , bo_device = '{$bo_device}' ";
|
||||
if (is_checked('chk_grp_admin')) $grp_fields .= " , bo_admin = '{$bo_admin}' ";
|
||||
if (is_checked('chk_grp_list_level')) $grp_fields .= " , bo_list_level = '{$bo_list_level}' ";
|
||||
if (is_checked('chk_grp_read_level')) $grp_fields .= " , bo_read_level = '{$bo_read_level}' ";
|
||||
if (is_checked('chk_grp_write_level')) $grp_fields .= " , bo_write_level = '{$bo_write_level}' ";
|
||||
if (is_checked('chk_grp_reply_level')) $grp_fields .= " , bo_reply_level = '{$bo_reply_level}' ";
|
||||
if (is_checked('chk_grp_comment_level')) $grp_fields .= " , bo_comment_level = '{$bo_comment_level}' ";
|
||||
if (is_checked('chk_grp_link_level')) $grp_fields .= " , bo_link_level = '{$bo_link_level}' ";
|
||||
if (is_checked('chk_grp_upload_level')) $grp_fields .= " , bo_upload_level = '{$bo_upload_level}' ";
|
||||
if (is_checked('chk_grp_download_level')) $grp_fields .= " , bo_download_level = '{$bo_download_level}' ";
|
||||
if (is_checked('chk_grp_html_level')) $grp_fields .= " , bo_html_level = '{$bo_html_level}' ";
|
||||
if (is_checked('chk_grp_count_modify')) $grp_fields .= " , bo_count_modify = '{$bo_count_modify}' ";
|
||||
if (is_checked('chk_grp_count_delete')) $grp_fields .= " , bo_count_delete = '{$bo_count_delete}' ";
|
||||
if (is_checked('chk_grp_read_point')) $grp_fields .= " , bo_read_point = '{$bo_read_point}' ";
|
||||
if (is_checked('chk_grp_write_point')) $grp_fields .= " , bo_write_point = '{$bo_write_point}' ";
|
||||
if (is_checked('chk_grp_comment_point')) $grp_fields .= " , bo_comment_point = '{$bo_comment_point}' ";
|
||||
if (is_checked('chk_grp_download_point')) $grp_fields .= " , bo_download_point = '{$bo_download_point}' ";
|
||||
if (is_checked('chk_grp_category_list')) {
|
||||
$grp_fields .= " , bo_category_list = '{$str_bo_category_list}' ";
|
||||
$grp_fields .= " , bo_use_category = '{$bo_use_category}' ";
|
||||
}
|
||||
if (is_checked('chk_grp_use_sideview')) $grp_fields .= " , bo_use_sideview = '{$bo_use_sideview}' ";
|
||||
if (is_checked('chk_grp_use_file_content')) $grp_fields .= " , bo_use_file_content = '{$bo_use_file_content}' ";
|
||||
if (is_checked('chk_grp_use_secret')) $grp_fields .= " , bo_use_secret = '{$bo_use_secret}' ";
|
||||
if (is_checked('chk_grp_use_dhtml_editor')) $grp_fields .= " , bo_use_dhtml_editor = '{$bo_use_dhtml_editor}' ";
|
||||
if (is_checked('chk_grp_select_editor')) $grp_fields .= " , bo_select_editor = '{$bo_select_editor}' ";
|
||||
if (is_checked('chk_grp_use_rss_view')) $grp_fields .= " , bo_use_rss_view = '{$bo_use_rss_view}' ";
|
||||
if (is_checked('chk_grp_use_good')) $grp_fields .= " , bo_use_good = '{$bo_use_good}' ";
|
||||
if (is_checked('chk_grp_use_nogood')) $grp_fields .= " , bo_use_nogood = '{$bo_use_nogood}' ";
|
||||
if (is_checked('chk_grp_use_name')) $grp_fields .= " , bo_use_name = '{$bo_use_name}' ";
|
||||
if (is_checked('chk_grp_use_signature')) $grp_fields .= " , bo_use_signature = '{$bo_use_signature}' ";
|
||||
if (is_checked('chk_grp_use_ip_view')) $grp_fields .= " , bo_use_ip_view = '{$bo_use_ip_view}' ";
|
||||
if (is_checked('chk_grp_use_list_view')) $grp_fields .= " , bo_use_list_view = '{$bo_use_list_view}' ";
|
||||
if (is_checked('chk_grp_use_list_file')) $grp_fields .= " , bo_use_list_file = '{$bo_use_list_file}' ";
|
||||
if (is_checked('chk_grp_use_list_content')) $grp_fields .= " , bo_use_list_content = '{$bo_use_list_content}' ";
|
||||
if (is_checked('chk_grp_use_email')) $grp_fields .= " , bo_use_email = '{$bo_use_email}' ";
|
||||
if (is_checked('chk_grp_use_cert')) $grp_fields .= " , bo_use_cert = '{$bo_use_cert}' ";
|
||||
if (is_checked('chk_grp_use_sns')) $grp_fields .= " , bo_use_sns = '{$bo_use_sns}' ";
|
||||
if (is_checked('chk_grp_use_captcha')) $grp_fields .= " , bo_use_captcha = '{$bo_use_captcha}' ";
|
||||
if (is_checked('chk_grp_skin')) $grp_fields .= " , bo_skin = '{$bo_skin}' ";
|
||||
if (is_checked('chk_grp_mobile_skin')) $grp_fields .= " , bo_mobile_skin = '{$bo_mobile_skin}' ";
|
||||
if (is_checked('chk_grp_gallery_cols')) $grp_fields .= " , bo_gallery_cols = '{$bo_gallery_cols}' ";
|
||||
if (is_checked('chk_grp_gallery_width')) $grp_fields .= " , bo_gallery_width = '{$bo_gallery_width}' ";
|
||||
if (is_checked('chk_grp_gallery_height')) $grp_fields .= " , bo_gallery_height = '{$bo_gallery_height}' ";
|
||||
if (is_checked('chk_grp_mobile_gallery_width')) $grp_fields .= " , bo_mobile_gallery_width = '{$bo_mobile_gallery_width}' ";
|
||||
if (is_checked('chk_grp_mobile_gallery_height'))$grp_fields .= " , bo_mobile_gallery_height = '{$bo_mobile_gallery_height}' ";
|
||||
if (is_checked('chk_grp_table_width')) $grp_fields .= " , bo_table_width = '{$bo_table_width}' ";
|
||||
if (is_checked('chk_grp_page_rows')) $grp_fields .= " , bo_page_rows = '{$bo_page_rows}' ";
|
||||
if (is_checked('chk_grp_mobile_page_rows')) $grp_fields .= " , bo_mobile_page_rows = '{$bo_mobile_page_rows}' ";
|
||||
if (is_checked('chk_grp_subject_len')) $grp_fields .= " , bo_subject_len = '{$bo_subject_len}' ";
|
||||
if (is_checked('chk_grp_mobile_subject_len')) $grp_fields .= " , bo_mobile_subject_len = '{$bo_mobile_subject_len}' ";
|
||||
if (is_checked('chk_grp_new')) $grp_fields .= " , bo_new = '{$bo_new}' ";
|
||||
if (is_checked('chk_grp_hot')) $grp_fields .= " , bo_hot = '{$bo_hot}' ";
|
||||
if (is_checked('chk_grp_image_width')) $grp_fields .= " , bo_image_width = '{$bo_image_width}' ";
|
||||
if (is_checked('chk_grp_reply_order')) $grp_fields .= " , bo_reply_order = '{$bo_reply_order}' ";
|
||||
if (is_checked('chk_grp_sort_field')) $grp_fields .= " , bo_sort_field = '{$bo_sort_field}' ";
|
||||
if (is_checked('chk_grp_write_min')) $grp_fields .= " , bo_write_min = '{$bo_write_min}' ";
|
||||
if (is_checked('chk_grp_write_max')) $grp_fields .= " , bo_write_max = '{$bo_write_max}' ";
|
||||
if (is_checked('chk_grp_comment_min')) $grp_fields .= " , bo_comment_min = '{$bo_comment_min}' ";
|
||||
if (is_checked('chk_grp_comment_max')) $grp_fields .= " , bo_comment_max = '{$bo_comment_max}' ";
|
||||
if (is_checked('chk_grp_upload_count')) $grp_fields .= " , bo_upload_count = '{$bo_upload_count}' ";
|
||||
if (is_checked('chk_grp_upload_size')) $grp_fields .= " , bo_upload_size = '{$bo_upload_size}' ";
|
||||
if (is_checked('chk_grp_use_reservation_overlap')) $grp_fields .= " , bo_use_reservation_overlap = '{$bo_use_reservation_overlap}' ";
|
||||
if (is_checked('chk_grp_reservation_overlap_count')) $grp_fields .= " , bo_reservation_overlap_count = '{$bo_reservation_overlap_count}' ";
|
||||
|
||||
//최고관리자만 수정가능
|
||||
if ($is_admin === 'super') {
|
||||
if (is_checked('chk_grp_include_head')) $grp_fields .= " , bo_include_head = '{$bo_include_head}' ";
|
||||
if (is_checked('chk_grp_include_tail')) $grp_fields .= " , bo_include_tail = '{$bo_include_tail}' ";
|
||||
if (is_checked('chk_grp_content_head')) $grp_fields .= " , bo_content_head = '{$bo_content_head}' ";
|
||||
if (is_checked('chk_grp_content_tail')) $grp_fields .= " , bo_content_tail = '{$bo_content_tail}' ";
|
||||
if (is_checked('chk_grp_mobile_content_head')) $grp_fields .= " , bo_mobile_content_head = '{$bo_mobile_content_head}' ";
|
||||
if (is_checked('chk_grp_mobile_content_tail')) $grp_fields .= " , bo_mobile_content_tail = '{$bo_mobile_content_tail}' ";
|
||||
}
|
||||
|
||||
if (is_checked('chk_grp_insert_content')) $grp_fields .= " , bo_insert_content = '{$bo_insert_content}' ";
|
||||
if (is_checked('chk_grp_use_search')) $grp_fields .= " , bo_use_search = '{$bo_use_search}' ";
|
||||
if (is_checked('chk_grp_order')) $grp_fields .= " , bo_order = '{$bo_order}' ";
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
if (is_checked('chk_grp_' . $i)) {
|
||||
$grp_fields .= " , bo_{$i}_subj = '" . $etcs['bo_' . $i . '_subj'] . "' ";
|
||||
$grp_fields .= " , bo_{$i} = '" . $etcs['bo_' . $i] . "' ";
|
||||
}
|
||||
}
|
||||
|
||||
if ($grp_fields) {
|
||||
sql_query(" update {$g5['board_table']} set bo_table = bo_table {$grp_fields} where gr_id = '$gr_id' ");
|
||||
}
|
||||
|
||||
|
||||
// 모든 게시판 동일 옵션 적용
|
||||
$all_fields = '';
|
||||
if (is_checked('chk_all_device')) $all_fields .= " , bo_device = '{$bo_device}' ";
|
||||
if (is_checked('chk_all_admin')) $all_fields .= " , bo_admin = '{$bo_admin}' ";
|
||||
if (is_checked('chk_all_list_level')) $all_fields .= " , bo_list_level = '{$bo_list_level}' ";
|
||||
if (is_checked('chk_all_read_level')) $all_fields .= " , bo_read_level = '{$bo_read_level}' ";
|
||||
if (is_checked('chk_all_write_level')) $all_fields .= " , bo_write_level = '{$bo_write_level}' ";
|
||||
if (is_checked('chk_all_reply_level')) $all_fields .= " , bo_reply_level = '{$bo_reply_level}' ";
|
||||
if (is_checked('chk_all_comment_level')) $all_fields .= " , bo_comment_level = '{$bo_comment_level}' ";
|
||||
if (is_checked('chk_all_link_level')) $all_fields .= " , bo_link_level = '{$bo_link_level}' ";
|
||||
if (is_checked('chk_all_upload_level')) $all_fields .= " , bo_upload_level = '{$bo_upload_level}' ";
|
||||
if (is_checked('chk_all_download_level')) $all_fields .= " , bo_download_level = '{$bo_download_level}' ";
|
||||
if (is_checked('chk_all_html_level')) $all_fields .= " , bo_html_level = '{$bo_html_level}' ";
|
||||
if (is_checked('chk_all_count_modify')) $all_fields .= " , bo_count_modify = '{$bo_count_modify}' ";
|
||||
if (is_checked('chk_all_count_delete')) $all_fields .= " , bo_count_delete = '{$bo_count_delete}' ";
|
||||
if (is_checked('chk_all_read_point')) $all_fields .= " , bo_read_point = '{$bo_read_point}' ";
|
||||
if (is_checked('chk_all_write_point')) $all_fields .= " , bo_write_point = '{$bo_write_point}' ";
|
||||
if (is_checked('chk_all_comment_point')) $all_fields .= " , bo_comment_point = '{$bo_comment_point}' ";
|
||||
if (is_checked('chk_all_download_point')) $all_fields .= " , bo_download_point = '{$bo_download_point}' ";
|
||||
if (is_checked('chk_all_category_list')) {
|
||||
$all_fields .= " , bo_category_list = '{$str_bo_category_list}' ";
|
||||
$all_fields .= " , bo_use_category = '{$bo_use_category}' ";
|
||||
}
|
||||
if (is_checked('chk_all_use_sideview')) $all_fields .= " , bo_use_sideview = '{$bo_use_sideview}' ";
|
||||
if (is_checked('chk_all_use_file_content')) $all_fields .= " , bo_use_file_content = '{$bo_use_file_content}' ";
|
||||
if (is_checked('chk_all_use_secret')) $all_fields .= " , bo_use_secret = '{$bo_use_secret}' ";
|
||||
if (is_checked('chk_all_use_dhtml_editor')) $all_fields .= " , bo_use_dhtml_editor = '{$bo_use_dhtml_editor}' ";
|
||||
if (is_checked('chk_all_select_editor')) $all_fields .= " , bo_select_editor = '{$bo_select_editor}' ";
|
||||
if (is_checked('chk_all_use_rss_view')) $all_fields .= " , bo_use_rss_view = '{$bo_use_rss_view}' ";
|
||||
if (is_checked('chk_all_use_good')) $all_fields .= " , bo_use_good = '{$bo_use_good}' ";
|
||||
if (is_checked('chk_all_use_nogood')) $all_fields .= " , bo_use_nogood = '{$bo_use_nogood}' ";
|
||||
if (is_checked('chk_all_use_name')) $all_fields .= " , bo_use_name = '{$bo_use_name}' ";
|
||||
if (is_checked('chk_all_use_signature')) $all_fields .= " , bo_use_signature = '{$bo_use_signature}' ";
|
||||
if (is_checked('chk_all_use_ip_view')) $all_fields .= " , bo_use_ip_view = '{$bo_use_ip_view}' ";
|
||||
if (is_checked('chk_all_use_list_view')) $all_fields .= " , bo_use_list_view = '{$bo_use_list_view}' ";
|
||||
if (is_checked('chk_all_use_list_file')) $all_fields .= " , bo_use_list_file = '{$bo_use_list_file}' ";
|
||||
if (is_checked('chk_all_use_list_content')) $all_fields .= " , bo_use_list_content = '{$bo_use_list_content}' ";
|
||||
if (is_checked('chk_all_use_email')) $all_fields .= " , bo_use_email = '{$bo_use_email}' ";
|
||||
if (is_checked('chk_all_use_cert')) $all_fields .= " , bo_use_cert = '{$bo_use_cert}' ";
|
||||
if (is_checked('chk_all_use_sns')) $all_fields .= " , bo_use_sns = '{$bo_use_sns}' ";
|
||||
if (is_checked('chk_all_use_captcha')) $all_fields .= " , bo_use_captcha = '{$bo_use_captcha}' ";
|
||||
if (is_checked('chk_all_skin')) $all_fields .= " , bo_skin = '{$bo_skin}' ";
|
||||
if (is_checked('chk_all_mobile_skin')) $all_fields .= " , bo_mobile_skin = '{$bo_mobile_skin}' ";
|
||||
if (is_checked('chk_all_gallery_cols')) $all_fields .= " , bo_gallery_cols = '{$bo_gallery_cols}' ";
|
||||
if (is_checked('chk_all_gallery_width')) $all_fields .= " , bo_gallery_width = '{$bo_gallery_width}' ";
|
||||
if (is_checked('chk_all_gallery_height')) $all_fields .= " , bo_gallery_height = '{$bo_gallery_height}' ";
|
||||
if (is_checked('chk_all_mobile_gallery_width')) $all_fields .= " , bo_mobile_gallery_width = '{$bo_mobile_gallery_width}' ";
|
||||
if (is_checked('chk_all_mobile_gallery_height')) $all_fields .= " , bo_mobile_gallery_height = '{$bo_mobile_gallery_height}' ";
|
||||
if (is_checked('chk_all_table_width')) $all_fields .= " , bo_table_width = '{$bo_table_width}' ";
|
||||
if (is_checked('chk_all_page_rows')) $all_fields .= " , bo_page_rows = '{$bo_page_rows}' ";
|
||||
if (is_checked('chk_all_mobile_page_rows')) $all_fields .= " , bo_mobile_page_rows = '{$bo_mobile_page_rows}' ";
|
||||
if (is_checked('chk_all_subject_len')) $all_fields .= " , bo_subject_len = '{$bo_subject_len}' ";
|
||||
if (is_checked('chk_all_mobile_subject_len')) $all_fields .= " , bo_mobile_subject_len = '{$bo_mobile_subject_len}' ";
|
||||
if (is_checked('chk_all_new')) $all_fields .= " , bo_new = '{$bo_new}' ";
|
||||
if (is_checked('chk_all_hot')) $all_fields .= " , bo_hot = '{$bo_hot}' ";
|
||||
if (is_checked('chk_all_image_width')) $all_fields .= " , bo_image_width = '{$bo_image_width}' ";
|
||||
if (is_checked('chk_all_reply_order')) $all_fields .= " , bo_reply_order = '{$bo_reply_order}' ";
|
||||
if (is_checked('chk_all_sort_field')) $all_fields .= " , bo_sort_field = '{$bo_sort_field}' ";
|
||||
if (is_checked('chk_all_write_min')) $all_fields .= " , bo_write_min = '{$bo_write_min}' ";
|
||||
if (is_checked('chk_all_write_max')) $all_fields .= " , bo_write_max = '{$bo_write_max}' ";
|
||||
if (is_checked('chk_all_comment_min')) $all_fields .= " , bo_comment_min = '{$bo_comment_min}' ";
|
||||
if (is_checked('chk_all_comment_max')) $all_fields .= " , bo_comment_max = '{$bo_comment_max}' ";
|
||||
if (is_checked('chk_all_upload_count')) $all_fields .= " , bo_upload_count = '{$bo_upload_count}' ";
|
||||
if (is_checked('chk_all_upload_size')) $all_fields .= " , bo_upload_size = '{$bo_upload_size}' ";
|
||||
if (is_checked('chk_all_use_reservation_overlap')) $all_fields .= " , bo_use_reservation_overlap = '{$bo_use_reservation_overlap}' ";
|
||||
if (is_checked('chk_all_reservation_overlap_count')) $all_fields .= " , bo_reservation_overlap_count = '{$bo_reservation_overlap_count}' ";
|
||||
|
||||
//최고관리자만 수정가능
|
||||
if ($is_admin === 'super') {
|
||||
if (is_checked('chk_all_include_head')) $all_fields .= " , bo_include_head = '{$bo_include_head}' ";
|
||||
if (is_checked('chk_all_include_tail')) $all_fields .= " , bo_include_tail = '{$bo_include_tail}' ";
|
||||
if (is_checked('chk_all_content_head')) $all_fields .= " , bo_content_head = '{$bo_content_head}' ";
|
||||
if (is_checked('chk_all_content_tail')) $all_fields .= " , bo_content_tail = '{$bo_content_tail}' ";
|
||||
if (is_checked('chk_all_mobile_content_head')) $all_fields .= " , bo_mobile_content_head = '{$bo_mobile_content_head}' ";
|
||||
if (is_checked('chk_all_mobile_content_tail')) $all_fields .= " , bo_mobile_content_tail = '{$bo_mobile_content_tail}' ";
|
||||
}
|
||||
|
||||
if (is_checked('chk_all_insert_content')) $all_fields .= " , bo_insert_content = '{$bo_insert_content}' ";
|
||||
if (is_checked('chk_all_use_search')) $all_fields .= " , bo_use_search = '{$bo_use_search}' ";
|
||||
if (is_checked('chk_all_order')) $all_fields .= " , bo_order = '{$bo_order}' ";
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
if (is_checked('chk_all_' . $i)) {
|
||||
$all_fields .= " , bo_{$i}_subj = '" . $etcs['bo_' . $i . '_subj'] . "' ";
|
||||
$all_fields .= " , bo_{$i} = '" . $etcs['bo_' . $i] . "' ";
|
||||
}
|
||||
}
|
||||
|
||||
if ($all_fields) {
|
||||
sql_query(" update {$g5['board_table']} set bo_table = bo_table {$all_fields} ");
|
||||
}
|
||||
|
||||
delete_cache_latest($bo_table);
|
||||
|
||||
if (function_exists('get_admin_captcha_by')) {
|
||||
get_admin_captcha_by('remove');
|
||||
}
|
||||
|
||||
run_event('admin_board_form_update', $bo_table, $w);
|
||||
|
||||
goto_url("./board_form.php?w=u&bo_table={$bo_table}&{$qstr}");
|
||||
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
$sub_menu = "300100";
|
||||
require_once './_common.php';
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'r');
|
||||
|
||||
$sql_common = " from {$g5['board_table']} a ";
|
||||
$sql_search = " where (1) ";
|
||||
|
||||
if ($is_admin != "super") {
|
||||
$sql_common .= " , {$g5['group_table']} b ";
|
||||
$sql_search .= " and (a.gr_id = b.gr_id and b.gr_admin = '{$member['mb_id']}') ";
|
||||
}
|
||||
|
||||
if ($stx) {
|
||||
$sql_search .= " and ( ";
|
||||
switch ($sfl) {
|
||||
case "bo_table":
|
||||
$sql_search .= " ($sfl like '$stx%') ";
|
||||
break;
|
||||
case "a.gr_id":
|
||||
$sql_search .= " ($sfl = '$stx') ";
|
||||
break;
|
||||
default:
|
||||
$sql_search .= " ($sfl like '%$stx%') ";
|
||||
break;
|
||||
}
|
||||
$sql_search .= " ) ";
|
||||
}
|
||||
|
||||
if (!$sst) {
|
||||
$sst = "a.gr_id, a.bo_table";
|
||||
$sod = "asc";
|
||||
}
|
||||
$sql_order = " order by $sst $sod ";
|
||||
|
||||
$sql = " select count(*) as cnt {$sql_common} {$sql_search} {$sql_order} ";
|
||||
$row = sql_fetch($sql);
|
||||
$total_count = $row['cnt'];
|
||||
|
||||
$rows = $config['cf_page_rows'];
|
||||
$total_page = ceil($total_count / $rows); // 전체 페이지 계산
|
||||
if ($page < 1) {
|
||||
$page = 1; // 페이지가 없으면 첫 페이지 (1 페이지)
|
||||
}
|
||||
$from_record = ($page - 1) * $rows; // 시작 열을 구함
|
||||
|
||||
$sql = " select * {$sql_common} {$sql_search} {$sql_order} limit {$from_record}, {$rows} ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
$listall = '<a href="' . $_SERVER['SCRIPT_NAME'] . '" class="ov_listall">전체목록</a>';
|
||||
|
||||
$g5['title'] = '게시판관리';
|
||||
require_once './admin.head.php';
|
||||
|
||||
$colspan = 15;
|
||||
?>
|
||||
|
||||
<div class="local_ov01 local_ov">
|
||||
<?php echo $listall ?>
|
||||
<span class="btn_ov01"><span class="ov_txt">생성된 게시판수</span><span class="ov_num"> <?php echo number_format($total_count) ?>개</span></span>
|
||||
</div>
|
||||
|
||||
<form name="fsearch" id="fsearch" class="local_sch01 local_sch" method="get">
|
||||
<label for="sfl" class="sound_only">검색대상</label>
|
||||
<select name="sfl" id="sfl">
|
||||
<option value="bo_table" <?php echo get_selected($sfl, "bo_table", true); ?>>TABLE</option>
|
||||
<option value="bo_subject" <?php echo get_selected($sfl, "bo_subject"); ?>>제목</option>
|
||||
<option value="a.gr_id" <?php echo get_selected($sfl, "a.gr_id"); ?>>그룹ID</option>
|
||||
</select>
|
||||
<label for="stx" class="sound_only">검색어<strong class="sound_only"> 필수</strong></label>
|
||||
<input type="text" name="stx" value="<?php echo $stx ?>" id="stx" required class="required frm_input">
|
||||
<input type="submit" value="검색" class="btn_submit">
|
||||
</form>
|
||||
|
||||
<form name="fboardlist" id="fboardlist" action="./board_list_update.php" onsubmit="return fboardlist_submit(this);" method="post">
|
||||
<input type="hidden" name="sst" value="<?php echo $sst ?>">
|
||||
<input type="hidden" name="sod" value="<?php echo $sod ?>">
|
||||
<input type="hidden" name="sfl" value="<?php echo $sfl ?>">
|
||||
<input type="hidden" name="stx" value="<?php echo $stx ?>">
|
||||
<input type="hidden" name="page" value="<?php echo $page ?>">
|
||||
<input type="hidden" name="token" value="<?php echo isset($token) ? $token : ''; ?>">
|
||||
|
||||
<div class="tbl_head01 tbl_wrap">
|
||||
<table>
|
||||
<caption><?php echo $g5['title']; ?> 목록</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<label for="chkall" class="sound_only">게시판 전체</label>
|
||||
<input type="checkbox" name="chkall" value="1" id="chkall" onclick="check_all(this.form)">
|
||||
</th>
|
||||
<th scope="col"><?php echo subject_sort_link('a.gr_id') ?>그룹</a></th>
|
||||
<th scope="col"><?php echo subject_sort_link('bo_table') ?>TABLE</a></th>
|
||||
<th scope="col"><?php echo subject_sort_link('bo_skin', '', 'desc') ?>스킨</a></th>
|
||||
<th scope="col"><?php echo subject_sort_link('bo_mobile_skin', '', 'desc') ?>모바일<br>스킨</a></th>
|
||||
<th scope="col"><?php echo subject_sort_link('bo_subject') ?>제목</a></th>
|
||||
<th scope="col">읽기P<span class="sound_only">포인트</span></th>
|
||||
<th scope="col">쓰기P<span class="sound_only">포인트</span></th>
|
||||
<th scope="col">댓글P<span class="sound_only">포인트</span></th>
|
||||
<th scope="col">다운P<span class="sound_only">포인트</span></th>
|
||||
<th scope="col"><?php echo subject_sort_link('bo_use_sns') ?>SNS<br>사용</a></th>
|
||||
<th scope="col"><?php echo subject_sort_link('bo_use_search') ?>검색<br>사용</a></th>
|
||||
<th scope="col"><?php echo subject_sort_link('bo_order') ?>출력<br>순서</a></th>
|
||||
<th scope="col">접속기기</th>
|
||||
<th scope="col">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
for ($i = 0; $row = sql_fetch_array($result); $i++) {
|
||||
$one_update = '<a href="./board_form.php?w=u&bo_table=' . $row['bo_table'] . '&' . $qstr . '" class="btn btn_03">수정</a>';
|
||||
$one_copy = '<a href="./board_copy.php?bo_table=' . $row['bo_table'] . '" class="board_copy btn btn_02" target="win_board_copy">복사</a>';
|
||||
|
||||
$bg = 'bg' . ($i % 2);
|
||||
?>
|
||||
|
||||
<tr class="<?php echo $bg; ?>">
|
||||
<td class="td_chk">
|
||||
<label for="chk_<?php echo $i; ?>" class="sound_only"><?php echo get_text($row['bo_subject']) ?></label>
|
||||
<input type="checkbox" name="chk[]" value="<?php echo $i ?>" id="chk_<?php echo $i ?>">
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($is_admin == 'super') { ?>
|
||||
<?php echo get_group_select("gr_id[$i]", $row['gr_id']) ?>
|
||||
<?php } else { ?>
|
||||
<input type="hidden" name="gr_id[<?php echo $i ?>]" value="<?php echo $row['gr_id'] ?>"><?php echo $row['gr_subject'] ?>
|
||||
<?php } ?>
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" name="board_table[<?php echo $i ?>]" value="<?php echo $row['bo_table'] ?>">
|
||||
<a href="<?php echo get_pretty_url($row['bo_table']) ?>"><?php echo $row['bo_table'] ?></a>
|
||||
</td>
|
||||
<td>
|
||||
<label for="bo_skin_<?php echo $i; ?>" class="sound_only">스킨</label>
|
||||
<?php echo get_skin_select('board', 'bo_skin_' . $i, "bo_skin[$i]", $row['bo_skin']); ?>
|
||||
</td>
|
||||
<td>
|
||||
<label for="bo_mobile_skin_<?php echo $i; ?>" class="sound_only">모바일 스킨</label>
|
||||
<?php echo get_mobile_skin_select('board', 'bo_mobile_skin_' . $i, "bo_mobile_skin[$i]", $row['bo_mobile_skin']); ?>
|
||||
</td>
|
||||
<td>
|
||||
<label for="bo_subject_<?php echo $i; ?>" class="sound_only">게시판 제목<strong class="sound_only"> 필수</strong></label>
|
||||
<input type="text" name="bo_subject[<?php echo $i ?>]" value="<?php echo get_text($row['bo_subject']) ?>" id="bo_subject_<?php echo $i ?>" required class="required tbl_input bo_subject full_input" size="10">
|
||||
</td>
|
||||
<td class="td_numsmall">
|
||||
<label for="bo_read_point_<?php echo $i; ?>" class="sound_only">읽기 포인트</label>
|
||||
<input type="text" name="bo_read_point[<?php echo $i ?>]" value="<?php echo $row['bo_read_point'] ?>" id="bo_read_point_<?php echo $i; ?>" class="tbl_input" size="2">
|
||||
</td>
|
||||
<td class="td_numsmall">
|
||||
<label for="bo_write_point_<?php echo $i; ?>" class="sound_only">쓰기 포인트</label>
|
||||
<input type="text" name="bo_write_point[<?php echo $i ?>]" value="<?php echo $row['bo_write_point'] ?>" id="bo_write_point_<?php echo $i; ?>" class="tbl_input" size="2">
|
||||
</td>
|
||||
<td class="td_numsmall">
|
||||
<label for="bo_comment_point_<?php echo $i; ?>" class="sound_only">댓글 포인트</label>
|
||||
<input type="text" name="bo_comment_point[<?php echo $i ?>]" value="<?php echo $row['bo_comment_point'] ?>" id="bo_comment_point_<?php echo $i; ?>" class="tbl_input" size="2">
|
||||
</td>
|
||||
<td class="td_numsmall">
|
||||
<label for="bo_download_point_<?php echo $i; ?>" class="sound_only">다운<br>포인트</label>
|
||||
<input type="text" name="bo_download_point[<?php echo $i ?>]" value="<?php echo $row['bo_download_point'] ?>" id="bo_download_point_<?php echo $i; ?>" class="tbl_input" size="2">
|
||||
</td>
|
||||
<td class="td_numsmall">
|
||||
<label for="bo_use_sns_<?php echo $i; ?>" class="sound_only">SNS<br>사용</label>
|
||||
<input type="checkbox" name="bo_use_sns[<?php echo $i ?>]" value="1" id="bo_use_sns_<?php echo $i ?>" <?php echo $row['bo_use_sns'] ? "checked" : "" ?>>
|
||||
</td>
|
||||
<td class="td_numsmall">
|
||||
<label for="bo_use_search_<?php echo $i; ?>" class="sound_only">검색<br>사용</label>
|
||||
<input type="checkbox" name="bo_use_search[<?php echo $i ?>]" value="1" id="bo_use_search_<?php echo $i ?>" <?php echo $row['bo_use_search'] ? "checked" : "" ?>>
|
||||
</td>
|
||||
<td class="td_numsmall">
|
||||
<label for="bo_order_<?php echo $i; ?>" class="sound_only">출력<br>순서</label>
|
||||
<input type="text" name="bo_order[<?php echo $i ?>]" value="<?php echo $row['bo_order'] ?>" id="bo_order_<?php echo $i ?>" class="tbl_input" size="2">
|
||||
</td>
|
||||
<td class="td_mngsmall">
|
||||
<label for="bo_device_<?php echo $i; ?>" class="sound_only">접속기기</label>
|
||||
<select name="bo_device[<?php echo $i ?>]" id="bo_device_<?php echo $i ?>">
|
||||
<option value="both" <?php echo get_selected($row['bo_device'], 'both', true); ?>>모두</option>
|
||||
<option value="pc" <?php echo get_selected($row['bo_device'], 'pc'); ?>>PC</option>
|
||||
<option value="mobile" <?php echo get_selected($row['bo_device'], 'mobile'); ?>>모바일</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="td_mng td_mng_m">
|
||||
<?php echo $one_update ?>
|
||||
<?php echo $one_copy ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
if ($i == 0) {
|
||||
echo '<tr><td colspan="' . $colspan . '" class="empty_table">자료가 없습니다.</td></tr>';
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="btn_fixed_top">
|
||||
<input type="submit" name="act_button" value="선택수정" onclick="document.pressed=this.value" class="btn_02 btn">
|
||||
<?php if ($is_admin == 'super') { ?>
|
||||
<input type="submit" name="act_button" value="선택삭제" onclick="document.pressed=this.value" class="btn_02 btn">
|
||||
<a href="./board_form.php" id="bo_add" class="btn_01 btn">게시판 추가</a>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<?php echo get_paging(G5_IS_MOBILE ? $config['cf_mobile_pages'] : $config['cf_write_pages'], $page, $total_page, $_SERVER['SCRIPT_NAME'] . '?' . $qstr . '&page='); ?>
|
||||
|
||||
<script>
|
||||
function fboardlist_submit(f) {
|
||||
if (!is_checked("chk[]")) {
|
||||
alert(document.pressed + " 하실 항목을 하나 이상 선택하세요.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.pressed == "선택삭제") {
|
||||
if (!confirm("선택한 자료를 정말 삭제하시겠습니까?")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$(".board_copy").click(function() {
|
||||
window.open(this.href, "win_board_copy", "left=100,top=100,width=550,height=450");
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once './admin.tail.php';
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
$sub_menu = "300100";
|
||||
require_once './_common.php';
|
||||
|
||||
check_demo();
|
||||
|
||||
$post_count_chk = (isset($_POST['chk']) && is_array($_POST['chk'])) ? count($_POST['chk']) : 0;
|
||||
$chk = (isset($_POST['chk']) && is_array($_POST['chk'])) ? $_POST['chk'] : array();
|
||||
$act_button = isset($_POST['act_button']) ? strip_tags($_POST['act_button']) : '';
|
||||
$board_table = (isset($_POST['board_table']) && is_array($_POST['board_table'])) ? $_POST['board_table'] : array();
|
||||
|
||||
if (!$post_count_chk) {
|
||||
alert($act_button . " 하실 항목을 하나 이상 체크하세요.");
|
||||
}
|
||||
|
||||
check_admin_token();
|
||||
|
||||
if ($act_button === "선택수정") {
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
for ($i = 0; $i < $post_count_chk; $i++) {
|
||||
// 실제 번호를 넘김
|
||||
$k = isset($_POST['chk'][$i]) ? (int) $_POST['chk'][$i] : 0;
|
||||
|
||||
$post_gr_id = isset($_POST['gr_id'][$k]) ? clean_xss_tags($_POST['gr_id'][$k], 1, 1) : '';
|
||||
$post_bo_device = isset($_POST['bo_device'][$k]) ? clean_xss_tags($_POST['bo_device'][$k], 1, 1) : '';
|
||||
$post_bo_skin = isset($_POST['bo_skin'][$k]) ? clean_xss_tags($_POST['bo_skin'][$k], 1, 1) : '';
|
||||
$post_bo_mobile_skin = isset($_POST['bo_mobile_skin'][$k]) ? clean_xss_tags($_POST['bo_mobile_skin'][$k], 1, 1) : '';
|
||||
$post_bo_read_point = isset($_POST['bo_read_point'][$k]) ? clean_xss_tags($_POST['bo_read_point'][$k], 1, 1) : '';
|
||||
$post_bo_write_point = isset($_POST['bo_write_point'][$k]) ? clean_xss_tags($_POST['bo_write_point'][$k], 1, 1) : '';
|
||||
$post_bo_comment_point = isset($_POST['bo_comment_point'][$k]) ? clean_xss_tags($_POST['bo_comment_point'][$k], 1, 1) : '';
|
||||
$post_bo_download_point = isset($_POST['bo_download_point'][$k]) ? clean_xss_tags($_POST['bo_download_point'][$k], 1, 1) : '';
|
||||
$post_bo_use_search = isset($_POST['bo_use_search'][$k]) ? clean_xss_tags($_POST['bo_use_search'][$k], 1, 1) : '';
|
||||
$post_bo_use_sns = isset($_POST['bo_use_sns'][$k]) ? clean_xss_tags($_POST['bo_use_sns'][$k], 1, 1) : '';
|
||||
$post_bo_order = isset($_POST['bo_order'][$k]) ? clean_xss_tags($_POST['bo_order'][$k], 1, 1) : '';
|
||||
$post_board_table = isset($_POST['board_table'][$k]) ? clean_xss_tags($_POST['board_table'][$k], 1, 1) : '';
|
||||
|
||||
if ($is_admin != 'super') {
|
||||
$sql = " select count(*) as cnt from {$g5['board_table']} a, {$g5['group_table']} b
|
||||
where a.gr_id = '" . sql_real_escape_string($post_gr_id) . "'
|
||||
and a.gr_id = b.gr_id
|
||||
and b.gr_admin = '{$member['mb_id']}' ";
|
||||
$row = sql_fetch($sql);
|
||||
if (!$row['cnt']) {
|
||||
alert('최고관리자가 아닌 경우 다른 관리자의 게시판(' . $board_table[$k] . ')은 수정이 불가합니다.');
|
||||
}
|
||||
}
|
||||
|
||||
$p_bo_subject = is_array($_POST['bo_subject']) ? strip_tags(clean_xss_attributes($_POST['bo_subject'][$k])) : '';
|
||||
|
||||
$sql = " update {$g5['board_table']}
|
||||
set gr_id = '" . sql_real_escape_string($post_gr_id) . "',
|
||||
bo_subject = '" . $p_bo_subject . "',
|
||||
bo_device = '" . sql_real_escape_string($post_bo_device) . "',
|
||||
bo_skin = '" . sql_real_escape_string($post_bo_skin) . "',
|
||||
bo_mobile_skin = '" . sql_real_escape_string($post_bo_mobile_skin) . "',
|
||||
bo_read_point = '" . sql_real_escape_string($post_bo_read_point) . "',
|
||||
bo_write_point = '" . sql_real_escape_string($post_bo_write_point) . "',
|
||||
bo_comment_point = '" . sql_real_escape_string($post_bo_comment_point) . "',
|
||||
bo_download_point = '" . sql_real_escape_string($post_bo_download_point) . "',
|
||||
bo_use_search = '" . sql_real_escape_string($post_bo_use_search) . "',
|
||||
bo_use_sns = '" . sql_real_escape_string($post_bo_use_sns) . "',
|
||||
bo_order = '" . sql_real_escape_string($post_bo_order) . "'
|
||||
where bo_table = '" . sql_real_escape_string($post_board_table) . "' ";
|
||||
|
||||
sql_query($sql);
|
||||
}
|
||||
} elseif ($act_button === "선택삭제") {
|
||||
if ($is_admin != 'super') {
|
||||
alert('게시판 삭제는 최고관리자만 가능합니다.');
|
||||
}
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'd');
|
||||
|
||||
// _BOARD_DELETE_ 상수를 선언해야 board_delete.inc.php 가 정상 작동함
|
||||
/* 확인필요 22.05.27
|
||||
A file should declare new symbols (classes, functions, constants, etc.) and cause no other side effects,
|
||||
or it should execute logic with side effects, but should not do both.*/
|
||||
define('_BOARD_DELETE_', true);
|
||||
|
||||
for ($i = 0; $i < $post_count_chk; $i++) {
|
||||
// 실제 번호를 넘김
|
||||
$k = isset($_POST['chk'][$i]) ? (int) $_POST['chk'][$i] : 0;
|
||||
|
||||
// include 전에 $bo_table 값을 반드시 넘겨야 함
|
||||
$tmp_bo_table = isset($_POST['board_table'][$k]) ? trim(clean_xss_tags($_POST['board_table'][$k], 1, 1)) : '';
|
||||
|
||||
if (preg_match("/^[A-Za-z0-9_]+$/", $tmp_bo_table)) {
|
||||
include './board_delete.inc.php';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run_event('admin_board_list_update', $act_button, $chk, $board_table, $qstr);
|
||||
|
||||
goto_url('./board_list.php?' . $qstr);
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
$sub_menu = '300100';
|
||||
require_once './_common.php';
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
if (!$board['bo_table']) {
|
||||
alert('존재하지 않는 게시판입니다.');
|
||||
}
|
||||
|
||||
$g5['title'] = $board['bo_subject'] . ' 게시판 썸네일 삭제';
|
||||
require_once './admin.head.php';
|
||||
?>
|
||||
|
||||
<div class="local_desc02 local_desc">
|
||||
<p>
|
||||
완료 메세지가 나오기 전에 프로그램의 실행을 중지하지 마십시오.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$dir = G5_DATA_PATH . '/file/' . $bo_table;
|
||||
|
||||
$cnt = 0;
|
||||
if (is_dir($dir)) {
|
||||
echo '<ul>';
|
||||
$files = glob($dir . '/thumb-*');
|
||||
if (is_array($files)) {
|
||||
foreach ($files as $thumbnail) {
|
||||
$cnt++;
|
||||
@unlink($thumbnail);
|
||||
|
||||
echo '<li>' . $thumbnail . '</li>' . PHP_EOL;
|
||||
|
||||
flush();
|
||||
|
||||
if (($cnt % 10) == 0) {
|
||||
echo PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo '<li>완료됨</li></ul>' . PHP_EOL;
|
||||
echo '<div class="local_desc01 local_desc"><p><strong>썸네일 ' . $cnt . '건의 삭제 완료됐습니다.</strong></p></div>' . PHP_EOL;
|
||||
} else {
|
||||
echo '<p>첨부파일 디렉토리가 존재하지 않습니다.</p>';
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="btn_confirm01 btn_confirm"><a href="./board_form.php?w=u&bo_table=<?php echo $bo_table; ?>&<?php echo $qstr; ?>">게시판 수정으로 돌아가기</a></div>
|
||||
|
||||
<?php
|
||||
require_once './admin.tail.php';
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
$sub_menu = "300200";
|
||||
require_once './_common.php';
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
if ($is_admin != 'super' && $w == '') {
|
||||
alert('최고관리자만 접근 가능합니다.');
|
||||
}
|
||||
|
||||
$html_title = '게시판그룹';
|
||||
$gr_id_attr = '';
|
||||
$sound_only = '';
|
||||
|
||||
if (!isset($group['gr_id'])) {
|
||||
$group['gr_id'] = '';
|
||||
$group['gr_subject'] = '';
|
||||
$group['gr_device'] = '';
|
||||
}
|
||||
|
||||
$gr = array('gr_use_access' => 0, 'gr_admin' => '');
|
||||
if ($w == '') {
|
||||
$gr_id_attr = 'required';
|
||||
$sound_only = '<strong class="sound_only"> 필수</strong>';
|
||||
$html_title .= ' 생성';
|
||||
} elseif ($w == 'u') {
|
||||
$gr_id_attr = 'readonly';
|
||||
$gr = sql_fetch(" select * from {$g5['group_table']} where gr_id = '$gr_id' ");
|
||||
$html_title .= ' 수정';
|
||||
} else {
|
||||
alert('제대로 된 값이 넘어오지 않았습니다.');
|
||||
}
|
||||
|
||||
if (!isset($group['gr_device'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['group_table']}` ADD `gr_device` ENUM('both','pc','mobile') NOT NULL DEFAULT 'both' AFTER `gr_subject` ", false);
|
||||
}
|
||||
|
||||
// 접근회원수
|
||||
$sql1 = " select count(*) as cnt from {$g5['group_member_table']} where gr_id = '{$gr_id}' ";
|
||||
$row1 = sql_fetch($sql1);
|
||||
$group_member_count = $row1['cnt'];
|
||||
|
||||
$g5['title'] = $html_title;
|
||||
require_once './admin.head.php';
|
||||
?>
|
||||
|
||||
<form name="fboardgroup" id="fboardgroup" action="./boardgroup_form_update.php" onsubmit="return fboardgroup_check(this);" method="post" autocomplete="off">
|
||||
<input type="hidden" name="w" value="<?php echo $w ?>">
|
||||
<input type="hidden" name="sfl" value="<?php echo $sfl ?>">
|
||||
<input type="hidden" name="stx" value="<?php echo $stx ?>">
|
||||
<input type="hidden" name="sst" value="<?php echo $sst ?>">
|
||||
<input type="hidden" name="sod" value="<?php echo $sod ?>">
|
||||
<input type="hidden" name="page" value="<?php echo $page ?>">
|
||||
<input type="hidden" name="token" value="">
|
||||
|
||||
<div class="tbl_frm01 tbl_wrap">
|
||||
<table>
|
||||
<caption><?php echo $g5['title']; ?></caption>
|
||||
<colgroup>
|
||||
<col class="grid_4">
|
||||
<col>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row"><label for="gr_id">그룹 ID<?php echo $sound_only ?></label></th>
|
||||
<td><input type="text" name="gr_id" value="<?php echo $group['gr_id'] ?>" id="gr_id" <?php echo $gr_id_attr; ?> class="<?php echo $gr_id_attr; ?> alnum_ frm_input" maxlength="10">
|
||||
<?php
|
||||
if ($w == '') {
|
||||
echo '영문자, 숫자, _ 만 가능 (공백없이)';
|
||||
} else {
|
||||
echo '<a href="' . G5_BBS_URL . '/group.php?gr_id=' . $group['gr_id'] . '" class="btn_frmline">게시판그룹 바로가기</a>';
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="gr_subject">그룹 제목<strong class="sound_only"> 필수</strong></label></th>
|
||||
<td>
|
||||
<input type="text" name="gr_subject" value="<?php echo get_text($group['gr_subject']) ?>" id="gr_subject" required class="required frm_input" size="80">
|
||||
<?php
|
||||
if ($w == 'u') {
|
||||
echo '<a href="./board_form.php?gr_id=' . $gr_id . '" class="btn_frmline">게시판생성</a>';
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="gr_device">접속기기</label></th>
|
||||
<td>
|
||||
<?php echo help("PC 와 모바일 사용을 구분합니다.") ?>
|
||||
<select id="gr_device" name="gr_device">
|
||||
<option value="both" <?php echo get_selected($group['gr_device'], 'both', true); ?>>PC와 모바일에서 모두 사용</option>
|
||||
<option value="pc" <?php echo get_selected($group['gr_device'], 'pc'); ?>>PC 전용</option>
|
||||
<option value="mobile" <?php echo get_selected($group['gr_device'], 'mobile'); ?>>모바일 전용</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php
|
||||
if ($is_admin == 'super') {
|
||||
echo '<label for="gr_admin">그룹 관리자</label>';
|
||||
} else {
|
||||
echo '그룹 관리자';
|
||||
}
|
||||
?>
|
||||
</th>
|
||||
<td>
|
||||
<?php
|
||||
if ($is_admin == 'super') {
|
||||
echo '<input type="text" id="gr_admin" name="gr_admin" class="frm_input" value="' . $gr['gr_admin'] . '" maxlength="20">';
|
||||
} else {
|
||||
echo '<input type="hidden" id="gr_admin" name="gr_admin" value="' . $gr['gr_admin'] . '">' . $gr['gr_admin'];
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="gr_use_access">접근회원사용</label></th>
|
||||
<td>
|
||||
<?php echo help("사용에 체크하시면 이 그룹에 속한 게시판은 접근가능한 회원만 접근이 가능합니다.") ?>
|
||||
<input type="checkbox" name="gr_use_access" value="1" id="gr_use_access" <?php echo $gr['gr_use_access'] ? 'checked' : ''; ?>>
|
||||
사용
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">접근회원수</th>
|
||||
<td>
|
||||
<?php
|
||||
echo '<a href="./boardgroupmember_list.php?gr_id=' . $gr_id . '">' . $group_member_count . '</a>';
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php for ($i = 1; $i <= 10; $i++) { ?>
|
||||
<tr>
|
||||
<th scope="row">여분필드<?php echo $i ?></th>
|
||||
<td class="td_extra">
|
||||
<label for="gr_<?php echo $i ?>_subj">여분필드 <?php echo $i ?> 제목</label>
|
||||
<input type="text" name="gr_<?php echo $i ?>_subj" value="<?php echo isset($group['gr_' . $i . '_subj']) ? get_text($group['gr_' . $i . '_subj']) : ''; ?>" id="gr_<?php echo $i ?>_subj" class="frm_input">
|
||||
<label for="gr_<?php echo $i ?>">여분필드 <?php echo $i ?> 내용</label>
|
||||
<input type="text" name="gr_<?php echo $i ?>" value="<?php echo isset($gr['gr_' . $i]) ? get_sanitize_input($gr['gr_' . $i]) : ''; ?>" id="gr_<?php echo $i ?>" class="frm_input">
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="btn_fixed_top">
|
||||
<a href="./boardgroup_list.php?<?php echo $qstr ?>" class="btn btn_02">목록</a>
|
||||
<input type="submit" class="btn_submit btn" accesskey="s" value="확인">
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<div class="local_desc01 local_desc">
|
||||
<p>
|
||||
게시판을 생성하시려면 1개 이상의 게시판그룹이 필요합니다.<br>
|
||||
게시판그룹을 이용하시면 더 효과적으로 게시판을 관리할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function fboardgroup_check(f) {
|
||||
f.action = './boardgroup_form_update.php';
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once './admin.tail.php';
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
$sub_menu = "300200";
|
||||
require_once './_common.php';
|
||||
|
||||
if ($w == 'u') {
|
||||
check_demo();
|
||||
}
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
if ($is_admin != 'super' && $w == '') {
|
||||
alert('최고관리자만 접근 가능합니다.');
|
||||
}
|
||||
|
||||
check_admin_token();
|
||||
|
||||
$gr_id = isset($_POST['gr_id']) ? $_POST['gr_id'] : '';
|
||||
|
||||
if (!preg_match("/^([A-Za-z0-9_]{1,10})$/", $gr_id)) {
|
||||
alert('그룹 ID는 공백없이 영문자, 숫자, _ 만 사용 가능합니다. (10자 이내)');
|
||||
}
|
||||
|
||||
if (empty($gr_subject)) {
|
||||
alert('그룹 제목을 입력하세요.');
|
||||
}
|
||||
|
||||
$posts = array();
|
||||
|
||||
$check_keys = array(
|
||||
'gr_subject' => '',
|
||||
'gr_device' => '',
|
||||
'gr_admin' => '',
|
||||
);
|
||||
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$check_keys['gr_' . $i . '_subj'] = isset($_POST['gr_' . $i . '_subj']) ? $_POST['gr_' . $i . '_subj'] : '';
|
||||
$check_keys['gr_' . $i] = isset($_POST['gr_' . $i]) ? $_POST['gr_' . $i] : '';
|
||||
}
|
||||
|
||||
foreach ($check_keys as $key => $value) {
|
||||
if ($key === 'gr_subject') {
|
||||
$posts[$key] = isset($_POST[$key]) ? strip_tags(clean_xss_attributes($_POST[$key])) : '';
|
||||
} else {
|
||||
$posts[$key] = isset($_POST[$key]) ? $_POST[$key] : '';
|
||||
}
|
||||
}
|
||||
|
||||
$sql_common = " gr_subject = '{$posts['gr_subject']}',
|
||||
gr_device = '{$posts['gr_device']}',
|
||||
gr_admin = '{$posts['gr_admin']}',
|
||||
gr_1_subj = '{$posts['gr_1_subj']}',
|
||||
gr_2_subj = '{$posts['gr_2_subj']}',
|
||||
gr_3_subj = '{$posts['gr_3_subj']}',
|
||||
gr_4_subj = '{$posts['gr_4_subj']}',
|
||||
gr_5_subj = '{$posts['gr_5_subj']}',
|
||||
gr_6_subj = '{$posts['gr_6_subj']}',
|
||||
gr_7_subj = '{$posts['gr_7_subj']}',
|
||||
gr_8_subj = '{$posts['gr_8_subj']}',
|
||||
gr_9_subj = '{$posts['gr_9_subj']}',
|
||||
gr_10_subj = '{$posts['gr_10_subj']}',
|
||||
gr_1 = '{$posts['gr_1']}',
|
||||
gr_2 = '{$posts['gr_2']}',
|
||||
gr_3 = '{$posts['gr_3']}',
|
||||
gr_4 = '{$posts['gr_4']}',
|
||||
gr_5 = '{$posts['gr_5']}',
|
||||
gr_6 = '{$posts['gr_6']}',
|
||||
gr_7 = '{$posts['gr_7']}',
|
||||
gr_8 = '{$posts['gr_8']}',
|
||||
gr_9 = '{$posts['gr_9']}',
|
||||
gr_10 = '{$posts['gr_10']}' ";
|
||||
if (isset($_POST['gr_use_access'])) {
|
||||
$sql_common .= ", gr_use_access = '{$_POST['gr_use_access']}' ";
|
||||
} else {
|
||||
$sql_common .= ", gr_use_access = '' ";
|
||||
}
|
||||
|
||||
if ($w == '') {
|
||||
$sql = " select count(*) as cnt from {$g5['group_table']} where gr_id = '{$gr_id}' ";
|
||||
$row = sql_fetch($sql);
|
||||
if ($row['cnt']) {
|
||||
alert('이미 존재하는 그룹 ID 입니다.');
|
||||
}
|
||||
|
||||
$sql = " insert into {$g5['group_table']}
|
||||
set gr_id = '{$gr_id}',
|
||||
{$sql_common} ";
|
||||
sql_query($sql);
|
||||
} elseif ($w == "u") {
|
||||
$sql = " update {$g5['group_table']}
|
||||
set {$sql_common}
|
||||
where gr_id = '{$gr_id}' ";
|
||||
sql_query($sql);
|
||||
} else {
|
||||
alert('제대로 된 값이 넘어오지 않았습니다.');
|
||||
}
|
||||
|
||||
run_event('admin_boardgroup_form_update', $gr_id, $w);
|
||||
|
||||
goto_url('./boardgroup_form.php?w=u&gr_id=' . $gr_id . '&' . $qstr);
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
$sub_menu = "300200";
|
||||
require_once './_common.php';
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'r');
|
||||
|
||||
if (!isset($group['gr_device'])) {
|
||||
// 게시판 그룹 사용 필드 추가
|
||||
// both : pc, mobile 둘다 사용
|
||||
// pc : pc 전용 사용
|
||||
// mobile : mobile 전용 사용
|
||||
// none : 사용 안함
|
||||
sql_query(" ALTER TABLE `{$g5['group_table']}` ADD `gr_device` ENUM( 'both', 'pc', 'mobile' ) NOT NULL DEFAULT 'both' AFTER `gr_subject` ", false);
|
||||
}
|
||||
|
||||
$sql_common = " from {$g5['group_table']} ";
|
||||
|
||||
$sql_search = " where (1) ";
|
||||
if ($is_admin != 'super') {
|
||||
$sql_search .= " and (gr_admin = '{$member['mb_id']}') ";
|
||||
}
|
||||
|
||||
if ($stx) {
|
||||
$sql_search .= " and ( ";
|
||||
switch ($sfl) {
|
||||
case "gr_id":
|
||||
case "gr_admin":
|
||||
$sql_search .= " ({$sfl} = '{$stx}') ";
|
||||
break;
|
||||
default:
|
||||
$sql_search .= " ({$sfl} like '%{$stx}%') ";
|
||||
break;
|
||||
}
|
||||
$sql_search .= " ) ";
|
||||
}
|
||||
|
||||
if ($sst) {
|
||||
$sql_order = " order by {$sst} {$sod} ";
|
||||
} else {
|
||||
$sql_order = " order by gr_id asc ";
|
||||
}
|
||||
|
||||
$sql = " select count(*) as cnt {$sql_common} {$sql_search} {$sql_order} ";
|
||||
$row = sql_fetch($sql);
|
||||
$total_count = $row['cnt'];
|
||||
|
||||
$rows = $config['cf_page_rows'];
|
||||
$total_page = ceil($total_count / $rows); // 전체 페이지 계산
|
||||
if ($page < 1) {
|
||||
$page = 1; // 페이지가 없으면 첫 페이지 (1 페이지)
|
||||
}
|
||||
$from_record = ($page - 1) * $rows; // 시작 열을 구함
|
||||
|
||||
$sql = " select * {$sql_common} {$sql_search} {$sql_order} limit {$from_record}, {$rows} ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
$listall = '<a href="' . $_SERVER['SCRIPT_NAME'] . '" class="ov_listall">처음</a>';
|
||||
|
||||
$g5['title'] = '게시판그룹설정';
|
||||
require_once './admin.head.php';
|
||||
|
||||
$colspan = 10;
|
||||
?>
|
||||
|
||||
<div class="local_ov01 local_ov">
|
||||
<?php echo $listall ?>
|
||||
<span class="btn_ov01"><span class="ov_txt">전체그룹</span><span class="ov_num"> <?php echo number_format($total_count) ?>개</span></span>
|
||||
</div>
|
||||
|
||||
<form name="fsearch" id="fsearch" class="local_sch01 local_sch" method="get">
|
||||
<label for="sfl" class="sound_only">검색대상</label>
|
||||
<select name="sfl" id="sfl">
|
||||
<option value="gr_subject" <?php echo get_selected($sfl, "gr_subject"); ?>>제목</option>
|
||||
<option value="gr_id" <?php echo get_selected($sfl, "gr_id"); ?>>ID</option>
|
||||
<option value="gr_admin" <?php echo get_selected($sfl, "gr_admin"); ?>>그룹관리자</option>
|
||||
</select>
|
||||
<label for="stx" class="sound_only">검색어<strong class="sound_only"> 필수</strong></label>
|
||||
<input type="text" name="stx" id="stx" value="<?php echo $stx ?>" required class="required frm_input">
|
||||
<input type="submit" value="검색" class="btn_submit">
|
||||
</form>
|
||||
|
||||
|
||||
<form name="fboardgrouplist" id="fboardgrouplist" action="./boardgroup_list_update.php" onsubmit="return fboardgrouplist_submit(this);" method="post">
|
||||
<input type="hidden" name="sst" value="<?php echo $sst ?>">
|
||||
<input type="hidden" name="sod" value="<?php echo $sod ?>">
|
||||
<input type="hidden" name="sfl" value="<?php echo $sfl ?>">
|
||||
<input type="hidden" name="stx" value="<?php echo $stx ?>">
|
||||
<input type="hidden" name="page" value="<?php echo $page ?>">
|
||||
<input type="hidden" name="token" value="">
|
||||
|
||||
<div class="tbl_head01 tbl_wrap">
|
||||
<table>
|
||||
<caption><?php echo $g5['title']; ?> 목록</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<label for="chkall" class="sound_only">그룹 전체</label>
|
||||
<input type="checkbox" name="chkall" value="1" id="chkall" onclick="check_all(this.form)">
|
||||
</th>
|
||||
<th scope="col"><?php echo subject_sort_link('gr_id') ?>그룹아이디</a></th>
|
||||
<th scope="col"><?php echo subject_sort_link('gr_subject') ?>제목</a></th>
|
||||
<th scope="col"><?php echo subject_sort_link('gr_admin') ?>그룹관리자</a></th>
|
||||
<th scope="col">게시판</th>
|
||||
<th scope="col">접근<br>사용</th>
|
||||
<th scope="col">접근<br>회원수</th>
|
||||
<th scope="col"><?php echo subject_sort_link('gr_order') ?>출력<br>순서</a></th>
|
||||
<th scope="col">접속기기</th>
|
||||
<th scope="col">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
for ($i = 0; $row = sql_fetch_array($result); $i++) {
|
||||
// 접근회원수
|
||||
$sql1 = " select count(*) as cnt from {$g5['group_member_table']} where gr_id = '{$row['gr_id']}' ";
|
||||
$row1 = sql_fetch($sql1);
|
||||
|
||||
// 게시판수
|
||||
$sql2 = " select count(*) as cnt from {$g5['board_table']} where gr_id = '{$row['gr_id']}' ";
|
||||
$row2 = sql_fetch($sql2);
|
||||
|
||||
$s_upd = '<a href="./boardgroup_form.php?' . $qstr . '&w=u&gr_id=' . $row['gr_id'] . '" class="btn_03 btn">수정</a>';
|
||||
|
||||
$bg = 'bg' . ($i % 2);
|
||||
?>
|
||||
|
||||
<tr class="<?php echo $bg; ?>">
|
||||
<td class="td_chk">
|
||||
<input type="hidden" name="group_id[<?php echo $i ?>]" value="<?php echo $row['gr_id'] ?>">
|
||||
<label for="chk_<?php echo $i; ?>" class="sound_only"><?php echo get_text($row['gr_subject']); ?> 그룹</label>
|
||||
<input type="checkbox" name="chk[]" value="<?php echo $i ?>" id="chk_<?php echo $i ?>">
|
||||
</td>
|
||||
<td class="td_left"><a href="<?php echo G5_BBS_URL ?>/group.php?gr_id=<?php echo $row['gr_id'] ?>"><?php echo $row['gr_id'] ?></a></td>
|
||||
<td class="td_input">
|
||||
<label for="gr_subject_<?php echo $i; ?>" class="sound_only">그룹제목</label>
|
||||
<input type="text" name="gr_subject[<?php echo $i ?>]" value="<?php echo get_text($row['gr_subject']) ?>" id="gr_subject_<?php echo $i ?>" class="tbl_input">
|
||||
</td>
|
||||
<td class="td_mng td_input">
|
||||
<?php if ($is_admin == 'super') { ?>
|
||||
<label for="gr_admin_<?php echo $i; ?>" class="sound_only">그룹관리자</label>
|
||||
<input type="text" name="gr_admin[<?php echo $i ?>]" value="<?php echo get_sanitize_input($row['gr_admin']); ?>" id="gr_admin_<?php echo $i ?>" class="tbl_input" size="10" maxlength="20">
|
||||
<?php } else { ?>
|
||||
<input type="hidden" name="gr_admin[<?php echo $i ?>]" value="<?php echo get_sanitize_input($row['gr_admin']); ?>"><?php echo get_text($row['gr_admin']); ?>
|
||||
<?php } ?>
|
||||
</td>
|
||||
<td class="td_num"><a href="./board_list.php?sfl=a.gr_id&stx=<?php echo $row['gr_id'] ?>"><?php echo $row2['cnt'] ?></a></td>
|
||||
<td class="td_numsmall">
|
||||
<label for="gr_use_access_<?php echo $i; ?>" class="sound_only">접근회원 사용</label>
|
||||
<input type="checkbox" name="gr_use_access[<?php echo $i ?>]" <?php echo $row['gr_use_access'] ? 'checked' : '' ?> value="1" id="gr_use_access_<?php echo $i ?>">
|
||||
</td>
|
||||
<td class="td_num"><a href="./boardgroupmember_list.php?gr_id=<?php echo $row['gr_id'] ?>"><?php echo $row1['cnt'] ?></a></td>
|
||||
<td class="td_numsmall">
|
||||
<label for="gr_order_<?php echo $i; ?>" class="sound_only">메인메뉴 출력순서</label>
|
||||
<input type="text" name="gr_order[<?php echo $i ?>]" value="<?php echo $row['gr_order'] ?>" id="gr_order_<?php echo $i ?>" class="tbl_input" size="2">
|
||||
</td>
|
||||
<td class="td_mng">
|
||||
<label for="gr_device_<?php echo $i; ?>" class="sound_only">접속기기</label>
|
||||
<select name="gr_device[<?php echo $i ?>]" id="gr_device_<?php echo $i ?>">
|
||||
<option value="both" <?php echo get_selected($row['gr_device'], 'both'); ?>>모두</option>
|
||||
<option value="pc" <?php echo get_selected($row['gr_device'], 'pc'); ?>>PC</option>
|
||||
<option value="mobile" <?php echo get_selected($row['gr_device'], 'mobile'); ?>>모바일</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="td_mng td_mng_s"><?php echo $s_upd ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
if ($i == 0) {
|
||||
echo '<tr><td colspan="' . $colspan . '" class="empty_table">자료가 없습니다.</td></tr>';
|
||||
}
|
||||
?>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="btn_fixed_top">
|
||||
<input type="submit" name="act_button" onclick="document.pressed=this.value" value="선택수정" class="btn btn_02">
|
||||
<input type="submit" name="act_button" onclick="document.pressed=this.value" value="선택삭제" class="btn btn_02">
|
||||
<a href="./boardgroup_form.php" class="btn btn_01">게시판그룹 추가</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="local_desc01 local_desc">
|
||||
<p>
|
||||
접근사용 옵션을 설정하시면 관리자가 지정한 회원만 해당 그룹에 접근할 수 있습니다.<br>
|
||||
접근사용 옵션은 해당 그룹에 속한 모든 게시판에 적용됩니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$pagelist = get_paging(G5_IS_MOBILE ? $config['cf_mobile_pages'] : $config['cf_write_pages'], $page, $total_page, $_SERVER['SCRIPT_NAME'] . '?' . $qstr . '&page=');
|
||||
echo $pagelist;
|
||||
?>
|
||||
|
||||
<script>
|
||||
function fboardgrouplist_submit(f) {
|
||||
if (!is_checked("chk[]")) {
|
||||
alert(document.pressed + " 하실 항목을 하나 이상 선택하세요.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.pressed == "선택삭제") {
|
||||
if (!confirm("선택한 자료를 정말 삭제하시겠습니까?")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once './admin.tail.php';
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
$sub_menu = "300200";
|
||||
require_once './_common.php';
|
||||
|
||||
check_demo();
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
check_admin_token();
|
||||
|
||||
$post_chk = isset($_POST['chk']) ? (array) $_POST['chk'] : array();
|
||||
$post_group_id = isset($_POST['group_id']) ? (array) $_POST['group_id'] : array();
|
||||
$act_button = isset($_POST['act_button']) ? $_POST['act_button'] : '';
|
||||
|
||||
$chk_count = count($post_chk);
|
||||
|
||||
if (!$chk_count) {
|
||||
alert($act_button . '할 게시판그룹을 1개이상 선택해 주세요.');
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $chk_count; $i++) {
|
||||
$k = isset($post_chk[$i]) ? (int) $post_chk[$i] : 0;
|
||||
$gr_id = preg_replace('/[^a-z0-9_]/i', '', $post_group_id[$k]);
|
||||
$gr_subject = isset($_POST['gr_subject'][$k]) ? strip_tags(clean_xss_attributes($_POST['gr_subject'][$k])) : '';
|
||||
$gr_admin = isset($_POST['gr_admin'][$k]) ? strip_tags(clean_xss_attributes($_POST['gr_admin'][$k])) : '';
|
||||
$gr_device = isset($_POST['gr_device'][$k]) ? clean_xss_tags($_POST['gr_device'][$k], 1, 1, 10) : '';
|
||||
$gr_use_access = isset($_POST['gr_use_access'][$k]) ? (int) $_POST['gr_use_access'][$k] : 0;
|
||||
$gr_order = isset($_POST['gr_order'][$k]) ? (int) $_POST['gr_order'][$k] : 0;
|
||||
|
||||
if ($act_button == '선택수정') {
|
||||
$sql = " update {$g5['group_table']}
|
||||
set gr_subject = '{$gr_subject}',
|
||||
gr_device = '" . sql_real_escape_string($gr_device) . "',
|
||||
gr_admin = '" . sql_real_escape_string($gr_admin) . "',
|
||||
gr_use_access = '" . $gr_use_access . "',
|
||||
gr_order = '" . $gr_order . "'
|
||||
where gr_id = '{$gr_id}' ";
|
||||
if ($is_admin != 'super') {
|
||||
$sql .= " and gr_admin = '{$gr_admin}' ";
|
||||
}
|
||||
sql_query($sql);
|
||||
} elseif ($act_button == '선택삭제') {
|
||||
$row = sql_fetch(" select count(*) as cnt from {$g5['board_table']} where gr_id = '$gr_id' ");
|
||||
if ($row['cnt']) {
|
||||
alert("이 그룹에 속한 게시판이 존재하여 게시판 그룹을 삭제할 수 없습니다.\\n\\n이 그룹에 속한 게시판을 먼저 삭제하여 주십시오.", './board_list.php?sfl=gr_id&stx=' . $gr_id);
|
||||
}
|
||||
|
||||
// 그룹 삭제
|
||||
sql_query(" delete from {$g5['group_table']} where gr_id = '$gr_id' ");
|
||||
|
||||
// 그룹접근 회원 삭제
|
||||
sql_query(" delete from {$g5['group_member_table']} where gr_id = '$gr_id' ");
|
||||
}
|
||||
}
|
||||
|
||||
run_event('admin_boardgroup_list_update', $act_button, $post_chk, $post_group_id, $qstr);
|
||||
|
||||
goto_url('./boardgroup_list.php?' . $qstr);
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
$sub_menu = "300200";
|
||||
require_once './_common.php';
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
$mb = get_member($mb_id);
|
||||
$token = isset($token) ? $token : '';
|
||||
|
||||
if (!(isset($mb['mb_id']) && $mb['mb_id'])) {
|
||||
alert('존재하지 않는 회원입니다.');
|
||||
}
|
||||
|
||||
$g5['title'] = '접근가능그룹';
|
||||
require_once './admin.head.php';
|
||||
|
||||
$colspan = 4;
|
||||
?>
|
||||
|
||||
<form name="fboardgroupmember_form" id="fboardgroupmember_form" action="./boardgroupmember_update.php" onsubmit="return boardgroupmember_form_check(this)" method="post">
|
||||
<input type="hidden" name="mb_id" value="<?php echo $mb['mb_id'] ?>" id="mb_id">
|
||||
<input type="hidden" name="token" value="" id="token">
|
||||
|
||||
<div class="local_ov01 local_ov">
|
||||
<span class="btn_ov01"><span class="ov_txt"> 아이디</span><span class="ov_num"><?php echo $mb['mb_id'] ?></span></span>
|
||||
<span class="btn_ov01"><span class="ov_txt"> 이름</span><span class="ov_num"><?php echo get_text($mb['mb_name']); ?></span></span>
|
||||
<span class="btn_ov01"><span class="ov_txt"> 닉네임</span><span class="ov_num"><?php echo $mb['mb_nick'] ?></span></span>
|
||||
</div>
|
||||
|
||||
<div class="local_cmd01 local_cmd">
|
||||
<label for="gr_id">그룹지정</label>
|
||||
<select name="gr_id" id="gr_id">
|
||||
<option value="">접근가능 그룹을 선택하세요.</option>
|
||||
<?php
|
||||
$sql = " select *
|
||||
from {$g5['group_table']}
|
||||
where gr_use_access = 1 ";
|
||||
if ($is_admin != 'super') {
|
||||
$sql .= " and gr_admin = '{$member['mb_id']}' ";
|
||||
}
|
||||
$sql .= " order by gr_id ";
|
||||
$result = sql_query($sql);
|
||||
for ($i = 0; $row = sql_fetch_array($result); $i++) {
|
||||
echo "<option value=\"" . $row['gr_id'] . "\">" . $row['gr_subject'] . "</option>";
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<input type="submit" value="선택" class="btn_submit btn" accesskey="s">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form name="fboardgroupmember" id="fboardgroupmember" action="./boardgroupmember_update.php" onsubmit="return fboardgroupmember_submit(this);" method="post">
|
||||
<input type="hidden" name="sst" value="<?php echo $sst ?>" id="sst">
|
||||
<input type="hidden" name="sod" value="<?php echo $sod ?>" id="sod">
|
||||
<input type="hidden" name="sfl" value="<?php echo $sfl ?>" id="sfl">
|
||||
<input type="hidden" name="stx" value="<?php echo $stx ?>" id="stx">
|
||||
<input type="hidden" name="page" value="<?php echo $page ?>" id="page">
|
||||
<input type="hidden" name="token" value="<?php echo get_sanitize_input($token); ?>" id="token">
|
||||
<input type="hidden" name="mb_id" value="<?php echo $mb['mb_id'] ?>" id="mb_id">
|
||||
<input type="hidden" name="w" value="d" id="w">
|
||||
|
||||
<div class="tbl_head01 tbl_wrap">
|
||||
<table>
|
||||
<caption><?php echo $g5['title']; ?> 목록</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<label for="chkall" class="sound_only">접근가능그룹 전체</label>
|
||||
<input type="checkbox" name="chkall" value="1" id="chkall" onclick="check_all(this.form)">
|
||||
</th>
|
||||
<th scope="col">그룹아이디</th>
|
||||
<th scope="col">그룹</th>
|
||||
<th scope="col">처리일시</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$sql = " select * from {$g5['group_member_table']} a, {$g5['group_table']} b
|
||||
where a.mb_id = '{$mb['mb_id']}'
|
||||
and a.gr_id = b.gr_id ";
|
||||
if ($is_admin != 'super') {
|
||||
$sql .= " and b.gr_admin = '{$member['mb_id']}' ";
|
||||
}
|
||||
$sql .= " order by a.gr_id desc ";
|
||||
$result = sql_query($sql);
|
||||
for ($i = 0; $row = sql_fetch_array($result); $i++) {
|
||||
?>
|
||||
<tr>
|
||||
<td class="td_chk">
|
||||
<label for="chk_<?php echo $i; ?>" class="sound_only"><?php echo $row['gr_subject'] ?> 그룹</label>
|
||||
<input type="checkbox" name="chk[]" value="<?php echo $row['gm_id'] ?>" id="chk_<?php echo $i ?>">
|
||||
</td>
|
||||
<td class="td_grid"><a href="<?php echo G5_BBS_URL; ?>/group.php?gr_id=<?php echo $row['gr_id'] ?>"><?php echo $row['gr_id'] ?></a></td>
|
||||
<td class="td_category"><?php echo $row['gr_subject'] ?></td>
|
||||
<td class="td_datetime"><?php echo $row['gm_datetime'] ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
|
||||
if ($i == 0) {
|
||||
echo '<tr><td colspan="' . $colspan . '" class="empty_table">접근가능한 그룹이 없습니다.</td></tr>';
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="btn_list01 btn_list">
|
||||
<input type="submit" name="" value="선택삭제" class="btn btn_02">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function fboardgroupmember_submit(f) {
|
||||
if (!is_checked("chk[]")) {
|
||||
alert("선택삭제 하실 항목을 하나 이상 선택하세요.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function boardgroupmember_form_check(f) {
|
||||
if (f.gr_id.value == '') {
|
||||
alert('접근가능 그룹을 선택하세요.');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once './admin.tail.php';
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
$sub_menu = "300200";
|
||||
require_once './_common.php';
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'r');
|
||||
|
||||
$gr = get_group($gr_id);
|
||||
if (!$gr['gr_id']) {
|
||||
alert('존재하지 않는 그룹입니다.');
|
||||
}
|
||||
|
||||
$sql_common = " from {$g5['group_member_table']} a
|
||||
left outer join {$g5['member_table']} b on (a.mb_id = b.mb_id) ";
|
||||
$sql_search = " where gr_id = '{$gr_id}' ";
|
||||
|
||||
// 회원아이디로 검색되지 않던 오류를 수정
|
||||
if (isset($stx) && $stx) {
|
||||
$sql_search .= " and ( ";
|
||||
switch ($sfl) {
|
||||
default:
|
||||
$sql_search .= " ($sfl like '%$stx%') ";
|
||||
break;
|
||||
}
|
||||
$sql_search .= " ) ";
|
||||
}
|
||||
|
||||
if (!$sst) {
|
||||
$sst = "gm_datetime";
|
||||
$sod = "desc";
|
||||
}
|
||||
$sql_order = " order by {$sst} {$sod} ";
|
||||
|
||||
$sql = " select count(*) as cnt
|
||||
{$sql_common}
|
||||
{$sql_search}
|
||||
{$sql_order} ";
|
||||
$row = sql_fetch($sql);
|
||||
$total_count = $row['cnt'];
|
||||
|
||||
$rows = $config['cf_page_rows'];
|
||||
$total_page = ceil($total_count / $rows); // 전체 페이지 계산
|
||||
if ($page < 1) {
|
||||
$page = 1; // 페이지가 없으면 첫 페이지 (1 페이지)
|
||||
}
|
||||
$from_record = ($page - 1) * $rows; // 시작 열을 구함
|
||||
|
||||
$sql = " select *
|
||||
{$sql_common}
|
||||
{$sql_search}
|
||||
{$sql_order}
|
||||
limit {$from_record}, {$rows} ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
$g5['title'] = $gr['gr_subject'] . ' 그룹 접근가능회원 (그룹아이디:' . $gr['gr_id'] . ')';
|
||||
require_once './admin.head.php';
|
||||
|
||||
$colspan = 7;
|
||||
?>
|
||||
|
||||
<form name="fsearch" id="fsearch" class="local_sch01 local_sch" method="get">
|
||||
<input type="hidden" name="gr_id" value="<?php echo $gr_id ?>">
|
||||
<label for="sfl" class="sound_only">검색대상</label>
|
||||
<select name="sfl" id="sfl">
|
||||
<option value="a.mb_id" <?php echo get_selected($sfl, "a.mb_id") ?>>회원아이디</option>
|
||||
</select>
|
||||
<label for="stx" class="sound_only">검색어<strong class="sound_only"> 필수</strong></label>
|
||||
<input type="text" name="stx" value="<?php echo $stx ?>" id="stx" required class="required frm_input">
|
||||
<input type="submit" value="검색" class="btn_submit">
|
||||
</form>
|
||||
|
||||
<form name="fboardgroupmember" id="fboardgroupmember" action="./boardgroupmember_update.php" onsubmit="return fboardgroupmember_submit(this);" method="post">
|
||||
<input type="hidden" name="sst" value="<?php echo $sst ?>">
|
||||
<input type="hidden" name="sod" value="<?php echo $sod ?>">
|
||||
<input type="hidden" name="sfl" value="<?php echo $sfl ?>">
|
||||
<input type="hidden" name="stx" value="<?php echo $stx ?>">
|
||||
<input type="hidden" name="page" value="<?php echo $page ?>">
|
||||
<input type="hidden" name="token" value="<?php echo $token ?>">
|
||||
<input type="hidden" name="gr_id" value="<?php echo $gr_id ?>">
|
||||
<input type="hidden" name="w" value="ld">
|
||||
|
||||
<div class="tbl_head01 tbl_wrap">
|
||||
<table>
|
||||
<caption><?php echo $g5['title']; ?> 목록</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<label for="chkall" class="sound_only">접근가능회원 전체</label>
|
||||
<input type="checkbox" name="chkall" value="1" id="chkall" onclick="check_all(this.form)">
|
||||
</th>
|
||||
<th scope="col">그룹</th>
|
||||
<th scope="col"><?php echo subject_sort_link('b.mb_id', 'gr_id=' . $gr_id) ?>회원아이디</a></th>
|
||||
<th scope="col"><?php echo subject_sort_link('b.mb_name', 'gr_id=' . $gr_id) ?>이름</a></th>
|
||||
<th scope="col"><?php echo subject_sort_link('b.mb_nick', 'gr_id=' . $gr_id) ?>별명</a></th>
|
||||
<th scope="col"><?php echo subject_sort_link('b.mb_today_login', 'gr_id=' . $gr_id) ?>최종접속</a></th>
|
||||
<th scope="col"><?php echo subject_sort_link('a.gm_datetime', 'gr_id=' . $gr_id) ?>처리일시</a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
for ($i = 0; $row = sql_fetch_array($result); $i++) {
|
||||
// 접근가능한 그룹수
|
||||
$sql2 = " select count(*) as cnt from {$g5['group_member_table']} where mb_id = '{$row['mb_id']}' ";
|
||||
$row2 = sql_fetch($sql2);
|
||||
$group = "";
|
||||
if ($row2['cnt']) {
|
||||
$group = '<a href="./boardgroupmember_form.php?mb_id=' . $row['mb_id'] . '">' . $row2['cnt'] . '</a>';
|
||||
}
|
||||
|
||||
$mb_nick = get_sideview($row['mb_id'], $row['mb_nick'], $row['mb_email'], $row['mb_homepage']);
|
||||
|
||||
$bg = 'bg' . ($i % 2);
|
||||
?>
|
||||
<tr class="<?php echo $bg; ?>">
|
||||
<td class="td_chk">
|
||||
<label for="chk_<?php echo $i; ?>" class="sound_only"><?php echo $row['mb_nick'] ?> 회원</label>
|
||||
<input type="checkbox" name="chk[]" value="<?php echo $row['gm_id'] ?>" id="chk_<?php echo $i ?>">
|
||||
</td>
|
||||
<td class="td_grid"><?php echo $group ?></td>
|
||||
<td class="td_mbid"><?php echo $row['mb_id'] ?></td>
|
||||
<td class="td_mbname"><?php echo get_text($row['mb_name']); ?></td>
|
||||
<td class="td_name sv_use"><?php echo $mb_nick ?></td>
|
||||
<td class="td_datetime"><?php echo substr($row['mb_today_login'], 2, 8) ?></td>
|
||||
<td class="td_datetime"><?php echo $row['gm_datetime'] ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
|
||||
if ($i == 0) {
|
||||
echo '<tr><td colspan="' . $colspan . '" class="empty_table">자료가 없습니다.</td></tr>';
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="btn_list01 btn_list">
|
||||
<input type="submit" name="" value="선택삭제">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php
|
||||
echo get_paging(G5_IS_MOBILE ? $config['cf_mobile_pages'] : $config['cf_write_pages'], $page, $total_page, "{$_SERVER['SCRIPT_NAME']}?$qstr&gr_id=$gr_id&page=");
|
||||
?>
|
||||
|
||||
<script>
|
||||
function fboardgroupmember_submit(f) {
|
||||
if (!is_checked("chk[]")) {
|
||||
alert("선택삭제 하실 항목을 하나 이상 선택하세요.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once './admin.tail.php';
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
$sub_menu = "300200";
|
||||
require_once './_common.php';
|
||||
|
||||
sql_query(" ALTER TABLE {$g5['group_member_table']} CHANGE `gm_id` `gm_id` INT( 11 ) DEFAULT '0' NOT NULL AUTO_INCREMENT ", false);
|
||||
|
||||
if ($w == '') {
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
$mb = get_member($mb_id);
|
||||
if (empty($mb['mb_id'])) {
|
||||
alert('존재하지 않는 회원입니다.');
|
||||
}
|
||||
|
||||
$gr = get_group($gr_id);
|
||||
if (empty($gr['gr_id'])) {
|
||||
alert('존재하지 않는 그룹입니다.');
|
||||
}
|
||||
|
||||
$sql = " select count(*) as cnt
|
||||
from {$g5['group_member_table']}
|
||||
where gr_id = '{$gr_id}'
|
||||
and mb_id = '{$mb_id}' ";
|
||||
$row = sql_fetch($sql);
|
||||
if ($row['cnt']) {
|
||||
alert('이미 등록되어 있는 자료입니다.');
|
||||
} else {
|
||||
check_admin_token();
|
||||
|
||||
$sql = " insert into {$g5['group_member_table']}
|
||||
set gr_id = '{$_POST['gr_id']}',
|
||||
mb_id = '{$_POST['mb_id']}',
|
||||
gm_datetime = '" . G5_TIME_YMDHIS . "' ";
|
||||
sql_query($sql);
|
||||
}
|
||||
} elseif ($w == 'd' || $w == 'ld') {
|
||||
auth_check_menu($auth, $sub_menu, 'd');
|
||||
|
||||
$count = count($_POST['chk']);
|
||||
if (!$count) {
|
||||
alert('삭제할 목록을 하나이상 선택해 주세요.');
|
||||
}
|
||||
|
||||
check_admin_token();
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$gm_id = (int) $_POST['chk'][$i];
|
||||
$sql = " select * from {$g5['group_member_table']} where gm_id = '$gm_id' ";
|
||||
$gm = sql_fetch($sql);
|
||||
if (!$gm['gm_id']) {
|
||||
if ($count == 1) {
|
||||
alert('존재하지 않는 자료입니다.');
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$sql = " delete from {$g5['group_member_table']} where gm_id = '$gm_id' ";
|
||||
sql_query($sql);
|
||||
}
|
||||
}
|
||||
|
||||
if ($w == 'ld') {
|
||||
goto_url('./boardgroupmember_list.php?gr_id=' . $gr_id);
|
||||
} else {
|
||||
goto_url('./boardgroupmember_form.php?mb_id=' . $mb_id);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
$sub_menu = "100510";
|
||||
require_once './_common.php';
|
||||
|
||||
if (!(version_compare(phpversion(), '5.3.0', '>=') && defined('G5_BROWSCAP_USE') && G5_BROWSCAP_USE)) {
|
||||
alert('사용할 수 없는 기능입니다.', correct_goto_url(G5_ADMIN_URL));
|
||||
}
|
||||
|
||||
if ($is_admin != 'super') {
|
||||
alert('최고관리자만 접근 가능합니다.');
|
||||
}
|
||||
|
||||
$g5['title'] = 'Browscap 업데이트';
|
||||
require_once './admin.head.php';
|
||||
?>
|
||||
|
||||
<div id="processing">
|
||||
<p>Browscap 정보를 업데이트하시려면 아래 업데이트 버튼을 클릭해 주세요.</p>
|
||||
<button type="button" id="run_update">업데이트</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
$("#run_update").on("click", function() {
|
||||
$("#processing").html('<div class="update_processing"></div><p>Browscap 정보를 업데이트 중입니다.</p>');
|
||||
|
||||
$.ajax({
|
||||
url: "./browscap_update.php",
|
||||
async: true,
|
||||
cache: false,
|
||||
dataType: "html",
|
||||
success: function(data) {
|
||||
if (data != "") {
|
||||
alert(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
$("#processing").html("<div class='check_processing'></div><p>Browscap 정보를 업데이트 했습니다.</p>");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once './admin.tail.php';
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
$sub_menu = "100520";
|
||||
require_once './_common.php';
|
||||
|
||||
if (!(version_compare(phpversion(), '5.3.0', '>=') && defined('G5_BROWSCAP_USE') && G5_BROWSCAP_USE)) {
|
||||
alert('사용할 수 없는 기능입니다.', correct_goto_url(G5_ADMIN_URL));
|
||||
}
|
||||
|
||||
if ($is_admin != 'super') {
|
||||
alert('최고관리자만 접근 가능합니다.');
|
||||
}
|
||||
|
||||
$rows = isset($_GET['rows']) ? preg_replace('#[^0-9]#', '', $_GET['rows']) : 0;
|
||||
if (!$rows) {
|
||||
$rows = 100;
|
||||
}
|
||||
|
||||
$g5['title'] = '접속로그 변환';
|
||||
require_once './admin.head.php';
|
||||
?>
|
||||
|
||||
<div id="processing">
|
||||
<p>접속로그 정보를 Browscap 정보로 변환하시려면 아래 업데이트 버튼을 클릭해 주세요.</p>
|
||||
<button type="button" id="run_update">업데이트</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
$(document).on("click", "#run_update", function() {
|
||||
$("#processing").html('<div class="update_processing"></div><p>Browscap 정보로 변환 중입니다.</p>');
|
||||
|
||||
$.ajax({
|
||||
method: "GET",
|
||||
url: "./browscap_converter.php",
|
||||
data: {
|
||||
rows: "<?php echo strval($rows); ?>"
|
||||
},
|
||||
async: true,
|
||||
cache: false,
|
||||
dataType: "html",
|
||||
success: function(data) {
|
||||
$("#processing").html(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once './admin.tail.php';
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
ini_set('memory_limit', '-1');
|
||||
require_once './_common.php';
|
||||
|
||||
// clean the output buffer
|
||||
ob_end_clean();
|
||||
|
||||
if (!(version_compare(phpversion(), '5.3.0', '>=') && defined('G5_BROWSCAP_USE') && G5_BROWSCAP_USE)) {
|
||||
die('사용할 수 없는 기능입니다.');
|
||||
}
|
||||
|
||||
if ($is_admin != 'super') {
|
||||
die('최고관리자로 로그인 후 실행해 주세요.');
|
||||
}
|
||||
|
||||
// browscap cache 파일 체크
|
||||
if (!is_file(G5_DATA_PATH . '/cache/browscap_cache.php')) {
|
||||
echo '<p>Browscap 정보가 없습니다. 아래 링크로 이동해 Browscap 정보를 업데이트 하세요.</p>' . PHP_EOL;
|
||||
echo '<p><a href="' . G5_ADMIN_URL . '/browscap.php">Browscap 업데이트</a></p>' . PHP_EOL;
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once G5_PLUGIN_PATH . '/browscap/Browscap.php';
|
||||
$browscap = new phpbrowscap\Browscap(G5_DATA_PATH . '/cache');
|
||||
$browscap->doAutoUpdate = false;
|
||||
$browscap->cacheFilename = 'browscap_cache.php';
|
||||
|
||||
// 데이터 변환
|
||||
$rows = isset($_GET['rows']) ? preg_replace('#[^0-9]#', '', $_GET['rows']) : 0;
|
||||
if (!$rows) {
|
||||
$rows = 100;
|
||||
}
|
||||
|
||||
$sql_common = " from {$g5['visit_table']} where vi_agent <> '' and ( vi_browser = '' or vi_os = '' or vi_device = '' ) ";
|
||||
$sql_order = " order by vi_id desc ";
|
||||
$sql_limit = " limit 0, " . strval($rows) . " ";
|
||||
|
||||
$sql = " select count(vi_id) as cnt $sql_common ";
|
||||
$row = sql_fetch($sql);
|
||||
$total_count = $row['cnt'];
|
||||
|
||||
$sql = " select vi_id, vi_agent, vi_browser, vi_os, vi_device
|
||||
$sql_common
|
||||
$sql_order
|
||||
$sql_limit ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
$cnt = 0;
|
||||
for ($i = 0; $row = sql_fetch_array($result); $i++) {
|
||||
$info = $browscap->getBrowser($row['vi_agent']);
|
||||
|
||||
$brow = $row['vi_browser'];
|
||||
if (!$brow) {
|
||||
$brow = $info->Comment;
|
||||
}
|
||||
|
||||
$os = $row['vi_os'];
|
||||
if (!$os) {
|
||||
$os = $info->Platform;
|
||||
}
|
||||
|
||||
$device = $row['vi_device'];
|
||||
if (!$device) {
|
||||
$device = $info->Device_Type;
|
||||
}
|
||||
|
||||
$sql2 = " update {$g5['visit_table']}
|
||||
set vi_browser = '$brow',
|
||||
vi_os = '$os',
|
||||
vi_device = '$device'
|
||||
where vi_id = '{$row['vi_id']}' ";
|
||||
sql_query($sql2);
|
||||
|
||||
$cnt++;
|
||||
}
|
||||
|
||||
if (($total_count - $cnt) == 0 || $total_count == 0) {
|
||||
echo '<div class="check_processing"></div><p>변환완료</p>';
|
||||
} else {
|
||||
echo '<p>총 ' . number_format($total_count) . '건 중 ' . number_format($cnt) . '건 변환완료<br><br>접속로그를 추가로 변환하시려면 아래 업데이트 버튼을 클릭해 주세요.</p><button type="button" id="run_update">업데이트</button>';
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
ini_set('memory_limit', '-1');
|
||||
|
||||
$sub_menu = "100510";
|
||||
require_once './_common.php';
|
||||
|
||||
// clean the output buffer
|
||||
ob_end_clean();
|
||||
|
||||
if (!(version_compare(phpversion(), '5.3.0', '>=') && defined('G5_BROWSCAP_USE') && G5_BROWSCAP_USE)) {
|
||||
die('사용할 수 없는 기능입니다.');
|
||||
}
|
||||
|
||||
if ($is_admin != 'super') {
|
||||
die('최고관리자만 접근 가능합니다.');
|
||||
}
|
||||
|
||||
require_once G5_PLUGIN_PATH . '/browscap/Browscap.php';
|
||||
|
||||
$browscap = new phpbrowscap\Browscap(G5_DATA_PATH . '/cache');
|
||||
$browscap->updateMethod = 'cURL';
|
||||
$browscap->cacheFilename = 'browscap_cache.php';
|
||||
$browscap->updateCache();
|
||||
|
||||
die('');
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
$sub_menu = '100900';
|
||||
require_once './_common.php';
|
||||
|
||||
if ($is_admin != 'super') {
|
||||
alert('최고관리자만 접근 가능합니다.', G5_URL);
|
||||
}
|
||||
|
||||
@require_once './safe_check.php';
|
||||
if (function_exists('social_log_file_delete')) {
|
||||
social_log_file_delete();
|
||||
}
|
||||
|
||||
run_event('adm_cache_file_delete_before');
|
||||
|
||||
$g5['title'] = '캐시파일 일괄삭제';
|
||||
require_once './admin.head.php';
|
||||
?>
|
||||
|
||||
<div class="local_desc02 local_desc">
|
||||
<p>
|
||||
완료 메세지가 나오기 전에 프로그램의 실행을 중지하지 마십시오.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
flush();
|
||||
|
||||
if (!$dir = @opendir(G5_DATA_PATH . '/cache')) {
|
||||
echo '<p>캐시디렉토리를 열지못했습니다.</p>';
|
||||
}
|
||||
|
||||
$cnt = 0;
|
||||
echo '<ul class="session_del">' . PHP_EOL;
|
||||
|
||||
$files = glob(G5_DATA_PATH . '/cache/latest-*');
|
||||
$content_files = glob(G5_DATA_PATH . '/cache/content-*');
|
||||
|
||||
$files = array_merge($files, $content_files);
|
||||
if (is_array($files)) {
|
||||
foreach ($files as $cache_file) {
|
||||
$cnt++;
|
||||
unlink($cache_file);
|
||||
echo '<li>' . $cache_file . '</li>' . PHP_EOL;
|
||||
|
||||
flush();
|
||||
|
||||
if ($cnt % 10 == 0) {
|
||||
echo PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run_event('adm_cache_file_delete');
|
||||
|
||||
echo '<li>완료됨</li></ul>' . PHP_EOL;
|
||||
echo '<div class="local_desc01 local_desc"><p><strong>최신글 캐시파일 ' . $cnt . '건 삭제 완료됐습니다.</strong><br>프로그램의 실행을 끝마치셔도 좋습니다.</p></div>' . PHP_EOL;
|
||||
?>
|
||||
|
||||
<?php
|
||||
require_once './admin.tail.php';
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
$sub_menu = '100910';
|
||||
require_once './_common.php';
|
||||
|
||||
if ($is_admin != 'super') {
|
||||
alert('최고관리자만 접근 가능합니다.', G5_URL);
|
||||
}
|
||||
|
||||
$g5['title'] = '캡챠파일 일괄삭제';
|
||||
require_once './admin.head.php';
|
||||
?>
|
||||
|
||||
<div class="local_desc02 local_desc">
|
||||
<p>
|
||||
완료 메세지가 나오기 전에 프로그램의 실행을 중지하지 마십시오.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
flush();
|
||||
|
||||
if (!$dir = @opendir(G5_DATA_PATH . '/cache')) {
|
||||
echo '<p>캐시디렉토리를 열지못했습니다.</p>';
|
||||
}
|
||||
|
||||
$cnt = 0;
|
||||
echo '<ul class="session_del">' . PHP_EOL;
|
||||
|
||||
$files = glob(G5_DATA_PATH . '/cache/?captcha-*');
|
||||
if (is_array($files)) {
|
||||
$before_time = G5_SERVER_TIME - 3600; // 한시간전
|
||||
foreach ($files as $gcaptcha_file) {
|
||||
$modification_time = filemtime($gcaptcha_file); // 파일접근시간
|
||||
|
||||
if ($modification_time > $before_time) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cnt++;
|
||||
unlink($gcaptcha_file);
|
||||
echo '<li>' . $gcaptcha_file . '</li>' . PHP_EOL;
|
||||
|
||||
flush();
|
||||
|
||||
if ($cnt % 10 == 0) {
|
||||
echo PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo '<li>완료됨</li></ul>' . PHP_EOL;
|
||||
echo '<div class="local_desc01 local_desc"><p><strong>캡챠파일 ' . $cnt . '건의 삭제 완료됐습니다.</strong><br>프로그램의 실행을 끝마치셔도 좋습니다.</p></div>' . PHP_EOL;
|
||||
?>
|
||||
|
||||
<?php
|
||||
require_once './admin.tail.php';
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
define('G5_IS_ADMIN', true);
|
||||
require_once '../../common.php';
|
||||
require_once G5_ADMIN_PATH . '/admin.lib.php';
|
||||
|
||||
// 💡 [추가] 코드 관리자 모듈 테이블 정의
|
||||
$g5['ui_manager_table'] = G5_TABLE_PREFIX.'ui_manager';
|
||||
$g5['form_category_table'] = G5_TABLE_PREFIX.'form_category';
|
||||
$g5['common_lang_table'] = G5_TABLE_PREFIX.'common_lang';
|
||||
$g5['form_option_history_table'] = G5_TABLE_PREFIX.'form_option_history';
|
||||
|
||||
if (isset($token)) {
|
||||
$token = @htmlspecialchars(strip_tags($token), ENT_QUOTES);
|
||||
}
|
||||
|
||||
run_event('admin_common');
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
// 💡 [수정] 700번대 최상위 메뉴 배열에 아이콘 클래스('fa-puzzle-piece')를 추가합니다.
|
||||
$menu['menu700'][] = array('700000', 'UI/폼 관리', G5_ADMIN_URL.'/code_manager/ui_manager_list.php', 'code_manager', 'fa-puzzle-piece');
|
||||
|
||||
// 'UI/폼 관리'의 하위 메뉴들을 정의합니다.
|
||||
$menu['menu700'][] = array('700100', 'UI 리소스 관리', G5_ADMIN_URL.'/code_manager/ui_manager_list.php', 'ui_resource_manager');
|
||||
$menu['menu700'][] = array('700900', '솔루션 설치', G5_ADMIN_URL.'/code_manager/install.php', 'ui_solution_install');
|
||||
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
$sub_menu = '700100';
|
||||
include_once('./_common.php');
|
||||
|
||||
// 💡 [추가] w 값을 먼저 정의합니다.
|
||||
$w = isset($_REQUEST['w']) ? substr(trim($_REQUEST['w']), 0, 1) : '';
|
||||
|
||||
// ==================================================================
|
||||
// 💡 [핵심 수정] 폼 제출 처리 로직 (신규/수정)
|
||||
// ==================================================================
|
||||
// POST 요청이고, w값이 넘어왔을 때 (신규='', 수정='u') 처리
|
||||
if (isset($_POST['w']) && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
check_admin_token();
|
||||
|
||||
$w_from_post = $_POST['w']; // POST로 받은 w값을 기준으로 처리
|
||||
$um_id = (int)$_POST['um_id'];
|
||||
$parent_id = (int)$_POST['parent_id'];
|
||||
$fc_key = trim($_POST['fc_key']);
|
||||
$cl_name = trim($_POST['cl_name']);
|
||||
$fc_order = (int)$_POST['fc_order'];
|
||||
$is_used = (int)$_POST['is_used'];
|
||||
|
||||
if (!$um_id) {
|
||||
alert('잘못된 접근입니다.');
|
||||
}
|
||||
if (!$fc_key) {
|
||||
alert('옵션 키(Key)를 입력해주세요.');
|
||||
}
|
||||
if (!$cl_name) {
|
||||
alert('옵션 이름을 입력해주세요.');
|
||||
}
|
||||
|
||||
// 1. g5_form_category 테이블에 마스터 정보 저장
|
||||
$sql_common = "
|
||||
um_id = '{$um_id}',
|
||||
parent_id = '{$parent_id}',
|
||||
fc_key = '{$fc_key}',
|
||||
fc_order = '{$fc_order}',
|
||||
is_used = '{$is_used}',
|
||||
updated_at = '" . G5_TIME_YMDHIS . "',
|
||||
updated_by = '{$member['mb_id']}'
|
||||
";
|
||||
|
||||
if ($w_from_post == 'u') { // 수정
|
||||
$fc_id = (int)$_POST['fc_id'];
|
||||
if (!$fc_id) alert('fc_id 값이 없습니다.');
|
||||
|
||||
$sql = "UPDATE {$g5['form_category_table']} SET {$sql_common} WHERE fc_id = '{$fc_id}'";
|
||||
sql_query($sql);
|
||||
} else { // 신규
|
||||
$sql_common .= ", created_at = '" . G5_TIME_YMDHIS . "', created_by = '{$member['mb_id']}'";
|
||||
$sql = "INSERT INTO {$g5['form_category_table']} SET {$sql_common}";
|
||||
sql_query($sql);
|
||||
$fc_id = sql_insert_id();
|
||||
}
|
||||
|
||||
// 2. g5_common_lang 테이블에 다국어 이름 저장 (없으면 생성, 있으면 수정)
|
||||
$sql = "SELECT cl_id FROM {$g5['common_lang_table']} WHERE target_table = '{$g5['form_category_table']}' AND target_id = '{$fc_id}' AND lang_code = 'ko'";
|
||||
$lang_row = sql_fetch($sql);
|
||||
|
||||
if (isset($lang_row['cl_id'])) {
|
||||
$sql = "UPDATE {$g5['common_lang_table']} SET cl_name = '{$cl_name}', updated_at = '" . G5_TIME_YMDHIS . "', updated_by = '{$member['mb_id']}' WHERE cl_id = '{$lang_row['cl_id']}'";
|
||||
} else {
|
||||
$sql = "INSERT INTO {$g5['common_lang_table']} SET target_table = '{$g5['form_category_table']}', target_id = '{$fc_id}', lang_code = 'ko', cl_name = '{$cl_name}', updated_at = '" . G5_TIME_YMDHIS . "', updated_by = '{$member['mb_id']}'";
|
||||
}
|
||||
sql_query($sql);
|
||||
|
||||
goto_url("./category_list.php?um_id=$um_id");
|
||||
}
|
||||
// ==================================================================
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'r');
|
||||
|
||||
// 그룹 ID가 없으면 되돌려 보냄
|
||||
$um_id = isset($_GET['um_id']) ? (int)$_GET['um_id'] : 0;
|
||||
if (!$um_id) {
|
||||
alert('UI 리소스 ID가 올바르지 않습니다.', './ui_manager_list.php');
|
||||
}
|
||||
|
||||
// 현재 관리하려는 리소스의 정보를 가져옴
|
||||
$sql = "SELECT * FROM {$g5['ui_manager_table']} WHERE um_id = '{$um_id}'";
|
||||
$ui_resource = sql_fetch($sql);
|
||||
|
||||
if (!isset($ui_resource['um_id']) || $ui_resource['resource_type'] != 'DATA') {
|
||||
alert('존재하지 않거나 데이터 타입이 아닌 리소스입니다.', './ui_manager_list.php');
|
||||
}
|
||||
|
||||
// 계층형으로 정렬된 카테고리 목록을 가져오는 함수
|
||||
function get_category_view_list($um_id)
|
||||
{
|
||||
global $g5;
|
||||
$sql = "SELECT
|
||||
A.*,
|
||||
B.cl_name
|
||||
FROM
|
||||
{$g5['form_category_table']} AS A
|
||||
LEFT JOIN
|
||||
{$g5['common_lang_table']} AS B
|
||||
ON
|
||||
(A.fc_id = B.target_id AND B.target_table = '{$g5['form_category_table']}' AND B.lang_code = 'ko')
|
||||
WHERE
|
||||
A.um_id = '{$um_id}' AND A.is_deleted = 0
|
||||
ORDER BY
|
||||
A.parent_id, A.fc_order, A.fc_id";
|
||||
$result = sql_query($sql);
|
||||
$categories = [];
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$categories[] = $row;
|
||||
}
|
||||
|
||||
$view_list = [];
|
||||
// 재귀 함수를 사용하여 계층 구조를 평탄화하고 depth를 추가
|
||||
generate_category_list($categories, 0, 0, $view_list);
|
||||
return $view_list;
|
||||
}
|
||||
|
||||
function generate_category_list(&$source, $parent_id, $depth, &$result_list)
|
||||
{
|
||||
foreach ($source as $item) {
|
||||
if ($item['parent_id'] == $parent_id) {
|
||||
$item['depth'] = $depth;
|
||||
$result_list[] = $item;
|
||||
generate_category_list($source, $item['fc_id'], $depth + 1, $result_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$category_list = get_category_view_list($um_id);
|
||||
$category_list_count = count($category_list);
|
||||
|
||||
$g5['title'] = get_text($ui_resource['resource_desc']) . ' : 옵션/카테고리 관리';
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
|
||||
add_stylesheet('<link rel="stylesheet" href="' . G5_ADMIN_URL . '/code_manager/css/code_manager.css?ver=1.1">', 0);
|
||||
?>
|
||||
|
||||
<div class="local_desc01 local_desc">
|
||||
<p>
|
||||
<strong>'<?php echo get_text($ui_resource['resource_desc']); ?>'</strong> 리소스에 포함될 옵션 또는 카테고리를 관리합니다.<br>
|
||||
'부모 카테고리'를 지정하여 대/중/소 분류와 같은 계층 구조를 만들 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section id="code_manager">
|
||||
<div class="code-manager-header">
|
||||
<h2 class="code-manager-title">
|
||||
<a href="./ui_manager_list.php" class="btn btn_02">리소스 목록으로</a>
|
||||
<?php echo get_text($ui_resource['resource_desc']); ?>
|
||||
(<code><?php echo get_text($ui_resource['resource_code']); ?></code>)
|
||||
</h2>
|
||||
<div class="code-manager-actions">
|
||||
<button type="button" id="add-category-btn" class="btn btn_01">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i> 새 옵션 추가
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 새 옵션(카테고리) 추가 폼 -->
|
||||
<div id="category-form-container" style="display: none; margin-top: 20px;">
|
||||
<form name="fcategoryform" id="fcategoryform" action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="post">
|
||||
<input type="hidden" name="w" value="">
|
||||
<input type="hidden" name="um_id" value="<?php echo $um_id; ?>">
|
||||
<input type="hidden" name="fc_id" value="">
|
||||
<input type="hidden" name="token" value="<?php echo get_admin_token(); ?>">
|
||||
|
||||
<div class="tbl_frm01 tbl_wrap">
|
||||
<table>
|
||||
<caption>옵션/카테고리 추가/수정 폼</caption>
|
||||
<colgroup>
|
||||
<col class="grid_4">
|
||||
<col>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row"><label for="parent_id">부모 카테고리</label></th>
|
||||
<td>
|
||||
<select name="parent_id" id="parent_id">
|
||||
<option value="0">최상위 카테고리</option>
|
||||
<?php foreach ($category_list as $cat) : ?>
|
||||
<option value="<?php echo $cat['fc_id']; ?>">
|
||||
<?php echo str_repeat(' ', $cat['depth']); ?><?php echo get_text($cat['cl_name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="fc_key">옵션 키(Key)</label></th>
|
||||
<td>
|
||||
<input type="text" name="fc_key" id="fc_key" required class="required frm_input" size="30">
|
||||
<span class="frm_info">DB에 저장될 고유한 값입니다. (영문, 숫자, _ 사용)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="cl_name">옵션 이름</label></th>
|
||||
<td>
|
||||
<input type="text" name="cl_name" id="cl_name" required class="required frm_input" size="50">
|
||||
<span class="frm_info">화면에 표시될 이름입니다.</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="fc_order">정렬순서</label></th>
|
||||
<td>
|
||||
<input type="number" name="fc_order" value="0" id="fc_order" class="frm_input" size="5">
|
||||
<span class="frm_info">숫자가 낮을수록 먼저 표시됩니다.</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">사용 여부</th>
|
||||
<td>
|
||||
<label><input type="radio" name="is_used" value="1" checked> 사용함</label>
|
||||
<label><input type="radio" name="is_used" value="0"> 사용안함</label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="btn_confirm01 btn_confirm">
|
||||
<button type="button" id="cancel-category-btn" class="btn_cancel btn">취소</button>
|
||||
<input type="submit" value="저장" class="btn_submit btn" accesskey="s">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 등록된 옵션(카테고리) 목록 테이블 -->
|
||||
<div class="tbl_head01 tbl_wrap" style="margin-top: 20px;">
|
||||
<table>
|
||||
<caption>옵션/카테고리 목록</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">카테고리 이름</th>
|
||||
<th scope="col" style="width: 150px;">옵션 키 (Key)</th>
|
||||
<th scope="col" style="width: 100px;">부모 ID</th>
|
||||
<th scope="col" style="width: 60px;">순서</th>
|
||||
<th scope="col" style="width: 60px;">사용</th>
|
||||
<th scope="col" style="width: 180px;">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($category_list_count > 0) : ?>
|
||||
<?php foreach ($category_list as $cat) : ?>
|
||||
<tr>
|
||||
<td class="td_left category-name-depth-<?php echo $cat['depth']; ?>">
|
||||
<?php if ($cat['depth'] > 0) : ?>
|
||||
<span class="depth-prefix">└</span>
|
||||
<?php endif; ?>
|
||||
<?php echo get_text($cat['cl_name']); ?>
|
||||
</td>
|
||||
<td><?php echo get_text($cat['fc_key']); ?></td>
|
||||
<td><?php echo $cat['parent_id']; ?></td>
|
||||
<td><?php echo $cat['fc_order']; ?></td>
|
||||
<td><?php echo $cat['is_used'] ? 'Y' : 'N'; ?></td>
|
||||
<td class="td_mng td_mng_s">
|
||||
<button type="button" class="btn btn_02 btn_edit_category"
|
||||
data-fc_id="<?php echo $cat['fc_id']; ?>"
|
||||
data-parent_id="<?php echo $cat['parent_id']; ?>"
|
||||
data-fc_key="<?php echo get_text($cat['fc_key']); ?>"
|
||||
data-cl_name="<?php echo get_text($cat['cl_name']); ?>"
|
||||
data-fc_order="<?php echo $cat['fc_order']; ?>"
|
||||
data-is_used="<?php echo $cat['is_used']; ?>">수정
|
||||
</button>
|
||||
<a href="./lang_manager.php?target_table=<?php echo $g5['form_category_table']; ?>&target_id=<?php echo $cat['fc_id']; ?>"
|
||||
class="btn btn_01">다국어</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<tr class="empty_table">
|
||||
<td colspan="6">등록된 옵션이 없습니다.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script src="<?php echo G5_ADMIN_URL; ?>/code_manager/js/ui_manager.js?ver=1.4"></script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
$sub_menu = '100900'; // admin.menu100.php 에 정의된 메뉴 코드
|
||||
include_once('./_common.php');
|
||||
|
||||
// ==================================================================
|
||||
// 💡 [핵심] 폼 제출 처리 로직
|
||||
// ==================================================================
|
||||
if (isset($w) && $w == 'u' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
check_admin_token();
|
||||
|
||||
// 입력값 정리
|
||||
$screen_code = trim($_POST['screen_code']);
|
||||
$group_code = trim($_POST['group_code']);
|
||||
$resource_code = trim($_POST['resource_code']);
|
||||
$resource_type = trim($_POST['resource_type']);
|
||||
$resource_desc = trim($_POST['resource_desc']);
|
||||
$cl_name = isset($_POST['cl_name']) ? trim($_POST['cl_name']) : ''; // LABEL 타입일 때만 넘어옴
|
||||
|
||||
// 유효성 검사
|
||||
if (!$screen_code || !$group_code || !$resource_code || !$resource_type) {
|
||||
alert('필수 항목을 모두 입력해주세요.');
|
||||
}
|
||||
if ($resource_type == 'LABEL' && !$cl_name) {
|
||||
alert('UI 라벨 타입은 한국어 라벨명을 필수로 입력해야 합니다.');
|
||||
}
|
||||
|
||||
// 중복 검사
|
||||
$sql = "SELECT COUNT(*) as cnt FROM {$g5['ui_manager_table']} WHERE screen_code = '{$screen_code}' AND group_code = '{$group_code}' AND resource_code = '{$resource_code}'";
|
||||
$row = sql_fetch($sql);
|
||||
if ($row['cnt']) {
|
||||
alert('이미 등록된 리소스 코드입니다.');
|
||||
}
|
||||
|
||||
// 1. g5_ui_manager 테이블에 리소스 '설계' 정보 저장
|
||||
$sql = "INSERT INTO {$g5['ui_manager_table']}
|
||||
SET screen_code = '{$screen_code}',
|
||||
group_code = '{$group_code}',
|
||||
resource_code = '{$resource_code}',
|
||||
resource_type = '{$resource_type}',
|
||||
resource_desc = '{$resource_desc}',
|
||||
is_used = '1',
|
||||
created_at = '".G5_TIME_YMDHIS."',
|
||||
created_by = '{$member['mb_id']}',
|
||||
updated_at = '".G5_TIME_YMDHIS."',
|
||||
updated_by = '{$member['mb_id']}'";
|
||||
sql_query($sql);
|
||||
$um_id = sql_insert_id();
|
||||
|
||||
// 2. 리소스 타입이 'LABEL'인 경우, g5_common_lang 테이블에 실제 텍스트 저장
|
||||
if ($resource_type == 'LABEL' && $cl_name) {
|
||||
$sql = "INSERT INTO {$g5['common_lang_table']}
|
||||
SET target_table = '{$g5['ui_manager_table']}',
|
||||
target_id = '{$um_id}',
|
||||
lang_code = 'ko',
|
||||
cl_name = '{$cl_name}',
|
||||
updated_at = '".G5_TIME_YMDHIS."',
|
||||
updated_by = '{$member['mb_id']}'";
|
||||
sql_query($sql);
|
||||
}
|
||||
|
||||
goto_url('./code_list.php');
|
||||
}
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'r');
|
||||
|
||||
// 등록된 리소스 목록 조회
|
||||
$sql = "SELECT * FROM {$g5['ui_manager_table']} ORDER BY screen_code, group_code, resource_code";
|
||||
$result = sql_query($sql);
|
||||
$resource_list = [];
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$resource_list[] = $row;
|
||||
}
|
||||
$resource_list_count = count($resource_list);
|
||||
|
||||
$g5['title'] = 'UI 리소스 관리';
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
|
||||
// 솔루션 전용 CSS/JS 파일을 불러옵니다.
|
||||
add_stylesheet('<link rel="stylesheet" href="' . G5_ADMIN_URL . '/code_manager/css/code_manager.css">', 0);
|
||||
?>
|
||||
|
||||
<div class="local_desc01 local_desc">
|
||||
<p>
|
||||
웹사이트의 모든 화면에 사용되는 텍스트(라벨)와 선택 옵션(데이터)을 체계적으로 관리합니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section id="code_manager">
|
||||
<div class="code-manager-header">
|
||||
<h2 class="code-manager-title">UI 리소스 목록</h2>
|
||||
<div class="code-manager-actions">
|
||||
<button type="button" id="add-resource-btn" class="btn btn_01">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i> 새 리소스 추가
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 새 리소스 추가 폼 -->
|
||||
<div id="resource-form-container" style="display: none;">
|
||||
<form name="fresourceform" id="fresourceform" action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="post">
|
||||
<input type="hidden" name="w" value="u">
|
||||
<input type="hidden" name="token" value="<?php echo get_admin_token(); ?>">
|
||||
|
||||
<div class="tbl_frm01 tbl_wrap">
|
||||
<table>
|
||||
<caption>UI 리소스 추가 폼</caption>
|
||||
<colgroup>
|
||||
<col class="grid_4">
|
||||
<col>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row"><label for="screen_code">화면 코드</label></th>
|
||||
<td>
|
||||
<input type="text" name="screen_code" id="screen_code" required class="required frm_input" size="30">
|
||||
<span class="frm_info">리소스가 사용될 화면의 고유 코드 (예: order_form, member_join)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="group_code">그룹 코드</label></th>
|
||||
<td>
|
||||
<input type="text" name="group_code" id="group_code" required class="required frm_input" size="30">
|
||||
<span class="frm_info">화면 내에서 리소스를 묶어줄 그룹 코드 (예: address_info, common_options)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">리소스 타입</th>
|
||||
<td>
|
||||
<label><input type="radio" name="resource_type" value="LABEL" checked> UI 라벨 (단일 텍스트)</label>
|
||||
<label><input type="radio" name="resource_type" value="DATA"> 데이터 (선택 옵션)</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="resource-type-field" id="label-field">
|
||||
<th scope="row"><label for="cl_name">한국어 라벨명</label></th>
|
||||
<td>
|
||||
<input type="text" name="cl_name" id="cl_name" class="frm_input" size="50">
|
||||
<span class="frm_info">화면에 표시될 실제 텍스트 (예: 집의 유형, 창호 재질)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="resource_code">리소스 코드</label></th>
|
||||
<td>
|
||||
<input type="text" name="resource_code" id="resource_code" required class="required frm_input" size="30">
|
||||
<span class="frm_info">개발자가 이 리소스를 호출할 때 사용할 고유 코드 (예: house_type_label, house_type_data)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="resource_desc">설명</label></th>
|
||||
<td>
|
||||
<input type="text" name="resource_desc" id="resource_desc" class="frm_input" size="80">
|
||||
<span class="frm_info">이 리소스의 용도에 대한 설명 (관리자 참고용)</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="btn_confirm01 btn_confirm">
|
||||
<button type="button" id="cancel-resource-btn" class="btn_cancel btn">취소</button>
|
||||
<input type="submit" value="리소스 등록" class="btn_submit btn" accesskey="s">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 등록된 리소스 목록 테이블 -->
|
||||
<div class="tbl_head01 tbl_wrap">
|
||||
<table>
|
||||
<caption>UI 리소스 목록</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">화면 코드</th>
|
||||
<th scope="col">그룹 코드</th>
|
||||
<th scope="col">리소스 코드</th>
|
||||
<th scope="col">타입</th>
|
||||
<th scope="col">설명</th>
|
||||
<th scope="col">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($resource_list_count > 0) : ?>
|
||||
<?php foreach ($resource_list as $res) : ?>
|
||||
<tr>
|
||||
<td><?php echo get_text($res['screen_code']); ?></td>
|
||||
<td><?php echo get_text($res['group_code']); ?></td>
|
||||
<td><?php echo get_text($res['resource_code']); ?></td>
|
||||
<td><?php echo $res['resource_type']; ?></td>
|
||||
<td class="td_left"><?php echo get_text($res['resource_desc']); ?></td>
|
||||
<td class="td_mng">
|
||||
<?php if ($res['resource_type'] == 'DATA') : ?>
|
||||
<a href="./category_list.php?um_id=<?php echo $res['um_id']; ?>" class="btn btn_03">옵션 관리</a>
|
||||
<?php endif; ?>
|
||||
<a href="./category_list.php?w=u&um_id=<?php echo $res['um_id']; ?>" class="btn btn_02">수정</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<tr class="empty_table">
|
||||
<td colspan="6">등록된 리소스가 없습니다.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script src="<?php echo G5_ADMIN_URL; ?>/code_manager/js/code_manager.js"></script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
@@ -0,0 +1,196 @@
|
||||
/* 폼 옵션 관리 솔루션 전용 스타일 */
|
||||
#code_manager .code-manager-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
#code_manager .code-manager-title {
|
||||
margin: 0;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
#code_manager .code-manager-actions .btn {
|
||||
padding: 5px 12px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* 새 리소스 추가 폼 */
|
||||
#resource-form-container {
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
border: 1px solid #e0e0e0;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 5px;
|
||||
}
|
||||
#resource-form-container .frm_info {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 💡 [추가] 아코디언 UI 스타일 */
|
||||
#resource-list-accordion {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.accordion-item {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.accordion-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.accordion-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 20px;
|
||||
background-color: #f7f7f7;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
.accordion-header:hover {
|
||||
background-color: #efefef;
|
||||
}
|
||||
.accordion-header .screen-title {
|
||||
font-size: 1.1em;
|
||||
font-weight: 500;
|
||||
}
|
||||
.accordion-header .screen-title .fa {
|
||||
margin-right: 8px;
|
||||
color: #555;
|
||||
}
|
||||
.accordion-header .resource-count {
|
||||
font-size: 0.9em;
|
||||
color: #fff;
|
||||
background-color: #888;
|
||||
padding: 3px 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.accordion-header .accordion-icon {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.accordion-item.active .accordion-header .accordion-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.accordion-content {
|
||||
display: none;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
.accordion-content .tbl_wrap {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 💡 [추가] 리소스 타입 시각적 구분 */
|
||||
.res-type-label, .res-type-data {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
font-size: 0.85em;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
.res-type-label {
|
||||
background-color: #3498db; /* 파란색 계열 */
|
||||
}
|
||||
.res-type-data {
|
||||
background-color: #2ecc71; /* 녹색 계열 */
|
||||
}
|
||||
|
||||
/* 기타 스타일 */
|
||||
.tbl_head01 .td_left {
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
/* ... 기존 CSS 코드 ... */
|
||||
|
||||
/* 💡 [추가] 카테고리 목록 들여쓰기 스타일 */
|
||||
.category-name-depth-0 { font-weight: bold; }
|
||||
.category-name-depth-1 { padding-left: 25px !important; }
|
||||
.category-name-depth-2 { padding-left: 50px !important; }
|
||||
.category-name-depth-3 { padding-left: 75px !important; }
|
||||
.category-name-depth-4 { padding-left: 100px !important; }
|
||||
|
||||
.depth-prefix {
|
||||
font-family: "Malgun Gothic", "Apple SD Gothic Neo", sans-serif;
|
||||
font-weight: normal;
|
||||
color: #aaa;
|
||||
margin-right: 5px;
|
||||
}
|
||||
/* ... 기존 CSS 코드 ... */
|
||||
|
||||
/* 💡 [추가] 삭제 버튼 스타일 */
|
||||
.btn_delete {
|
||||
display: inline-block;
|
||||
padding: 0 10px;
|
||||
height: 28px;
|
||||
line-height: 26px;
|
||||
border: 1px solid #d43f3a;
|
||||
background: #d9534f;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn_delete:hover {
|
||||
background: #c9302c;
|
||||
border-color: #ac2925;
|
||||
}
|
||||
/* ... 기존 CSS 코드 ... */
|
||||
|
||||
/* 💡 [추가] 다국어 관리 페이지 스타일 */
|
||||
.h2_frm {
|
||||
margin: 20px 0 10px;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
.td_alignc {
|
||||
text-align: center;
|
||||
}
|
||||
#flangform textarea {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
}
|
||||
/* ... 기존 CSS 코드 ... */
|
||||
|
||||
/* 💡 [추가] 검색 폼 스타일 */
|
||||
#resource_search_form {
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
border: 1px solid #e9e9e9;
|
||||
background: #fcfcfc;
|
||||
}
|
||||
#resource_search_form .h2_frm {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
#resource_search_form .search-form-inner {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
#resource_search_form select,
|
||||
#resource_search_form .frm_input {
|
||||
height: 35px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
#resource_search_form .btn_submit {
|
||||
height: 35px;
|
||||
padding: 0 20px;
|
||||
font-size: 1em;
|
||||
}
|
||||
/* ... 기존 CSS 코드 ... */
|
||||
|
||||
/* 💡 [추가] 페이징 스타일 */
|
||||
.pagination_wrap {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.pagination_wrap .pg_wrap {
|
||||
display: inline-block;
|
||||
float: none;
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
$sub_menu = '700900';
|
||||
include_once('./_common.php');
|
||||
include_once(__DIR__ . '/lib/SchemaManager.class.php');
|
||||
|
||||
if ($is_admin != 'super') {
|
||||
alert('최고관리자만 접근 가능합니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL 파일에서 테이블 이름을 추출하는 함수
|
||||
*/
|
||||
function get_tables_from_sql_file($filepath) {
|
||||
$tables = [];
|
||||
if (!file_exists($filepath)) {
|
||||
return $tables;
|
||||
}
|
||||
|
||||
$lines = file($filepath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/CREATE TABLE(?: IF NOT EXISTS)? `([^`]+)`/i', $line, $matches)) {
|
||||
$tables[] = $matches[1];
|
||||
}
|
||||
}
|
||||
return $tables;
|
||||
}
|
||||
|
||||
$g5['title'] = 'UI 리소스 관리 솔루션 설치';
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
|
||||
$install_result = null;
|
||||
$delete_result = null;
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
// 💡 [수정] SQL 파일에서 테이블 목록 동적 로드
|
||||
$tables_to_check = get_tables_from_sql_file(__DIR__ . '/install.sql');
|
||||
|
||||
if ($action === 'install') {
|
||||
check_admin_token();
|
||||
// ... (설치 로직은 기존과 동일)
|
||||
$copy_results = [];
|
||||
$solution_files = [
|
||||
['source' => __DIR__ . '/lib/ui_manager.extend.php', 'target' => G5_EXTEND_PATH . '/ui_manager.extend.php', 'desc' => '핵심 기능 파일'],
|
||||
['source' => __DIR__ . '/admin.menu700.code_manager.php', 'target' => G5_ADMIN_PATH . '/admin.menu700.code_manager.php', 'desc' => '관리자 메뉴 파일']
|
||||
];
|
||||
foreach ($solution_files as $file) {
|
||||
$key = $file['target'];
|
||||
if (file_exists($file['source']) && is_writable(dirname($file['target']))) {
|
||||
@copy($file['source'], $file['target']);
|
||||
}
|
||||
}
|
||||
$sql_file = __DIR__ . '/install.sql';
|
||||
$db_results = [];
|
||||
try {
|
||||
$schemaManager = new SchemaManager($sql_file);
|
||||
$schemaManager->execute();
|
||||
$db_results = $schemaManager->get_results();
|
||||
} catch (Exception $e) { $db_results['errors'][] = $e->getMessage(); }
|
||||
$install_result = ['db' => $db_results];
|
||||
|
||||
} else if ($action === 'delete') {
|
||||
check_admin_token();
|
||||
$delete_result = ['tables' => [], 'menu' => ''];
|
||||
// 💡 [수정] 삭제할 테이블 목록도 동적으로 가져옴
|
||||
$tables_to_delete = get_tables_from_sql_file(__DIR__ . '/install.sql');
|
||||
foreach ($tables_to_delete as $table) {
|
||||
sql_query("DROP TABLE IF EXISTS `{$table}`", false);
|
||||
$delete_result['tables'][] = $table;
|
||||
}
|
||||
// $menu_file = G5_ADMIN_PATH . '/admin.menu700.code_manager.php';
|
||||
$solution_files = [
|
||||
['source' => __DIR__ . '/lib/ui_manager.extend.php', 'target' => G5_EXTEND_PATH . '/ui_manager.extend.php', 'desc' => '핵심 기능 파일'],
|
||||
['source' => __DIR__ . '/admin.menu700.code_manager.php', 'target' => G5_ADMIN_PATH . '/admin.menu700.code_manager.php', 'desc' => '관리자 메뉴 파일']
|
||||
];
|
||||
foreach ($solution_files as $file) {
|
||||
$key = $file['target'];
|
||||
var_dump($key);
|
||||
if (file_exists($key)) {
|
||||
if (@unlink($key)) {
|
||||
$delete_result['menu'] = '메뉴 파일 삭제 성공';
|
||||
} else {
|
||||
$delete_result['menu'] = '메뉴 파일 삭제 실패 (권한 확인 필요)';
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (file_exists($menu_file)) {
|
||||
// if (@unlink($menu_file)) {
|
||||
// $delete_result['menu'] = '메뉴 파일 삭제 성공';
|
||||
// } else {
|
||||
// $delete_result['menu'] = '메뉴 파일 삭제 실패 (권한 확인 필요)';
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
$existing_tables = array();
|
||||
foreach ($tables_to_check as $table) {
|
||||
if (sql_query("SHOW TABLES LIKE '$table'", false) && sql_num_rows(sql_query("SHOW TABLES LIKE '$table'", false)) > 0) {
|
||||
$existing_tables[] = $table;
|
||||
}
|
||||
}
|
||||
$is_installed = count($existing_tables) == count($tables_to_check);
|
||||
?>
|
||||
|
||||
<style>
|
||||
.install-container { max-width: 800px; margin: 20px auto; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||
.install-header { text-align: center; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 2px solid #AA20FF; }
|
||||
.install-header h1 { color: #AA20FF; margin-bottom: 10px; }
|
||||
.feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin: 30px 0; }
|
||||
.feature-card { padding: 20px; border: 1px solid #e0e0e0; border-radius: 8px; text-align: center; }
|
||||
.feature-card i { font-size: 2em; color: #AA20FF; margin-bottom: 10px; }
|
||||
.status-table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
||||
.status-table th, .status-table td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
|
||||
.status-table th { background-color: #fff; font-weight: bold; }
|
||||
.status-ok { color: #28a745; font-weight: bold; }
|
||||
.status-missing { color: #dc3545; font-weight: bold; }
|
||||
.install-btn { display: block; width: 200px; margin: 30px auto; padding: 15px 30px; background: #AA20FF; color: white; text-align: center; text-decoration: none; border-radius: 5px; font-size: 16px; font-weight: bold; border: none; cursor: pointer; transition: background-color 0.3s; }
|
||||
.install-btn:hover { background: #8A1ACC; color: white; }
|
||||
.install-btn:disabled { background: #ccc; cursor: not-allowed; }
|
||||
.alert { padding: 15px; margin: 20px 0; border-radius: 5px; }
|
||||
.alert-success { background-color: #d4edda; border: 1px solid #c3e6cb; color: #155724; }
|
||||
.alert-info { background-color: #d1ecf1; border: 1px solid #bee5eb; color: #0c5460; }
|
||||
.alert-danger { background-color: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; }
|
||||
.btn-secondary { background: #6c757d; color: white; border-color: #6c757d; padding: 5px 10px; border-radius: 4px; text-decoration: none; }
|
||||
.btn-secondary:hover { background: #5a6268; }
|
||||
.btn-danger { background: #dc3545; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; }
|
||||
.btn-danger:hover { background: #c82333; }
|
||||
.button-group { display: flex; justify-content: center; align-items: center; gap: 10px; }
|
||||
</style>
|
||||
|
||||
<div class="install-container">
|
||||
<div class="install-header">
|
||||
<h1><i class="fa fa-code"></i> UI 리소스 관리 솔루션</h1>
|
||||
<p>CSS, JS 등 UI 리소스를 효율적으로 관리하는 시스템</p>
|
||||
</div>
|
||||
|
||||
<?php if ($install_result): ?>
|
||||
<div class="alert alert-success"><h4><i class="fa fa-check-circle"></i> 설치 작업 완료</h4><p>데이터베이스 설치 작업이 완료되었습니다.</p><p><a href="./ui_manager_list.php" class="btn btn-primary">UI 리소스 관리로 이동</a></p></div>
|
||||
<?php elseif ($delete_result): ?>
|
||||
<div class="alert alert-danger"><h4><i class="fa fa-trash"></i> 삭제 작업 완료</h4><p>솔루션 관련 데이터와 파일이 삭제되었습니다.</p><ul><?php foreach($delete_result['tables'] as $tbl) echo "<li>{$tbl} 테이블 삭제됨</li>"; ?><li><?php echo $delete_result['menu']; ?></li></ul></div>
|
||||
<?php elseif ($is_installed): ?>
|
||||
<div class="alert alert-success"><h4><i class="fa fa-check-circle"></i> 설치 완료</h4><p>UI 리소스 관리 솔루션이 이미 설치되어 있습니다.</p><p><a href="./ui_manager_list.php" class="btn btn-primary">UI 리소스 관리로 이동</a></p></div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-info"><h4><i class="fa fa-info-circle"></i> 설치 필요</h4><p>UI 리소스 관리 솔루션을 사용하기 위해 설치가 필요합니다.</p></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h3><i class="fa fa-database"></i> 설치 상태</h3>
|
||||
<table class="status-table">
|
||||
<thead><tr><th>테이블명</th><th>설명</th><th>상태</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($tables_to_check as $table): ?>
|
||||
<tr>
|
||||
<td><code><?php echo $table; ?></code></td>
|
||||
<td><?php echo array('g5_ui_manager' => 'UI 리소스 마스터', 'g5_form_category' => '계층형 폼 카테고리', 'g5_common_lang' => '공용 다국어 정보', 'g5_form_option_history' => '폼 옵션 변경 이력')[$table] ?? '데이터 테이블'; ?></td>
|
||||
<td>
|
||||
<?php if (in_array($table, $existing_tables)): ?>
|
||||
<span class="status-ok"><i class="fa fa-check"></i> 설치됨</span>
|
||||
<?php else: ?>
|
||||
<span class="status-missing"><i class="fa fa-times"></i> 미설치</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php if (!$is_installed): ?>
|
||||
<form method="post" onsubmit="return confirm('솔루션을 설치하시겠습니까?');">
|
||||
<input type="hidden" name="action" value="install">
|
||||
<input type="hidden" name="token" value="<?php echo get_token(); ?>">
|
||||
<button type="submit" class="install-btn"><i class="fa fa-download"></i> 솔루션 설치하기</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($is_installed && !$install_result && !$delete_result): ?>
|
||||
<div class="button-group" style="text-align: center; margin-top: 20px;">
|
||||
<form method="post" onsubmit="return confirm('기존 데이터는 유지되며, 변경된 DB 구조만 업데이트 됩니다. 진행하시겠습니까?');">
|
||||
<input type="hidden" name="action" value="install">
|
||||
<input type="hidden" name="token" value="<?php echo get_token(); ?>">
|
||||
<button type="submit" class="btn btn-secondary"><i class="fa fa-sync"></i> 재설치 (업데이트)</button>
|
||||
</form>
|
||||
<form method="post" onsubmit="return confirm('정말로 솔루션을 삭제하시겠습니까? 모든 관련 데이터와 파일이 영구적으로 삭제됩니다.');">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="token" value="<?php echo get_token(); ?>">
|
||||
<button type="submit" class="btn-danger"><i class="fa fa-trash"></i> 솔루션 삭제하기</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,64 @@
|
||||
-- 1. [핵심] UI 리소스 마스터 테이블
|
||||
-- 이 테이블은 웹사이트의 모든 UI 요소(라벨, 데이터)의 '설계도' 역할을 합니다.
|
||||
CREATE TABLE IF NOT EXISTS `g5_ui_manager` (
|
||||
`um_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '고유 ID',
|
||||
`screen_code` varchar(50) NOT NULL COMMENT '화면 코드 (e.g. order_form)',
|
||||
`group_code` varchar(50) NOT NULL COMMENT '화면 내 그룹 코드 (e.g. address_info)',
|
||||
`resource_code` varchar(50) NOT NULL COMMENT '리소스 코드 (개발자가 사용)',
|
||||
`resource_type` enum('LABEL','DATA') NOT NULL COMMENT '리소스 타입 (라벨, 데이터)',
|
||||
`resource_desc` varchar(255) DEFAULT NULL COMMENT '리소스에 대한 설명 (관리자용)',
|
||||
`is_used` tinyint(1) NOT NULL DEFAULT '1' COMMENT '사용 여부',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일',
|
||||
`created_by` varchar(20) NOT NULL COMMENT '생성자',
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '수정일',
|
||||
`updated_by` varchar(20) NOT NULL COMMENT '수정자',
|
||||
PRIMARY KEY (`um_id`),
|
||||
UNIQUE KEY `resource_identifier` (`screen_code`,`group_code`,`resource_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='[솔루션] UI 리소스 관리자';
|
||||
|
||||
-- 2. 계층형 카테고리 테이블 ('DATA' 타입 리소스가 사용할 데이터)
|
||||
-- um_id를 통해 어떤 리소스에 속한 데이터인지 명시합니다.
|
||||
CREATE TABLE IF NOT EXISTS `g5_form_category` (
|
||||
`fc_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '카테고리 고유 ID',
|
||||
`um_id` int(11) NOT NULL COMMENT 'UI 리소스 ID (g5_ui_manager.um_id)',
|
||||
`parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '부모 카테고리 ID (0이면 최상위)',
|
||||
`fc_key` varchar(255) NOT NULL COMMENT 'DB에 저장될 값 (고유값)',
|
||||
`fc_order` int(11) NOT NULL DEFAULT '0' COMMENT '정렬 순서',
|
||||
`is_used` tinyint(1) NOT NULL DEFAULT '1' COMMENT '사용 여부',
|
||||
`is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '삭제 여부',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일',
|
||||
`created_by` varchar(20) NOT NULL COMMENT '생성자',
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '수정일',
|
||||
`updated_by` varchar(20) NOT NULL COMMENT '수정자',
|
||||
PRIMARY KEY (`fc_id`),
|
||||
KEY `um_id` (`um_id`),
|
||||
KEY `parent_id` (`parent_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='[솔루션] 계층형 폼 카테고리';
|
||||
|
||||
-- 3. 공용 다국어 정보 테이블
|
||||
-- 'LABEL' 타입의 실제 텍스트와 'DATA' 타입의 카테고리 이름을 모두 저장합니다.
|
||||
CREATE TABLE IF NOT EXISTS `g5_common_lang` (
|
||||
`cl_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '고유 ID',
|
||||
`target_table` varchar(50) NOT NULL COMMENT '대상 테이블명',
|
||||
`target_id` int(11) NOT NULL COMMENT '대상 레코드 ID',
|
||||
`lang_code` varchar(10) NOT NULL COMMENT '언어 코드',
|
||||
`cl_name` varchar(255) NOT NULL COMMENT '화면에 표시될 이름/값',
|
||||
`cl_description` text COMMENT '부가 설명 (툴팁 등)',
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '수정일',
|
||||
`updated_by` varchar(20) NOT NULL COMMENT '수정자',
|
||||
PRIMARY KEY (`cl_id`),
|
||||
UNIQUE KEY `target_lang` (`target_table`,`target_id`,`lang_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='[솔루션] 공용 다국어 정보';
|
||||
|
||||
-- 4. 변경 이력 테이블
|
||||
CREATE TABLE IF NOT EXISTS `g5_form_option_history` (
|
||||
`fh_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '고유 ID',
|
||||
`table_name` varchar(50) NOT NULL COMMENT '변경된 테이블명',
|
||||
`record_id` int(11) NOT NULL COMMENT '변경된 레코드 ID',
|
||||
`action_type` varchar(10) NOT NULL COMMENT '작업 종류',
|
||||
`change_data` longtext COMMENT '변경된 데이터 (JSON)',
|
||||
`changed_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '변경일',
|
||||
`changed_by` varchar(20) NOT NULL COMMENT '변경자',
|
||||
PRIMARY KEY (`fh_id`),
|
||||
KEY `table_name_record_id` (`table_name`,`record_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='[솔루션] 폼 옵션 변경 이력';
|
||||
@@ -0,0 +1,90 @@
|
||||
-- 1. 마스터 테이블: 옵션 그룹 (예: '집 유형', '창호 색상')
|
||||
CREATE TABLE IF NOT EXISTS `g5_form_group` (
|
||||
`fg_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '고유 ID',
|
||||
`project_code` varchar(50) NOT NULL DEFAULT 'default' COMMENT '프로젝트 코드',
|
||||
`site_code` varchar(50) NOT NULL DEFAULT 'default' COMMENT '사이트 코드',
|
||||
`fg_code` varchar(50) NOT NULL COMMENT '그룹 코드 (프로그램에서 사용)',
|
||||
`fg_order` int(11) NOT NULL DEFAULT '0' COMMENT '정렬 순서',
|
||||
`is_used` tinyint(1) NOT NULL DEFAULT '1' COMMENT '사용 여부 (1:사용, 0:미사용)',
|
||||
`is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '삭제 여부 (1:삭제, 0:정상)',
|
||||
`created_at` datetime NOT NULL COMMENT '생성일',
|
||||
`created_by` varchar(20) NOT NULL COMMENT '생성자',
|
||||
`updated_at` datetime NOT NULL COMMENT '수정일',
|
||||
`updated_by` varchar(20) NOT NULL COMMENT '수정자',
|
||||
`fg_temp_1` varchar(255) DEFAULT NULL COMMENT '임시 필드 1',
|
||||
`fg_temp_2` varchar(255) DEFAULT NULL COMMENT '임시 필드 2',
|
||||
`fg_temp_3` varchar(255) DEFAULT NULL COMMENT '임시 필드 3',
|
||||
`fg_temp_4` text DEFAULT NULL COMMENT '임시 필드 4',
|
||||
`fg_temp_5` text DEFAULT NULL COMMENT '임시 필드 5',
|
||||
`fg_extra_1` varchar(255) DEFAULT NULL COMMENT '여분 필드 1',
|
||||
`fg_extra_2` varchar(255) DEFAULT NULL COMMENT '여분 필드 2',
|
||||
`fg_extra_3` varchar(255) DEFAULT NULL COMMENT '여분 필드 3',
|
||||
`fg_extra_4` text DEFAULT NULL COMMENT '여분 필드 4',
|
||||
`fg_extra_5` text DEFAULT NULL COMMENT '여분 필드 5',
|
||||
PRIMARY KEY (`fg_id`),
|
||||
UNIQUE KEY `project_site_fg_code` (`project_code`,`site_code`,`fg_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='[솔루션] 폼 옵션 그룹';
|
||||
|
||||
-- 2. 디테일 테이블: 그룹에 속한 개별 옵션
|
||||
CREATE TABLE IF NOT EXISTS `g5_form_option` (
|
||||
`fo_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '고유 ID',
|
||||
`fg_id` int(11) NOT NULL COMMENT '그룹 ID (g5_form_group.fg_id)',
|
||||
`fo_key` varchar(255) NOT NULL COMMENT 'DB에 저장될 값 (고유값)',
|
||||
`fo_order` int(11) NOT NULL DEFAULT '0' COMMENT '정렬 순서',
|
||||
`is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '기본 선택 여부 (1:기본값)',
|
||||
`is_used` tinyint(1) NOT NULL DEFAULT '1' COMMENT '사용 여부 (1:사용, 0:미사용)',
|
||||
`is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '삭제 여부 (1:삭제, 0:정상)',
|
||||
`created_at` datetime NOT NULL COMMENT '생성일',
|
||||
`created_by` varchar(20) NOT NULL COMMENT '생성자',
|
||||
`updated_at` datetime NOT NULL COMMENT '수정일',
|
||||
`updated_by` varchar(20) NOT NULL COMMENT '수정자',
|
||||
`fo_temp_1` varchar(255) DEFAULT NULL COMMENT '임시 필드 1',
|
||||
`fo_temp_2` varchar(255) DEFAULT NULL COMMENT '임시 필드 2',
|
||||
`fo_temp_3` varchar(255) DEFAULT NULL COMMENT '임시 필드 3',
|
||||
`fo_temp_4` text DEFAULT NULL COMMENT '임시 필드 4',
|
||||
`fo_temp_5` text DEFAULT NULL COMMENT '임시 필드 5',
|
||||
`fo_extra_1` varchar(255) DEFAULT NULL COMMENT '여분 필드 1',
|
||||
`fo_extra_2` varchar(255) DEFAULT NULL COMMENT '여분 필드 2',
|
||||
`fo_extra_3` varchar(255) DEFAULT NULL COMMENT '여분 필드 3',
|
||||
`fo_extra_4` text DEFAULT NULL COMMENT '여분 필드 4',
|
||||
`fo_extra_5` text DEFAULT NULL COMMENT '여분 필드 5',
|
||||
PRIMARY KEY (`fo_id`),
|
||||
KEY `fg_id` (`fg_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='[솔루션] 폼 옵션 항목';
|
||||
|
||||
-- 3. [통합] 서브 디테일 테이블: 그룹과 옵션의 다국어 이름/설명
|
||||
CREATE TABLE IF NOT EXISTS `g5_common_lang` (
|
||||
`cl_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '고유 ID',
|
||||
`target_table` varchar(50) NOT NULL COMMENT '대상 테이블명 (예: g5_form_group)',
|
||||
`target_id` int(11) NOT NULL COMMENT '대상 레코드 ID',
|
||||
`lang_code` varchar(10) NOT NULL COMMENT '언어 코드 (ko, en, ja)',
|
||||
`cl_name` varchar(255) NOT NULL COMMENT '화면에 표시될 이름/값',
|
||||
`cl_description` text COMMENT '부가 설명 (툴팁 등)',
|
||||
`updated_at` datetime NOT NULL COMMENT '수정일',
|
||||
`updated_by` varchar(20) NOT NULL COMMENT '수정자',
|
||||
`cl_temp_1` varchar(255) DEFAULT NULL COMMENT '임시 필드 1',
|
||||
`cl_temp_2` varchar(255) DEFAULT NULL COMMENT '임시 필드 2',
|
||||
`cl_temp_3` varchar(255) DEFAULT NULL COMMENT '임시 필드 3',
|
||||
`cl_temp_4` text DEFAULT NULL COMMENT '임시 필드 4',
|
||||
`cl_temp_5` text DEFAULT NULL COMMENT '임시 필드 5',
|
||||
`cl_extra_1` varchar(255) DEFAULT NULL COMMENT '여분 필드 1',
|
||||
`cl_extra_2` varchar(255) DEFAULT NULL COMMENT '여분 필드 2',
|
||||
`cl_extra_3` varchar(255) DEFAULT NULL COMMENT '여분 필드 3',
|
||||
`cl_extra_4` text DEFAULT NULL COMMENT '여분 필드 4',
|
||||
`cl_extra_5` text DEFAULT NULL COMMENT '여분 필드 5',
|
||||
PRIMARY KEY (`cl_id`),
|
||||
UNIQUE KEY `target_lang` (`target_table`,`target_id`,`lang_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='[솔루션] 공용 다국어 정보';
|
||||
|
||||
-- 4. 변경 이력 테이블
|
||||
CREATE TABLE IF NOT EXISTS `g5_form_option_history` (
|
||||
`fh_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '고유 ID',
|
||||
`table_name` varchar(50) NOT NULL COMMENT '변경된 테이블명',
|
||||
`record_id` int(11) NOT NULL COMMENT '변경된 레코드 ID',
|
||||
`action_type` varchar(10) NOT NULL COMMENT '작업 종류 (INSERT, UPDATE, DELETE)',
|
||||
`change_data` longtext COMMENT '변경된 데이터 (JSON)',
|
||||
`changed_at` datetime NOT NULL COMMENT '변경일',
|
||||
`changed_by` varchar(20) NOT NULL COMMENT '변경자',
|
||||
PRIMARY KEY (`fh_id`),
|
||||
KEY `table_name_record_id` (`table_name`,`record_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='[솔루션] 폼 옵션 변경 이력';
|
||||
@@ -0,0 +1,41 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const addResourceButton = document.getElementById('add-resource-btn');
|
||||
const resourceFormContainer = document.getElementById('resource-form-container');
|
||||
const cancelResourceButton = document.getElementById('cancel-resource-btn');
|
||||
const resourceTypeRadios = document.querySelectorAll('input[name="resource_type"]');
|
||||
const labelField = document.getElementById('label-field');
|
||||
|
||||
// '새 리소스 추가' 버튼 클릭 이벤트
|
||||
if (addResourceButton) {
|
||||
addResourceButton.addEventListener('click', function() {
|
||||
if (resourceFormContainer) resourceFormContainer.style.display = 'block';
|
||||
this.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// '취소' 버튼 클릭 이벤트
|
||||
if (cancelResourceButton) {
|
||||
cancelResourceButton.addEventListener('click', function() {
|
||||
if (resourceFormContainer) resourceFormContainer.style.display = 'none';
|
||||
if (addResourceButton) addResourceButton.style.display = 'inline-block';
|
||||
});
|
||||
}
|
||||
|
||||
// 💡 [핵심] 리소스 타입 라디오 버튼 변경 이벤트
|
||||
function toggleResourceTypeFields() {
|
||||
const selectedType = document.querySelector('input[name="resource_type"]:checked').value;
|
||||
if (selectedType === 'LABEL') {
|
||||
labelField.style.display = ''; // 테이블 행이므로 기본값으로
|
||||
} else {
|
||||
labelField.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceTypeRadios.length > 0) {
|
||||
resourceTypeRadios.forEach(radio => {
|
||||
radio.addEventListener('change', toggleResourceTypeFields);
|
||||
});
|
||||
// 페이지 로드 시 초기 상태 설정
|
||||
toggleResourceTypeFields();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// =================================================================
|
||||
// 1. UI 리소스 목록 페이지 (ui_manager_list.php)
|
||||
// =================================================================
|
||||
const addResourceButton = document.getElementById('add-resource-btn');
|
||||
const resourceFormContainer = document.getElementById('resource-form-container');
|
||||
const cancelResourceButton = document.getElementById('cancel-resource-btn');
|
||||
const resourceTypeRadios = document.querySelectorAll('input[name="resource_type"]');
|
||||
const labelField = document.getElementById('label-field');
|
||||
const accordionItems = document.querySelectorAll('.accordion-item');
|
||||
const searchForm = document.getElementById('fsearch');
|
||||
const pageRowsSelect = document.getElementById('page_rows');
|
||||
|
||||
if (addResourceButton) {
|
||||
addResourceButton.addEventListener('click', function() {
|
||||
if (resourceFormContainer) resourceFormContainer.style.display = 'block';
|
||||
this.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
if (cancelResourceButton) {
|
||||
cancelResourceButton.addEventListener('click', function() {
|
||||
if (resourceFormContainer) resourceFormContainer.style.display = 'none';
|
||||
if (addResourceButton) addResourceButton.style.display = 'inline-block';
|
||||
});
|
||||
}
|
||||
|
||||
function toggleResourceTypeFields() {
|
||||
if (!document.querySelector('input[name="resource_type"]:checked')) return;
|
||||
const selectedType = document.querySelector('input[name="resource_type"]:checked').value;
|
||||
if (labelField) {
|
||||
labelField.style.display = (selectedType === 'LABEL') ? '' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceTypeRadios.length > 0) {
|
||||
resourceTypeRadios.forEach(radio => radio.addEventListener('change', toggleResourceTypeFields));
|
||||
toggleResourceTypeFields();
|
||||
}
|
||||
|
||||
if (accordionItems.length > 0) {
|
||||
accordionItems.forEach(item => {
|
||||
const header = item.querySelector('.accordion-header');
|
||||
const content = item.querySelector('.accordion-content');
|
||||
if (header && content) {
|
||||
header.addEventListener('click', () => {
|
||||
item.classList.toggle('active');
|
||||
content.style.display = item.classList.contains('active') ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (searchForm) {
|
||||
searchForm.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Enter') {
|
||||
// 엔터키 입력 시 기본 동작(폼 제출)을 막지 않음
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (pageRowsSelect) {
|
||||
pageRowsSelect.addEventListener('change', function() {
|
||||
if (searchForm) {
|
||||
searchForm.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 💡 [핵심] 옵션/카테고리 관리 페이지 (category_list.php) 로직 추가
|
||||
// =================================================================
|
||||
const addCategoryButton = document.getElementById('add-category-btn');
|
||||
const categoryFormContainer = document.getElementById('category-form-container');
|
||||
const cancelCategoryButton = document.getElementById('cancel-category-btn');
|
||||
const categoryForm = document.getElementById('fcategoryform');
|
||||
const editCategoryButtons = document.querySelectorAll('.btn_edit_category');
|
||||
|
||||
// "새 옵션 추가" 버튼 클릭
|
||||
if (addCategoryButton && categoryFormContainer) {
|
||||
addCategoryButton.addEventListener('click', function() {
|
||||
if (categoryForm) {
|
||||
categoryForm.reset();
|
||||
categoryForm.w.value = ''; // 신규 등록 모드
|
||||
categoryForm.fc_id.value = '';
|
||||
const submitButton = categoryForm.querySelector('input[type="submit"]');
|
||||
if (submitButton) submitButton.value = '저장';
|
||||
}
|
||||
categoryFormContainer.style.display = 'block';
|
||||
this.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// "취소" 버튼 클릭
|
||||
if (cancelCategoryButton && categoryFormContainer && addCategoryButton) {
|
||||
cancelCategoryButton.addEventListener('click', function() {
|
||||
categoryFormContainer.style.display = 'none';
|
||||
addCategoryButton.style.display = 'inline-block';
|
||||
});
|
||||
}
|
||||
|
||||
// "수정" 버튼 클릭
|
||||
if (editCategoryButtons.length > 0 && categoryForm) {
|
||||
editCategoryButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const data = this.dataset;
|
||||
|
||||
// 폼에 데이터 채우기
|
||||
categoryForm.w.value = 'u'; // 수정 모드
|
||||
categoryForm.fc_id.value = data.fc_id;
|
||||
categoryForm.parent_id.value = data.parent_id;
|
||||
categoryForm.fc_key.value = data.fc_key;
|
||||
categoryForm.cl_name.value = data.cl_name;
|
||||
categoryForm.fc_order.value = data.fc_order;
|
||||
|
||||
const isUsedRadio = categoryForm.querySelector(`input[name="is_used"][value="${data.is_used}"]`);
|
||||
if (isUsedRadio) isUsedRadio.checked = true;
|
||||
|
||||
const submitButton = categoryForm.querySelector('input[type="submit"]');
|
||||
if (submitButton) submitButton.value = '수정';
|
||||
|
||||
if (categoryFormContainer) categoryFormContainer.style.display = 'block';
|
||||
if (addCategoryButton) addCategoryButton.style.display = 'none';
|
||||
|
||||
categoryFormContainer.scrollIntoView({ behavior: 'smooth' });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 3. 리소스 삭제 기능
|
||||
// =================================================================
|
||||
const deleteResourceButtons = document.querySelectorAll('.btn_delete_resource');
|
||||
|
||||
if (deleteResourceButtons.length > 0) {
|
||||
deleteResourceButtons.forEach(button => {
|
||||
button.addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const um_id = this.dataset.um_id;
|
||||
const resource_desc_element = this.closest('tr').querySelector('.td_left');
|
||||
const resource_desc = resource_desc_element ? resource_desc_element.textContent.trim() : `ID: ${um_id}`;
|
||||
|
||||
if (confirm(`'${resource_desc}' 리소스를 정말 삭제하시겠습니까?\n\n이 리소스와 관련된 모든 하위 옵션(카테고리) 및 언어 데이터가 함께 영구적으로 삭제됩니다.`)) {
|
||||
const qstr = new URLSearchParams(window.location.search).toString();
|
||||
location.href = `./ui_manager_list.php?mode=delete&um_id=${um_id}&token=${g5_admin_token}&${qstr}`;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
$sub_menu = '700100';
|
||||
include_once('./_common.php');
|
||||
|
||||
// ... (폼 제출 처리 로직은 변경 없음) ...
|
||||
$target_table = isset($_REQUEST['target_table']) ? preg_replace('/[^a-z0-9_]/i', '', $_REQUEST['target_table']) : '';
|
||||
$target_id = isset($_REQUEST['target_id']) ? (int)$_REQUEST['target_id'] : 0;
|
||||
|
||||
if (!$target_table || !$target_id) {
|
||||
alert('잘못된 접근입니다.');
|
||||
}
|
||||
|
||||
if (isset($w) && $w == 'u' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
check_admin_token();
|
||||
$lang_code = trim($_POST['lang_code']);
|
||||
$cl_name = trim($_POST['cl_name']);
|
||||
$cl_description = trim($_POST['cl_description']);
|
||||
if (!$lang_code) alert('언어 코드를 선택해주세요.');
|
||||
if (!$cl_name) alert('이름/값을 입력해주세요.');
|
||||
$sql = "INSERT INTO {$g5['common_lang_table']}
|
||||
SET target_table = '{$target_table}',
|
||||
target_id = '{$target_id}',
|
||||
lang_code = '{$lang_code}',
|
||||
cl_name = '{$cl_name}',
|
||||
cl_description = '{$cl_description}',
|
||||
updated_at = '".G5_TIME_YMDHIS."',
|
||||
updated_by = '{$member['mb_id']}'
|
||||
ON DUPLICATE KEY UPDATE
|
||||
cl_name = '{$cl_name}',
|
||||
cl_description = '{$cl_description}',
|
||||
updated_at = '".G5_TIME_YMDHIS."',
|
||||
updated_by = '{$member['mb_id']}'";
|
||||
sql_query($sql);
|
||||
goto_url("./lang_manager.php?target_table=$target_table&target_id=$target_id");
|
||||
}
|
||||
if (isset($mode) && $mode == 'delete') {
|
||||
auth_check_menu($auth, $sub_menu, 'd');
|
||||
check_admin_token();
|
||||
$cl_id = isset($_GET['cl_id']) ? (int)$_GET['cl_id'] : 0;
|
||||
if (!$cl_id) alert('cl_id 값이 없습니다.');
|
||||
$sql = "DELETE FROM {$g5['common_lang_table']} WHERE cl_id = '{$cl_id}' AND target_table = '{$target_table}' AND target_id = '{$target_id}'";
|
||||
sql_query($sql);
|
||||
goto_url("./lang_manager.php?target_table=$target_table&target_id=$target_id");
|
||||
}
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'r');
|
||||
|
||||
// 💡 [핵심 추가] 이전 페이지로 돌아가기 위한 링크 생성
|
||||
$back_link = './ui_manager_list.php'; // 기본 돌아가기 링크
|
||||
if ($target_table == $g5['form_category_table']) {
|
||||
// 카테고리 다국어 관리였다면, 해당 카테고리가 속한 리소스의 옵션 관리 페이지로 돌아가야 함
|
||||
$sql_back = "SELECT um_id FROM {$g5['form_category_table']} WHERE fc_id = '{$target_id}'";
|
||||
$back_row = sql_fetch($sql_back);
|
||||
if (isset($back_row['um_id'])) {
|
||||
$back_link = './category_list.php?um_id=' . $back_row['um_id'];
|
||||
}
|
||||
}
|
||||
|
||||
// UiManager 클래스를 사용하여 사용 가능한 언어 목록 가져오기
|
||||
$active_languages = ui_manager()->get_data('language_list');
|
||||
|
||||
// 현재 관리 대상의 기본 정보(한국어)를 가져옴
|
||||
$sql = "SELECT cl_name FROM {$g5['common_lang_table']} WHERE target_table = '{$target_table}' AND target_id = '{$target_id}' AND lang_code = 'ko'";
|
||||
$parent_info = sql_fetch($sql);
|
||||
$parent_name = isset($parent_info['cl_name']) ? get_text($parent_info['cl_name']) : "ID: {$target_id}";
|
||||
|
||||
// 등록된 다국어 목록 조회
|
||||
$sql = "SELECT * FROM {$g5['common_lang_table']} WHERE target_table = '{$target_table}' AND target_id = '{$target_id}' ORDER BY lang_code";
|
||||
$result = sql_query($sql);
|
||||
$lang_list = [];
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$lang_list[] = $row;
|
||||
}
|
||||
$lang_list_count = count($lang_list);
|
||||
|
||||
$g5['title'] = '다국어 관리';
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
|
||||
add_stylesheet('<link rel="stylesheet" href="' . G5_ADMIN_URL . '/code_manager/css/code_manager.css?ver=1.1">', 0);
|
||||
?>
|
||||
|
||||
<div class="local_desc01 local_desc">
|
||||
<p>
|
||||
<strong>'<?php echo $parent_name; ?>'</strong> 항목에 대한 다국어 이름과 설명을 관리합니다.<br>
|
||||
'언어 코드'는 중복하여 등록할 수 없습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section id="code_manager">
|
||||
<h2 class="h2_frm">다국어 등록/수정</h2>
|
||||
<form name="flangform" id="flangform" action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="post">
|
||||
<input type="hidden" name="w" value="u">
|
||||
<input type="hidden" name="target_table" value="<?php echo $target_table; ?>">
|
||||
<input type="hidden" name="target_id" value="<?php echo $target_id; ?>">
|
||||
<input type="hidden" name="token" value="<?php echo get_admin_token(); ?>">
|
||||
|
||||
<div class="tbl_frm01 tbl_wrap">
|
||||
<table>
|
||||
<caption>다국어 정보 추가/수정 폼</caption>
|
||||
<colgroup>
|
||||
<col class="grid_4">
|
||||
<col>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row"><label for="lang_code">언어 코드</label></th>
|
||||
<td>
|
||||
<select name="lang_code" id="lang_code" required>
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($active_languages as $lang_item) : ?>
|
||||
<option value="<?php echo $lang_item['fc_key']; ?>"><?php echo get_text($lang_item['cl_name']); ?> (<?php echo $lang_item['fc_key']; ?>)</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="cl_name">이름/값</label></th>
|
||||
<td>
|
||||
<input type="text" name="cl_name" id="cl_name" required class="required frm_input" size="80">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="cl_description">부가 설명</label></th>
|
||||
<td>
|
||||
<textarea name="cl_description" id="cl_description" rows="5"></textarea>
|
||||
<span class="frm_info">툴팁 등 부가적으로 사용될 설명을 입력합니다. (선택사항)</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="btn_confirm01 btn_confirm">
|
||||
<!-- 💡 [핵심 수정] 목록으로 돌아가는 버튼 추가 -->
|
||||
<a href="<?php echo $back_link; ?>" class="btn_cancel btn">목록으로</a>
|
||||
<input type="submit" value="저장" class="btn_submit btn" accesskey="s">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- 등록된 다국어 목록 테이블 -->
|
||||
<div class="tbl_head01 tbl_wrap" style="margin-top: 20px;">
|
||||
<h2 class="h2_frm">등록된 다국어 목록</h2>
|
||||
<table>
|
||||
<caption>등록된 다국어 목록</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" style="width: 100px;">언어 코드</th>
|
||||
<th scope="col">이름/값</th>
|
||||
<th scope="col">부가 설명</th>
|
||||
<th scope="col" style="width: 120px;">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($lang_list_count > 0) : ?>
|
||||
<?php foreach ($lang_list as $lang) : ?>
|
||||
<tr>
|
||||
<td class="td_alignc"><?php echo get_text($lang['lang_code']); ?></td>
|
||||
<td class="td_left"><?php echo get_text($lang['cl_name']); ?></td>
|
||||
<td class="td_left"><?php echo get_text($lang['cl_description']); ?></td>
|
||||
<td class="td_mng">
|
||||
<button type="button" class="btn btn_02 btn_edit_lang"
|
||||
data-lang_code="<?php echo get_text($lang['lang_code']); ?>"
|
||||
data-cl_name="<?php echo get_text($lang['cl_name']); ?>"
|
||||
data-cl_description="<?php echo get_text($lang['cl_description']); ?>">수정</button>
|
||||
<a href="./lang_manager.php?mode=delete&cl_id=<?php echo $lang['cl_id']; ?>&target_table=<?php echo $target_table; ?>&target_id=<?php echo $target_id; ?>&token=<?php echo get_admin_token(); ?>"
|
||||
class="btn btn_delete" onclick="return confirm('정말 삭제하시겠습니까?');">삭제</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<tr class="empty_table">
|
||||
<td colspan="4">등록된 다국어 정보가 없습니다.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const editLangButtons = document.querySelectorAll('.btn_edit_lang');
|
||||
const langForm = document.getElementById('flangform');
|
||||
if (editLangButtons.length > 0 && langForm) {
|
||||
editLangButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const data = this.dataset;
|
||||
langForm.lang_code.value = data.lang_code;
|
||||
langForm.cl_name.value = data.cl_name;
|
||||
langForm.cl_description.value = data.cl_description;
|
||||
langForm.scrollIntoView({ behavior: 'smooth' });
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
/**
|
||||
* SQL 파일을 기반으로 데이터베이스 스키마를 관리(생성/업데이트)하는 범용 클래스
|
||||
*/
|
||||
class SchemaManager
|
||||
{
|
||||
private $sql_file_path;
|
||||
private $results;
|
||||
|
||||
/**
|
||||
* 생성자
|
||||
* @param string $sql_file_path install.sql 파일의 절대 경로
|
||||
*/
|
||||
public function __construct($sql_file_path)
|
||||
{
|
||||
if (!file_exists($sql_file_path)) {
|
||||
throw new Exception($sql_file_path . ' 파일을 찾을 수 없습니다.');
|
||||
}
|
||||
$this->sql_file_path = $sql_file_path;
|
||||
$this->results = [
|
||||
'created' => [],
|
||||
'existing' => [],
|
||||
'updated' => [],
|
||||
'failed' => [],
|
||||
'errors' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 스키마 설치/업데이트를 실행합니다.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$sql_statements = $this->parse_sql_file();
|
||||
|
||||
foreach ($sql_statements as $stmt) {
|
||||
// CREATE TABLE 문인지 확인
|
||||
if (preg_match('/^CREATE\s+TABLE/i', $stmt)) {
|
||||
$schema = $this->parse_create_table_sql($stmt);
|
||||
if ($schema && !empty($schema['name'])) {
|
||||
$this->process_table_schema($stmt, $schema);
|
||||
}
|
||||
} else {
|
||||
// CREATE TABLE 문이 아닌 다른 SQL 문 (e.g. INSERT, UPDATE)
|
||||
sql_query($stmt, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 처리 결과를 반환합니다.
|
||||
* @return array
|
||||
*/
|
||||
public function get_results()
|
||||
{
|
||||
return $this->results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 스키마를 처리합니다. (생성 또는 업데이트)
|
||||
* @param string $create_sql 전체 CREATE TABLE 구문
|
||||
* @param array $schema 파싱된 스키마 정보
|
||||
*/
|
||||
private function process_table_schema($create_sql, $schema)
|
||||
{
|
||||
$table_name = $schema['name'];
|
||||
|
||||
if ($this->table_exists($table_name)) {
|
||||
// 테이블이 존재하면, 컬럼 비교 및 추가
|
||||
$this->results['existing'][] = $table_name;
|
||||
$this->update_table_columns($table_name, $schema['columns']);
|
||||
} else {
|
||||
// 테이블이 존재하지 않으면, 새로 생성
|
||||
if (sql_query($create_sql, false)) {
|
||||
$this->results['created'][] = $table_name;
|
||||
} else {
|
||||
$this->results['failed'][] = $table_name;
|
||||
$this->results['errors'][] = "<strong>{$table_name} 테이블 생성 실패</strong>: " . sql_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블의 컬럼 구조를 업데이트합니다.
|
||||
* @param string $table_name
|
||||
* @param array $target_columns .sql 파일에 정의된 컬럼 목록
|
||||
*/
|
||||
private function update_table_columns($table_name, $target_columns)
|
||||
{
|
||||
$current_columns = $this->get_current_columns($table_name);
|
||||
$added_columns_in_table = [];
|
||||
|
||||
foreach ($target_columns as $col_name => $col_definition) {
|
||||
// 현재 테이블에 해당 컬럼이 없으면 추가
|
||||
if (!isset($current_columns[$col_name])) {
|
||||
$alter_sql = "ALTER TABLE `{$table_name}` ADD COLUMN `{$col_name}` {$col_definition}";
|
||||
if (sql_query($alter_sql, false)) {
|
||||
$added_columns_in_table[] = $col_name;
|
||||
} else {
|
||||
$this->results['failed'][] = "{$table_name} (컬럼: {$col_name})";
|
||||
$this->results['errors'][] = "<strong>{$table_name} 테이블에 '{$col_name}' 컬럼 추가 실패</strong>: " . sql_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($added_columns_in_table)) {
|
||||
$this->results['updated'][$table_name] = $added_columns_in_table;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL 파일을 읽고 각 구문으로 분리합니다.
|
||||
* @return array
|
||||
*/
|
||||
private function parse_sql_file()
|
||||
{
|
||||
$sql = file_get_contents($this->sql_file_path);
|
||||
// 주석 제거 (SQL 주석 '--' 와 C-style '/* ... */' 주석)
|
||||
$sql = preg_replace('/--.*/', '', $sql);
|
||||
$sql = preg_replace('!/\*.*?\*/!s', '', $sql);
|
||||
$sql = trim($sql);
|
||||
|
||||
// 세미콜론(;)을 기준으로 쿼리 분리
|
||||
return array_filter(array_map('trim', explode(';', $sql)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 존재 여부를 확인합니다.
|
||||
* @param string $table_name
|
||||
* @return bool
|
||||
*/
|
||||
private function table_exists($table_name)
|
||||
{
|
||||
$res = sql_query("SHOW TABLES LIKE '{$table_name}'", false);
|
||||
return sql_num_rows($res) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 DB에 있는 테이블의 컬럼 목록을 가져옵니다.
|
||||
* @param string $table_name
|
||||
* @return array
|
||||
*/
|
||||
private function get_current_columns($table_name)
|
||||
{
|
||||
$res = sql_query("SHOW COLUMNS FROM `{$table_name}`", false);
|
||||
$columns = [];
|
||||
while ($row = sql_fetch_array($res)) {
|
||||
$columns[$row['Field']] = true;
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE TABLE SQL 구문에서 테이블명과 컬럼 정의를 파싱합니다.
|
||||
* @param string $query CREATE TABLE 구문
|
||||
* @return array|null
|
||||
*/
|
||||
private function parse_create_table_sql($query)
|
||||
{
|
||||
$table_name = '';
|
||||
if (preg_match('/CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+`?(\w+)`?/i', $query, $matches)) {
|
||||
$table_name = $matches[1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 괄호 안의 내용만 추출
|
||||
$start = strpos($query, '(');
|
||||
$end = strrpos($query, ')');
|
||||
if ($start === false || $end === false) {
|
||||
return ['name' => $table_name, 'columns' => []];
|
||||
}
|
||||
$content = substr($query, $start + 1, $end - $start - 1);
|
||||
|
||||
// 줄 단위로 분리
|
||||
$lines = explode("\n", $content);
|
||||
|
||||
$columns = [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line, " \t\n\r\0\x0B,"); // 양쪽 공백과 마지막 쉼표 제거
|
||||
|
||||
// 컬럼 정의 라인인지 확인 (첫 단어가 `column_name` 형태)
|
||||
if (preg_match('/^`(\w+)`\s+(.*)/i', $line, $match)) {
|
||||
$col_name = $match[1];
|
||||
$col_definition = $match[2];
|
||||
$columns[$col_name] = $col_definition;
|
||||
}
|
||||
}
|
||||
|
||||
return ['name' => $table_name, 'columns' => $columns];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
/**
|
||||
* UI 리소스 관리 클래스
|
||||
* Singleton 패턴을 사용하여 인스턴스를 한 번만 생성하고,
|
||||
* 불러온 데이터를 캐시하여 DB 조회를 최소화합니다.
|
||||
*/
|
||||
class UiManager
|
||||
{
|
||||
private static $instance = null;
|
||||
private $resources = []; // 데이터를 캐시할 배열
|
||||
|
||||
// 외부에서 new 키워드로 인스턴스 생성을 막음
|
||||
private function __construct() {}
|
||||
|
||||
/**
|
||||
* 클래스의 유일한 인스턴스를 반환합니다.
|
||||
* @return UiManager
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'LABEL' 타입의 UI 텍스트를 가져옵니다.
|
||||
* @param string $resource_code 리소스 코드
|
||||
* @param string $lang 언어 코드 (기본값: 'ko')
|
||||
* @return string 라벨 텍스트 (없으면 resource_code 반환)
|
||||
*/
|
||||
public function get_label($resource_code, $lang = 'ko')
|
||||
{
|
||||
// 캐시 확인
|
||||
if (isset($this->resources['labels'][$lang][$resource_code])) {
|
||||
return $this->resources['labels'][$lang][$resource_code];
|
||||
}
|
||||
|
||||
global $g5;
|
||||
$resource_code_escaped = sql_real_escape_string($resource_code);
|
||||
$lang_escaped = sql_real_escape_string($lang);
|
||||
|
||||
$sql = "SELECT B.cl_name
|
||||
FROM {$g5['ui_manager_table']} AS A
|
||||
LEFT JOIN {$g5['common_lang_table']} AS B
|
||||
ON (A.um_id = B.target_id AND B.target_table = '{$g5['ui_manager_table']}' AND B.lang_code = '{$lang_escaped}')
|
||||
WHERE A.resource_code = '{$resource_code_escaped}' AND A.resource_type = 'LABEL'";
|
||||
$row = sql_fetch($sql);
|
||||
|
||||
$label_text = $row['cl_name'] ?? $resource_code;
|
||||
|
||||
// 결과 캐시
|
||||
$this->resources['labels'][$lang][$resource_code] = $label_text;
|
||||
|
||||
return $label_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'DATA' 타입의 옵션 목록을 배열로 가져옵니다.
|
||||
* @param string $resource_code 리소스 코드
|
||||
* @param string $lang 언어 코드 (기본값: 'ko')
|
||||
* @return array 옵션 목록 배열
|
||||
*/
|
||||
public function get_data($resource_code, $lang = 'ko')
|
||||
{
|
||||
// 캐시 확인
|
||||
if (isset($this->resources['data'][$lang][$resource_code])) {
|
||||
return $this->resources['data'][$lang][$resource_code];
|
||||
}
|
||||
|
||||
global $g5;
|
||||
$resource_code_escaped = sql_real_escape_string($resource_code);
|
||||
$lang_escaped = sql_real_escape_string($lang);
|
||||
|
||||
$sql_um = "SELECT um_id FROM {$g5['ui_manager_table']} WHERE resource_code = '{$resource_code_escaped}' AND resource_type = 'DATA'";
|
||||
$um_row = sql_fetch($sql_um);
|
||||
|
||||
if (!isset($um_row['um_id'])) {
|
||||
$this->resources['data'][$lang][$resource_code] = []; // 빈 결과도 캐시
|
||||
return [];
|
||||
}
|
||||
$um_id = $um_row['um_id'];
|
||||
|
||||
$sql = "SELECT A.fc_id, A.parent_id, A.fc_key, A.fc_order, B.cl_name
|
||||
FROM {$g5['form_category_table']} AS A
|
||||
LEFT JOIN {$g5['common_lang_table']} AS B
|
||||
ON (A.fc_id = B.target_id AND B.target_table = '{$g5['form_category_table']}' AND B.lang_code = '{$lang_escaped}')
|
||||
WHERE A.um_id = '{$um_id}' AND A.is_used = 1 AND A.is_deleted = 0
|
||||
ORDER BY A.fc_order, A.fc_id";
|
||||
|
||||
$result = sql_query($sql);
|
||||
$options = [];
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$options[] = $row;
|
||||
}
|
||||
|
||||
// 결과 캐시
|
||||
$this->resources['data'][$lang][$resource_code] = $options;
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'DATA' 타입 리소스를 사용하여 HTML <select> 태그를 생성합니다.
|
||||
* @param string $resource_code 리소스 코드
|
||||
* @param string $select_name <select> 태그의 name 속성
|
||||
* @param string $selected_value 미리 선택될 옵션의 값(fc_key)
|
||||
* @param string $attributes <select> 태그에 추가할 HTML 속성 (e.g., 'id="my-id" class="my-class"')
|
||||
* @param string $lang 언어 코드 (기본값: 'ko')
|
||||
* @return string 생성된 HTML <select> 태그
|
||||
*/
|
||||
public function render_select($resource_code, $select_name, $selected_value = '', $attributes = '', $lang = 'ko')
|
||||
{
|
||||
$options = $this->get_data($resource_code, $lang);
|
||||
|
||||
if (empty($options)) {
|
||||
return "<select name=\"{$select_name}\" {$attributes}><option value=\"\">옵션 없음</option></select>";
|
||||
}
|
||||
|
||||
$html = "<select name=\"{$select_name}\" {$attributes}>";
|
||||
$html .= "<option value=\"\">선택</option>";
|
||||
foreach ($options as $option) {
|
||||
$selected = ($option['fc_key'] == $selected_value) ? ' selected' : '';
|
||||
$html .= "<option value=\"" . htmlspecialchars($option['fc_key']) . "\"{$selected}>" . htmlspecialchars($option['cl_name']) . "</option>";
|
||||
}
|
||||
$html .= "</select>";
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
/**
|
||||
* UI 리소스 관리자 클래스 인스턴스를 반환하는 헬퍼 함수
|
||||
* @return UiManager
|
||||
*/
|
||||
function ui_manager() {
|
||||
// 클래스가 아직 로드되지 않았다면 인스턴스 생성
|
||||
if (!class_exists('UiManager')) {
|
||||
// UiManager 클래스 정의
|
||||
class UiManager
|
||||
{
|
||||
private static $instance = null;
|
||||
private $resources = []; // 데이터를 캐시할 배열
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
public static function getInstance()
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function get_label($resource_code, $lang = 'ko')
|
||||
{
|
||||
if (isset($this->resources['labels'][$lang][$resource_code])) {
|
||||
return $this->resources['labels'][$lang][$resource_code];
|
||||
}
|
||||
|
||||
global $g5;
|
||||
$resource_code_escaped = sql_real_escape_string($resource_code);
|
||||
$lang_escaped = sql_real_escape_string($lang);
|
||||
|
||||
$sql = "SELECT B.cl_name
|
||||
FROM {$g5['ui_manager_table']} AS A
|
||||
LEFT JOIN {$g5['common_lang_table']} AS B
|
||||
ON (A.um_id = B.target_id AND B.target_table = '{$g5['ui_manager_table']}' AND B.lang_code = '{$lang_escaped}')
|
||||
WHERE A.resource_code = '{$resource_code_escaped}' AND A.resource_type = 'LABEL'";
|
||||
$row = sql_fetch($sql);
|
||||
|
||||
$label_text = $row['cl_name'] ?? $resource_code;
|
||||
$this->resources['labels'][$lang][$resource_code] = $label_text;
|
||||
return $label_text;
|
||||
}
|
||||
|
||||
public function get_data($resource_code, $lang = 'ko')
|
||||
{
|
||||
if (isset($this->resources['data'][$lang][$resource_code])) {
|
||||
return $this->resources['data'][$lang][$resource_code];
|
||||
}
|
||||
|
||||
global $g5;
|
||||
$resource_code_escaped = sql_real_escape_string($resource_code);
|
||||
$lang_escaped = sql_real_escape_string($lang);
|
||||
|
||||
$sql_um = "SELECT um_id FROM {$g5['ui_manager_table']} WHERE resource_code = '{$resource_code_escaped}' AND resource_type = 'DATA'";
|
||||
$um_row = sql_fetch($sql_um);
|
||||
|
||||
if (!isset($um_row['um_id'])) {
|
||||
$this->resources['data'][$lang][$resource_code] = [];
|
||||
return [];
|
||||
}
|
||||
$um_id = $um_row['um_id'];
|
||||
|
||||
$sql = "SELECT A.fc_id, A.parent_id, A.fc_key, A.fc_order, B.cl_name
|
||||
FROM {$g5['form_category_table']} AS A
|
||||
LEFT JOIN {$g5['common_lang_table']} AS B
|
||||
ON (A.fc_id = B.target_id AND B.target_table = '{$g5['form_category_table']}' AND B.lang_code = '{$lang_escaped}')
|
||||
WHERE A.um_id = '{$um_id}' AND A.is_used = 1 AND A.is_deleted = 0
|
||||
ORDER BY A.fc_order, A.fc_id";
|
||||
|
||||
$result = sql_query($sql);
|
||||
$options = [];
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$options[] = $row;
|
||||
}
|
||||
|
||||
$this->resources['data'][$lang][$resource_code] = $options;
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function render_select($resource_code, $select_name, $selected_value = '', $attributes = '', $lang = 'ko')
|
||||
{
|
||||
$options = $this->get_data($resource_code, $lang);
|
||||
|
||||
if (empty($options)) {
|
||||
return "<select name=\"".htmlspecialchars($select_name)."\" {$attributes}><option value=\"\">옵션 없음</option></select>";
|
||||
}
|
||||
|
||||
$html = "<select name=\"".htmlspecialchars($select_name)."\" {$attributes}>";
|
||||
$html .= "<option value=\"\">선택</option>";
|
||||
foreach ($options as $option) {
|
||||
$selected = ($option['fc_key'] == $selected_value) ? ' selected' : '';
|
||||
$html .= "<option value=\"" . htmlspecialchars($option['fc_key']) . "\"{$selected}>" . htmlspecialchars($option['cl_name']) . "</option>";
|
||||
}
|
||||
$html .= "</select>";
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
}
|
||||
return UiManager::getInstance();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
$sub_menu = '100350';
|
||||
include_once('./_common.php');
|
||||
|
||||
$w = isset($_REQUEST['w']) ? substr(trim($_REQUEST['w']), 0, 1) : '';
|
||||
$um_id = isset($_REQUEST['um_id']) ? (int)$_REQUEST['um_id'] : 0;
|
||||
|
||||
// ==================================================================
|
||||
// 💡 [핵심] 폼 제출(수정) 처리 로직
|
||||
// ==================================================================
|
||||
if ($w == 'u' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
check_admin_token();
|
||||
|
||||
if (!$um_id) {
|
||||
alert('um_id 값이 없습니다.');
|
||||
}
|
||||
|
||||
// 입력값 정리
|
||||
$screen_code = trim($_POST['screen_code']);
|
||||
$group_code = trim($_POST['group_code']);
|
||||
$resource_code = trim($_POST['resource_code']);
|
||||
$resource_type = trim($_POST['resource_type']);
|
||||
$resource_desc = trim($_POST['resource_desc']);
|
||||
$cl_name = isset($_POST['cl_name']) ? trim($_POST['cl_name']) : '';
|
||||
|
||||
// 유효성 검사
|
||||
if (!$screen_code || !$group_code || !$resource_code || !$resource_type) {
|
||||
alert('필수 항목을 모두 입력해주세요.');
|
||||
}
|
||||
if ($resource_type == 'LABEL' && !$cl_name) {
|
||||
alert('UI 라벨 타입은 한국어 라벨명을 필수로 입력해야 합니다.');
|
||||
}
|
||||
|
||||
// 1. g5_ui_manager 테이블 정보 업데이트
|
||||
$sql = "UPDATE {$g5['ui_manager_table']}
|
||||
SET screen_code = '{$screen_code}',
|
||||
group_code = '{$group_code}',
|
||||
resource_code = '{$resource_code}',
|
||||
resource_type = '{$resource_type}',
|
||||
resource_desc = '{$resource_desc}',
|
||||
updated_at = '".G5_TIME_YMDHIS."',
|
||||
updated_by = '{$member['mb_id']}'
|
||||
WHERE um_id = '{$um_id}'";
|
||||
sql_query($sql);
|
||||
|
||||
// 2. 리소스 타입이 'LABEL'인 경우, g5_common_lang 테이블 정보 업데이트 (없으면 생성)
|
||||
if ($resource_type == 'LABEL') {
|
||||
$sql = "SELECT cl_id FROM {$g5['common_lang_table']} WHERE target_table = '{$g5['ui_manager_table']}' AND target_id = '{$um_id}' AND lang_code = 'ko'";
|
||||
$lang_row = sql_fetch($sql);
|
||||
|
||||
if (isset($lang_row['cl_id'])) { // 기존 데이터가 있으면 UPDATE
|
||||
$sql = "UPDATE {$g5['common_lang_table']}
|
||||
SET cl_name = '{$cl_name}',
|
||||
updated_at = '".G5_TIME_YMDHIS."',
|
||||
updated_by = '{$member['mb_id']}'
|
||||
WHERE cl_id = '{$lang_row['cl_id']}'";
|
||||
} else { // 기존 데이터가 없으면 INSERT
|
||||
$sql = "INSERT INTO {$g5['common_lang_table']}
|
||||
SET target_table = '{$g5['ui_manager_table']}',
|
||||
target_id = '{$um_id}',
|
||||
lang_code = 'ko',
|
||||
cl_name = '{$cl_name}',
|
||||
updated_at = '".G5_TIME_YMDHIS."',
|
||||
updated_by = '{$member['mb_id']}'";
|
||||
}
|
||||
sql_query($sql);
|
||||
}
|
||||
|
||||
goto_url('./ui_manager_list.php');
|
||||
}
|
||||
// ==================================================================
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'r');
|
||||
|
||||
// 수정할 리소스 데이터 조회
|
||||
if ($w == 'u') {
|
||||
if (!$um_id) {
|
||||
alert('um_id 값이 없습니다.', './ui_manager_list.php');
|
||||
}
|
||||
|
||||
$sql = "SELECT A.*, B.cl_name
|
||||
FROM {$g5['ui_manager_table']} AS A
|
||||
LEFT JOIN {$g5['common_lang_table']} AS B
|
||||
ON (A.um_id = B.target_id AND B.target_table = '{$g5['ui_manager_table']}' AND B.lang_code = 'ko')
|
||||
WHERE A.um_id = '{$um_id}'";
|
||||
$ui_resource = sql_fetch($sql);
|
||||
|
||||
if (!isset($ui_resource['um_id'])) {
|
||||
alert('존재하지 않는 리소스입니다.', './ui_manager_list.php');
|
||||
}
|
||||
} else {
|
||||
alert('w 값이 올바르지 않습니다.', './ui_manager_list.php');
|
||||
}
|
||||
|
||||
$g5['title'] = 'UI 리소스 수정';
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
|
||||
add_stylesheet('<link rel="stylesheet" href="' . G5_ADMIN_URL . '/code_manager/css/code_manager.css?ver=1.1">', 0);
|
||||
?>
|
||||
|
||||
<section id="code_manager_form">
|
||||
<h2 class="h2_frm">UI 리소스 수정</h2>
|
||||
|
||||
<form name="fresourceform" id="fresourceform" action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="post">
|
||||
<input type="hidden" name="w" value="u">
|
||||
<input type="hidden" name="um_id" value="<?php echo $um_id; ?>">
|
||||
<input type="hidden" name="token" value="<?php echo get_admin_token(); ?>">
|
||||
|
||||
<div class="tbl_frm01 tbl_wrap">
|
||||
<table>
|
||||
<caption>UI 리소스 수정 폼</caption>
|
||||
<colgroup>
|
||||
<col class="grid_4">
|
||||
<col>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row"><label for="screen_code">화면 코드</label></th>
|
||||
<td>
|
||||
<input type="text" name="screen_code" id="screen_code" required class="required frm_input" size="30" value="<?php echo get_text($ui_resource['screen_code']); ?>">
|
||||
<span class="frm_info">리소스가 사용될 화면의 고유 코드 (예: order_form, member_join)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="group_code">그룹 코드</label></th>
|
||||
<td>
|
||||
<input type="text" name="group_code" id="group_code" required class="required frm_input" size="30" value="<?php echo get_text($ui_resource['group_code']); ?>">
|
||||
<span class="frm_info">화면 내에서 리소스를 묶어줄 그룹 코드 (예: address_info, common_options)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">리소스 타입</th>
|
||||
<td>
|
||||
<label><input type="radio" name="resource_type" value="LABEL" <?php echo get_checked($ui_resource['resource_type'], 'LABEL'); ?>> UI 라벨 (단일 텍스트)</label>
|
||||
<label><input type="radio" name="resource_type" value="DATA" <?php echo get_checked($ui_resource['resource_type'], 'DATA'); ?>> 데이터 (선택 옵션)</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="resource-type-field" id="label-field">
|
||||
<th scope="row"><label for="cl_name">한국어 라벨명</label></th>
|
||||
<td>
|
||||
<input type="text" name="cl_name" id="cl_name" class="frm_input" size="50" value="<?php echo get_text($ui_resource['cl_name']); ?>">
|
||||
<span class="frm_info">화면에 표시될 실제 텍스트 (예: 집의 유형, 창호 재질)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="resource_code">리소스 코드</label></th>
|
||||
<td>
|
||||
<input type="text" name="resource_code" id="resource_code" required class="required frm_input" size="30" value="<?php echo get_text($ui_resource['resource_code']); ?>">
|
||||
<span class="frm_info">개발자가 이 리소스를 호출할 때 사용할 고유 코드 (예: house_type_label, house_type_data)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="resource_desc">설명</label></th>
|
||||
<td>
|
||||
<input type="text" name="resource_desc" id="resource_desc" class="frm_input" size="80" value="<?php echo get_text($ui_resource['resource_desc']); ?>">
|
||||
<span class="frm_info">이 리소스의 용도에 대한 설명 (관리자 참고용)</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="btn_confirm01 btn_confirm">
|
||||
<a href="./ui_manager_list.php" class="btn_cancel btn">목록으로</a>
|
||||
<input type="submit" value="수정" class="btn_submit btn" accesskey="s">
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<script src="<?php echo G5_ADMIN_URL; ?>/code_manager/js/ui_manager.js?ver=1.2"></script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
$sub_menu = '700100';
|
||||
include_once('./_common.php');
|
||||
|
||||
// ==================================================================
|
||||
// 삭제 처리 로직
|
||||
// ==================================================================
|
||||
if (isset($_GET['mode']) && $_GET['mode'] == 'delete') {
|
||||
auth_check_menu($auth, $sub_menu, 'd');
|
||||
check_admin_token();
|
||||
$um_id = isset($_GET['um_id']) ? (int)$_GET['um_id'] : 0;
|
||||
if (!$um_id) {
|
||||
alert('um_id 값이 없습니다.');
|
||||
}
|
||||
$sql = "SELECT resource_type FROM {$g5['ui_manager_table']} WHERE um_id = '{$um_id}'";
|
||||
$row = sql_fetch($sql);
|
||||
if (isset($row['resource_type'])) {
|
||||
if ($row['resource_type'] == 'DATA') {
|
||||
$sql_delete_lang = "DELETE FROM {$g5['common_lang_table']} WHERE target_table = '{$g5['form_category_table']}' AND target_id IN (SELECT fc_id FROM {$g5['form_category_table']} WHERE um_id = '{$um_id}')";
|
||||
sql_query($sql_delete_lang);
|
||||
$sql_delete_cat = "DELETE FROM {$g5['form_category_table']} WHERE um_id = '{$um_id}'";
|
||||
sql_query($sql_delete_cat);
|
||||
}
|
||||
$sql_delete_main_lang = "DELETE FROM {$g5['common_lang_table']} WHERE target_table = '{$g5['ui_manager_table']}' AND target_id = '{$um_id}'";
|
||||
sql_query($sql_delete_main_lang);
|
||||
$sql_delete_main = "DELETE FROM {$g5['ui_manager_table']} WHERE um_id = '{$um_id}'";
|
||||
sql_query($sql_delete_main);
|
||||
}
|
||||
// 삭제 후 현재 페이지와 검색 조건을 유지하도록 $qstr 사용
|
||||
goto_url('./ui_manager_list.php?'.$qstr);
|
||||
}
|
||||
|
||||
// ==================================================================
|
||||
// 💡 [핵심 수정 1] 폼 제출 처리 로직 (신규 등록)
|
||||
// ==================================================================
|
||||
// GnuBoard 표준에 따라 'w' 값이 비어있을 때를 신규 등록으로 처리합니다.
|
||||
if (empty($w) && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
check_admin_token();
|
||||
|
||||
// 입력값 정리
|
||||
$screen_code = trim($_POST['screen_code']);
|
||||
$group_code = trim($_POST['group_code']);
|
||||
$resource_code = trim($_POST['resource_code']);
|
||||
$resource_type = trim($_POST['resource_type']);
|
||||
$resource_desc = trim($_POST['resource_desc']);
|
||||
$cl_name = isset($_POST['cl_name']) ? trim($_POST['cl_name']) : '';
|
||||
|
||||
// 유효성 검사
|
||||
if (!$screen_code || !$group_code || !$resource_code || !$resource_type) {
|
||||
alert('필수 항목을 모두 입력해주세요.');
|
||||
}
|
||||
if ($resource_type == 'LABEL' && !$cl_name) {
|
||||
alert('UI 라벨 타입은 한국어 라벨명을 필수로 입력해야 합니다.');
|
||||
}
|
||||
|
||||
// 중복 체크
|
||||
$sql = "SELECT COUNT(*) as cnt FROM {$g5['ui_manager_table']} WHERE screen_code = '{$screen_code}' AND group_code = '{$group_code}' AND resource_code = '{$resource_code}'";
|
||||
$row = sql_fetch($sql);
|
||||
if ($row['cnt']) {
|
||||
alert('이미 동일한 화면/그룹/리소스 코드로 등록된 리소스가 존재합니다.');
|
||||
}
|
||||
|
||||
// 1. g5_ui_manager 테이블에 정보 INSERT
|
||||
$sql = "INSERT INTO {$g5['ui_manager_table']}
|
||||
SET screen_code = '{$screen_code}',
|
||||
group_code = '{$group_code}',
|
||||
resource_code = '{$resource_code}',
|
||||
resource_type = '{$resource_type}',
|
||||
resource_desc = '{$resource_desc}',
|
||||
is_used = '1',
|
||||
created_at = '".G5_TIME_YMDHIS."',
|
||||
created_by = '{$member['mb_id']}',
|
||||
updated_at = '".G5_TIME_YMDHIS."',
|
||||
updated_by = '{$member['mb_id']}'";
|
||||
sql_query($sql);
|
||||
$um_id = sql_insert_id();
|
||||
|
||||
// 2. 리소스 타입이 'LABEL'인 경우, g5_common_lang 테이블에 정보 INSERT
|
||||
if ($resource_type == 'LABEL' && $cl_name) {
|
||||
$sql = "INSERT INTO {$g5['common_lang_table']}
|
||||
SET target_table = '{$g5['ui_manager_table']}',
|
||||
target_id = '{$um_id}',
|
||||
lang_code = 'ko',
|
||||
cl_name = '{$cl_name}',
|
||||
updated_at = '".G5_TIME_YMDHIS."',
|
||||
updated_by = '{$member['mb_id']}'";
|
||||
sql_query($sql);
|
||||
}
|
||||
|
||||
goto_url('./ui_manager_list.php');
|
||||
}
|
||||
// ==================================================================
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'r');
|
||||
|
||||
// 검색 및 페이징 변수 처리
|
||||
$sfl = isset($_GET['sfl']) ? trim($_GET['sfl']) : '';
|
||||
$stx = isset($_GET['stx']) ? trim($_GET['stx']) : '';
|
||||
$page_rows = isset($_GET['page_rows']) && (int)$_GET['page_rows'] > 0 ? (int)$_GET['page_rows'] : 15;
|
||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||
if ($page < 1) $page = 1;
|
||||
|
||||
// 페이지당 목록 수($page_rows)를 $qstr에 추가하여 다른 링크에서도 유지되도록 함
|
||||
$qstr .= ($qstr ? '&' : '') . 'page_rows=' . urlencode($page_rows);
|
||||
|
||||
$sql_search = "";
|
||||
if ($sfl && $stx) {
|
||||
$stx_escaped = sql_real_escape_string($stx);
|
||||
$sql_search = " WHERE ";
|
||||
|
||||
switch ($sfl) {
|
||||
case 'screen_code':
|
||||
case 'group_code':
|
||||
case 'resource_code':
|
||||
case 'resource_desc':
|
||||
$sql_search .= "A.{$sfl} LIKE '%{$stx_escaped}%'";
|
||||
break;
|
||||
case 'cl_name':
|
||||
$sql_search .= "B.cl_name LIKE '%{$stx_escaped}%'";
|
||||
break;
|
||||
default: // '전체' 검색
|
||||
$sql_search .= " ( A.screen_code LIKE '%{$stx_escaped}%' OR A.group_code LIKE '%{$stx_escaped}%' OR A.resource_code LIKE '%{$stx_escaped}%' OR A.resource_desc LIKE '%{$stx_escaped}%' OR B.cl_name LIKE '%{$stx_escaped}%' ) ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 검색 조건에 맞는 모든 리소스를 한 번에 가져옴
|
||||
$sql = "SELECT A.*, B.cl_name
|
||||
FROM {$g5['ui_manager_table']} AS A
|
||||
LEFT JOIN {$g5['common_lang_table']} AS B
|
||||
ON (A.um_id = B.target_id AND B.target_table = '{$g5['ui_manager_table']}' AND B.lang_code = 'ko')
|
||||
{$sql_search}
|
||||
ORDER BY A.screen_code, A.group_code, A.resource_code";
|
||||
$result = sql_query($sql);
|
||||
|
||||
// 2. PHP에서 화면별로 그룹화
|
||||
$all_grouped_resources = [];
|
||||
$total_resource_count = 0;
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$all_grouped_resources[$row['screen_code']][] = $row;
|
||||
$total_resource_count++;
|
||||
}
|
||||
|
||||
// 3. 그룹화된 결과를 기준으로 페이징 처리
|
||||
$total_count = count($all_grouped_resources); // 전체 '화면' 수
|
||||
$total_page = ceil($total_count / $page_rows);
|
||||
$from_record = ($page - 1) * $page_rows;
|
||||
|
||||
// 현재 페이지에 해당하는 그룹만 잘라냄
|
||||
$paged_groups = array_slice($all_grouped_resources, $from_record, $page_rows, true);
|
||||
|
||||
$g5['title'] = 'UI 리소스 관리';
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
|
||||
add_stylesheet('<link rel="stylesheet" href="' . G5_ADMIN_URL . '/code_manager/css/code_manager.css?ver=1.3">', 0);
|
||||
?>
|
||||
|
||||
<div class="local_desc01 local_desc">
|
||||
<p>
|
||||
웹사이트의 모든 화면에 사용되는 텍스트(라벨)와 선택 옵션(데이터)을 체계적으로 관리합니다.<br>
|
||||
'화면 코드' 별로 그룹화되어 표시되며, 각 그룹을 클릭하여 내용을 확인하거나 수정할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 검색 폼 -->
|
||||
<section id="resource_search_form">
|
||||
<h2 class="h2_frm">리소스 검색</h2>
|
||||
<form name="fsearch" id="fsearch" method="get">
|
||||
<div class="search-form-inner">
|
||||
<label for="page_rows" class="sound_only">페이지당 개수</label>
|
||||
<select name="page_rows" id="page_rows" class="frm_input">
|
||||
<option value="15" <?php echo get_selected($page_rows, 15); ?>>15개씩</option>
|
||||
<option value="30" <?php echo get_selected($page_rows, 30); ?>>30개씩</option>
|
||||
<option value="50" <?php echo get_selected($page_rows, 50); ?>>50개씩</option>
|
||||
<option value="100" <?php echo get_selected($page_rows, 100); ?>>100개씩</option>
|
||||
</select>
|
||||
|
||||
<label for="sfl" class="sound_only">검색대상</label>
|
||||
<select name="sfl" id="sfl">
|
||||
<option value="all" <?php echo get_selected($sfl, 'all'); ?>>전체</option>
|
||||
<option value="screen_code" <?php echo get_selected($sfl, 'screen_code'); ?>>화면 코드</option>
|
||||
<option value="group_code" <?php echo get_selected($sfl, 'group_code'); ?>>그룹 코드</option>
|
||||
<option value="resource_code" <?php echo get_selected($sfl, 'resource_code'); ?>>리소스 코드</option>
|
||||
<option value="resource_desc" <?php echo get_selected($sfl, 'resource_desc'); ?>>설명</option>
|
||||
<option value="cl_name" <?php echo get_selected($sfl, 'cl_name'); ?>>라벨명</option>
|
||||
</select>
|
||||
|
||||
<label for="stx" class="sound_only">검색어</label>
|
||||
<input type="text" name="stx" value="<?php echo get_text($stx) ?>" id="stx" class="frm_input" size="30">
|
||||
<input type="submit" value="검색" class="btn_submit">
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section id="code_manager">
|
||||
<div class="code-manager-header">
|
||||
<h2 class="code-manager-title">UI 리소스 목록 (총 <?php echo number_format($total_count); ?>개 화면)</h2>
|
||||
<div class="code-manager-actions">
|
||||
<button type="button" id="add-resource-btn" class="btn btn_01">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i> 새 리소스 추가
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 새 리소스 추가 폼 -->
|
||||
<div id="resource-form-container" style="display: none;">
|
||||
<form name="fresourceform" id="fresourceform" action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="post">
|
||||
<!-- 💡 [핵심 수정 2] 신규 등록이므로 w 값을 비워둡니다. -->
|
||||
<input type="hidden" name="w" value="">
|
||||
<input type="hidden" name="token" value="<?php echo get_admin_token(); ?>">
|
||||
<div class="tbl_frm01 tbl_wrap">
|
||||
<table>
|
||||
<caption>UI 리소스 추가 폼</caption>
|
||||
<colgroup>
|
||||
<col class="grid_4">
|
||||
<col>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row"><label for="screen_code">화면 코드</label></th>
|
||||
<td>
|
||||
<input type="text" name="screen_code" id="screen_code" required class="required frm_input" size="30">
|
||||
<span class="frm_info">리소스가 사용될 화면의 고유 코드 (예: order_form, member_join)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="group_code">그룹 코드</label></th>
|
||||
<td>
|
||||
<input type="text" name="group_code" id="group_code" required class="required frm_input" size="30">
|
||||
<span class="frm_info">화면 내에서 리소스를 묶어줄 그룹 코드 (예: address_info, common_options)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">리소스 타입</th>
|
||||
<td>
|
||||
<label><input type="radio" name="resource_type" value="LABEL" checked> UI 라벨 (단일 텍스트)</label>
|
||||
<label><input type="radio" name="resource_type" value="DATA"> 데이터 (선택 옵션)</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="resource-type-field" id="label-field">
|
||||
<th scope="row"><label for="cl_name">한국어 라벨명</label></th>
|
||||
<td>
|
||||
<input type="text" name="cl_name" id="cl_name" class="frm_input" size="50">
|
||||
<span class="frm_info">화면에 표시될 실제 텍스트 (예: 집의 유형, 창호 재질)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="resource_code">리소스 코드</label></th>
|
||||
<td>
|
||||
<input type="text" name="resource_code" id="resource_code" required class="required frm_input" size="30">
|
||||
<span class="frm_info">개발자가 이 리소스를 호출할 때 사용할 고유 코드 (예: house_type_label, house_type_data)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="resource_desc">설명</label></th>
|
||||
<td>
|
||||
<input type="text" name="resource_desc" id="resource_desc" class="frm_input" size="80">
|
||||
<span class="frm_info">이 리소스의 용도에 대한 설명 (관리자 참고용)</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="btn_confirm01 btn_confirm">
|
||||
<button type="button" id="cancel-resource-btn" class="btn_cancel btn">취소</button>
|
||||
<input type="submit" value="리소스 등록" class="btn_submit btn" accesskey="s">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 아코디언 형태의 리소스 목록 -->
|
||||
<div id="resource-list-accordion">
|
||||
<?php if (count($paged_groups) == 0) : ?>
|
||||
<div class="empty_list">표시할 리소스가 없습니다.</div>
|
||||
<?php else : ?>
|
||||
<?php foreach ($paged_groups as $screen_code => $resources) : ?>
|
||||
<div class="accordion-item">
|
||||
<div class="accordion-header">
|
||||
<span class="screen-title"><i class="fa fa-desktop"></i> 화면: <strong><?php echo get_text($screen_code); ?></strong></span>
|
||||
<span class="resource-count"><?php echo count($resources); ?>개 리소스</span>
|
||||
<i class="fa fa-chevron-down accordion-icon"></i>
|
||||
</div>
|
||||
<div class="accordion-content">
|
||||
<div class="tbl_head01 tbl_wrap">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style="width: 15%;">
|
||||
<col style="width: 20%;">
|
||||
<col style="width: 10%;">
|
||||
<col>
|
||||
<col style="width: 240px;">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">그룹 코드</th>
|
||||
<th scope="col">리소스 코드</th>
|
||||
<th scope="col">타입</th>
|
||||
<th scope="col">설명</th>
|
||||
<th scope="col">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($resources as $res) : ?>
|
||||
<tr>
|
||||
<td><?php echo get_text($res['group_code']); ?></td>
|
||||
<td><?php echo get_text($res['resource_code']); ?></td>
|
||||
<td>
|
||||
<?php if ($res['resource_type'] == 'LABEL'): ?>
|
||||
<span class="res-type-label">라벨</span>
|
||||
<?php else: ?>
|
||||
<span class="res-type-data">데이터</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="td_left"><?php echo get_text($res['resource_desc']); ?></td>
|
||||
<td class="td_mng td_mng_s">
|
||||
<?php if ($res['resource_type'] == 'DATA') : ?>
|
||||
<a href="./category_list.php?um_id=<?php echo $res['um_id']; ?>" class="btn btn_03">옵션 관리</a>
|
||||
<?php endif; ?>
|
||||
<a href="./ui_manager_form.php?w=u&um_id=<?php echo $res['um_id']; ?>&<?php echo $qstr; ?>" class="btn btn_02">수정</a>
|
||||
<a href="./lang_manager.php?target_table=<?php echo $g5['ui_manager_table']; ?>&target_id=<?php echo $res['um_id']; ?>" class="btn btn_01">다국어</a>
|
||||
<button type="button" class="btn btn_delete btn_delete_resource" data-um_id="<?php echo $res['um_id']; ?>">삭제</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- 페이징 링크 출력 -->
|
||||
<div class="pagination_wrap">
|
||||
<?php
|
||||
$paging_url = $_SERVER['SCRIPT_NAME'].'?'.$qstr;
|
||||
echo get_paging(G5_IS_MOBILE ? $config['cf_mobile_pages'] : $config['cf_write_pages'], $page, $total_page, $paging_url);
|
||||
?>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<script>
|
||||
var g5_admin_token = "<?php echo get_admin_token(); ?>";
|
||||
</script>
|
||||
<script src="<?php echo G5_ADMIN_URL; ?>/code_manager/js/ui_manager.js?ver=1.3"></script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
+1823
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,387 @@
|
||||
<?php
|
||||
$sub_menu = "100100";
|
||||
require_once './_common.php';
|
||||
|
||||
check_demo();
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
if ($is_admin != 'super') {
|
||||
alert('최고관리자만 접근 가능합니다.');
|
||||
}
|
||||
|
||||
$sql = " select * from {$g5['config_table']} limit 1";
|
||||
$ori_config = sql_fetch($sql);
|
||||
|
||||
$cf_title = isset($_POST['cf_title']) ? strip_tags(clean_xss_attributes($_POST['cf_title'])) : '';
|
||||
$cf_admin = isset($_POST['cf_admin']) ? clean_xss_tags($_POST['cf_admin'], 1, 1) : '';
|
||||
|
||||
$mb = get_member($cf_admin);
|
||||
|
||||
if (!(isset($mb['mb_id']) && $mb['mb_id'])) {
|
||||
alert('최고관리자 회원아이디가 존재하지 않습니다.');
|
||||
}
|
||||
|
||||
check_admin_token();
|
||||
|
||||
$cf_social_servicelist = !empty($_POST['cf_social_servicelist']) ? implode(',', $_POST['cf_social_servicelist']) : '';
|
||||
|
||||
$check_keys = array('cf_cert_kcb_cd', 'cf_cert_kcp_cd', 'cf_cert_kcp_enckey', 'cf_editor', 'cf_recaptcha_site_key', 'cf_recaptcha_secret_key', 'cf_naver_clientid', 'cf_naver_secret', 'cf_facebook_appid', 'cf_facebook_secret', 'cf_twitter_key', 'cf_twitter_secret', 'cf_google_clientid', 'cf_google_secret', 'cf_googl_shorturl_apikey', 'cf_kakao_rest_key', 'cf_kakao_client_secret', 'cf_kakao_js_apikey', 'cf_payco_clientid', 'cf_payco_secret', 'cf_cert_kg_cd', 'cf_cert_kg_mid');
|
||||
|
||||
foreach ($check_keys as $key) {
|
||||
if (isset($_POST[$key]) && $_POST[$key]) {
|
||||
$_POST[$key] = preg_replace('/[^a-z0-9_\-\.]/i', '', $_POST[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$_POST['cf_icode_server_port'] = isset($_POST['cf_icode_server_port']) ? preg_replace('/[^0-9]/', '', $_POST['cf_icode_server_port']) : '7295';
|
||||
|
||||
if (isset($_POST['cf_intercept_ip']) && $_POST['cf_intercept_ip']) {
|
||||
$pattern = explode("\n", trim($_POST['cf_intercept_ip']));
|
||||
for ($i = 0; $i < count($pattern); $i++) {
|
||||
$pattern[$i] = trim($pattern[$i]);
|
||||
if (empty($pattern[$i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pattern[$i] = str_replace(".", "\.", $pattern[$i]);
|
||||
$pattern[$i] = str_replace("+", "[0-9\.]+", $pattern[$i]);
|
||||
$pat = "/^{$pattern[$i]}$/";
|
||||
|
||||
if (preg_match($pat, $_SERVER['REMOTE_ADDR'])) {
|
||||
alert("현재 접속 IP : " . $_SERVER['REMOTE_ADDR'] . " 가 차단될수 있기 때문에, 다른 IP를 입력해 주세요.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$check_keys = array(
|
||||
'cf_use_email_certify' => 'int',
|
||||
'cf_use_homepage' => 'int',
|
||||
'cf_req_homepage' => 'int',
|
||||
'cf_use_tel' => 'int',
|
||||
'cf_req_tel' => 'int',
|
||||
'cf_use_hp' => 'int',
|
||||
'cf_req_hp' => 'int',
|
||||
'cf_use_addr' => 'int',
|
||||
'cf_req_addr' => 'int',
|
||||
'cf_use_signature' => 'int',
|
||||
'cf_req_signature' => 'int',
|
||||
'cf_use_profile' => 'int',
|
||||
'cf_req_profile' => 'int',
|
||||
'cf_register_level' => 'int',
|
||||
'cf_register_point' => 'int',
|
||||
'cf_icon_level' => 'int',
|
||||
'cf_use_recommend' => 'int',
|
||||
'cf_leave_day' => 'int',
|
||||
'cf_search_part' => 'int',
|
||||
'cf_email_use' => 'int',
|
||||
'cf_email_wr_super_admin' => 'int',
|
||||
'cf_email_wr_group_admin' => 'int',
|
||||
'cf_email_wr_board_admin' => 'int',
|
||||
'cf_email_wr_write' => 'int',
|
||||
'cf_email_wr_comment_all' => 'int',
|
||||
'cf_email_mb_super_admin' => 'int',
|
||||
'cf_email_mb_member' => 'int',
|
||||
'cf_email_po_super_admin' => 'int',
|
||||
'cf_prohibit_id' => 'text',
|
||||
'cf_prohibit_email' => 'text',
|
||||
'cf_new_del' => 'int',
|
||||
'cf_memo_del' => 'int',
|
||||
'cf_visit_del' => 'int',
|
||||
'cf_popular_del' => 'int',
|
||||
'cf_use_member_icon' => 'int',
|
||||
'cf_member_icon_size' => 'int',
|
||||
'cf_member_icon_width' => 'int',
|
||||
'cf_member_icon_height' => 'int',
|
||||
'cf_member_img_size' => 'int',
|
||||
'cf_member_img_width' => 'int',
|
||||
'cf_member_img_height' => 'int',
|
||||
'cf_login_minutes' => 'int',
|
||||
'cf_formmail_is_member' => 'int',
|
||||
'cf_page_rows' => 'int',
|
||||
'cf_mobile_page_rows' => 'int',
|
||||
'cf_social_login_use' => 'int',
|
||||
'cf_cert_req' => 'int',
|
||||
'cf_cert_use' => 'int',
|
||||
'cf_cert_find' => 'int',
|
||||
'cf_cert_ipin' => 'char',
|
||||
'cf_cert_hp' => 'char',
|
||||
'cf_cert_simple' => 'char',
|
||||
'cf_cert_use_seed' => 'int',
|
||||
'cf_admin_email' => 'char',
|
||||
'cf_admin_email_name' => 'char',
|
||||
'cf_add_script' => 'text',
|
||||
'cf_use_point' => 'int',
|
||||
'cf_point_term' => 'int',
|
||||
'cf_use_copy_log' => 'int',
|
||||
'cf_login_point' => 'int',
|
||||
'cf_cut_name' => 'int',
|
||||
'cf_nick_modify' => 'int',
|
||||
'cf_new_skin' => 'char',
|
||||
'cf_new_rows' => 'int',
|
||||
'cf_search_skin' => 'char',
|
||||
'cf_connect_skin' => 'char',
|
||||
'cf_faq_skin' => 'char',
|
||||
'cf_read_point' => 'int',
|
||||
'cf_write_point' => 'int',
|
||||
'cf_comment_point' => 'int',
|
||||
'cf_download_point' => 'int',
|
||||
'cf_write_pages' => 'int',
|
||||
'cf_mobile_pages' => 'int',
|
||||
'cf_link_target' => 'char',
|
||||
'cf_delay_sec' => 'int',
|
||||
'cf_filter' => 'text',
|
||||
'cf_possible_ip' => 'text',
|
||||
'cf_analytics' => 'text',
|
||||
'cf_add_meta' => 'text',
|
||||
'cf_member_skin' => 'char',
|
||||
'cf_image_extension' => 'char',
|
||||
'cf_flash_extension' => 'char',
|
||||
'cf_movie_extension' => 'char',
|
||||
'cf_visit' => 'char',
|
||||
'cf_stipulation' => 'text',
|
||||
'cf_privacy' => 'text',
|
||||
'cf_open_modify' => 'int',
|
||||
'cf_memo_send_point' => 'int',
|
||||
'cf_mobile_new_skin' => 'char',
|
||||
'cf_mobile_search_skin' => 'char',
|
||||
'cf_mobile_connect_skin' => 'char',
|
||||
'cf_mobile_faq_skin' => 'char',
|
||||
'cf_mobile_member_skin' => 'char',
|
||||
'cf_captcha_mp3' => 'char',
|
||||
'cf_cert_limit' => 'int',
|
||||
'cf_sms_use' => 'char',
|
||||
'cf_sms_type' => 'char',
|
||||
'cf_icode_id' => 'char',
|
||||
'cf_icode_pw' => 'char',
|
||||
'cf_icode_server_ip' => 'char',
|
||||
'cf_captcha' => 'char',
|
||||
'cf_syndi_token' => '',
|
||||
'cf_syndi_except' => ''
|
||||
);
|
||||
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$check_keys['cf_' . $i . '_subj'] = isset($_POST['cf_' . $i . '_subj']) ? $_POST['cf_' . $i . '_subj'] : '';
|
||||
$check_keys['cf_' . $i] = isset($_POST['cf_' . $i]) ? $_POST['cf_' . $i] : '';
|
||||
}
|
||||
|
||||
foreach ($check_keys as $k => $v) {
|
||||
if ($v === 'int') {
|
||||
$_POST[$k] = isset($_POST[$k]) ? (int) $_POST[$k] : 0;
|
||||
} else {
|
||||
if (in_array($k, array('cf_analytics', 'cf_add_meta', 'cf_add_script', 'cf_stipulation', 'cf_privacy'))) {
|
||||
$_POST[$k] = isset($_POST[$k]) ? $_POST[$k] : '';
|
||||
} else {
|
||||
$_POST[$k] = isset($_POST[$k]) ? strip_tags(clean_xss_attributes($_POST[$k])) : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 본인확인을 사용할 경우 아이핀, 휴대폰인증 중 하나는 선택되어야 함
|
||||
if ($_POST['cf_cert_use'] && !$_POST['cf_cert_ipin'] && !$_POST['cf_cert_hp'] && !$_POST['cf_cert_simple']) {
|
||||
alert('본인확인을 위해 아이핀, 휴대폰 본인확인, KG이니시스 간편인증 서비스 중 하나 이상 선택해 주십시오.');
|
||||
}
|
||||
|
||||
if (!$_POST['cf_cert_use']) {
|
||||
$_POST['cf_cert_ipin'] = '';
|
||||
$_POST['cf_cert_hp'] = '';
|
||||
$_POST['cf_cert_simple'] = '';
|
||||
}
|
||||
|
||||
// 관리자가 자동등록방지를 사용해야 할 경우 ( 기본환경설정에서 최고관리자, 방문자분석 스크립트, 추가 메타태그, 추가 script, css 변경시 )
|
||||
$check_captcha = 0;
|
||||
|
||||
if ($cf_admin && $ori_config['cf_admin'] !== $cf_admin) {
|
||||
$check_captcha = 1;
|
||||
}
|
||||
|
||||
if ($_POST['cf_analytics'] && $ori_config['cf_analytics'] !== stripslashes($_POST['cf_analytics'])) {
|
||||
$check_captcha = 1;
|
||||
}
|
||||
|
||||
if ($_POST['cf_add_meta'] && $ori_config['cf_add_meta'] !== stripslashes($_POST['cf_add_meta'])) {
|
||||
$check_captcha = 1;
|
||||
}
|
||||
|
||||
if ($_POST['cf_add_script'] && $ori_config['cf_add_script'] !== stripslashes($_POST['cf_add_script'])) {
|
||||
$check_captcha = 1;
|
||||
}
|
||||
|
||||
if ($check_captcha) {
|
||||
include_once(G5_CAPTCHA_PATH . '/captcha.lib.php');
|
||||
|
||||
if (!chk_captcha()) {
|
||||
alert('자동등록방지 숫자가 틀렸습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
$sql = " update {$g5['config_table']}
|
||||
set cf_title = '{$cf_title}',
|
||||
cf_admin = '{$cf_admin}',
|
||||
cf_admin_email = '{$_POST['cf_admin_email']}',
|
||||
cf_admin_email_name = '{$_POST['cf_admin_email_name']}',
|
||||
cf_add_script = '{$_POST['cf_add_script']}',
|
||||
cf_use_point = '{$_POST['cf_use_point']}',
|
||||
cf_point_term = '{$_POST['cf_point_term']}',
|
||||
cf_use_copy_log = '{$_POST['cf_use_copy_log']}',
|
||||
cf_use_email_certify = '{$_POST['cf_use_email_certify']}',
|
||||
cf_login_point = '{$_POST['cf_login_point']}',
|
||||
cf_cut_name = '{$_POST['cf_cut_name']}',
|
||||
cf_nick_modify = '{$_POST['cf_nick_modify']}',
|
||||
cf_new_skin = '{$_POST['cf_new_skin']}',
|
||||
cf_new_rows = '{$_POST['cf_new_rows']}',
|
||||
cf_search_skin = '{$_POST['cf_search_skin']}',
|
||||
cf_connect_skin = '{$_POST['cf_connect_skin']}',
|
||||
cf_faq_skin = '{$_POST['cf_faq_skin']}',
|
||||
cf_read_point = '{$_POST['cf_read_point']}',
|
||||
cf_write_point = '{$_POST['cf_write_point']}',
|
||||
cf_comment_point = '{$_POST['cf_comment_point']}',
|
||||
cf_download_point = '{$_POST['cf_download_point']}',
|
||||
cf_write_pages = '{$_POST['cf_write_pages']}',
|
||||
cf_mobile_pages = '{$_POST['cf_mobile_pages']}',
|
||||
cf_link_target = '{$_POST['cf_link_target']}',
|
||||
cf_delay_sec = '{$_POST['cf_delay_sec']}',
|
||||
cf_filter = '{$_POST['cf_filter']}',
|
||||
cf_possible_ip = '" . trim($_POST['cf_possible_ip']) . "',
|
||||
cf_intercept_ip = '" . trim($_POST['cf_intercept_ip']) . "',
|
||||
cf_analytics = '{$_POST['cf_analytics']}',
|
||||
cf_add_meta = '{$_POST['cf_add_meta']}',
|
||||
cf_syndi_token = '{$_POST['cf_syndi_token']}',
|
||||
cf_syndi_except = '{$_POST['cf_syndi_except']}',
|
||||
cf_bbs_rewrite = '{$_POST['cf_bbs_rewrite']}',
|
||||
cf_member_skin = '{$_POST['cf_member_skin']}',
|
||||
cf_use_homepage = '{$_POST['cf_use_homepage']}',
|
||||
cf_req_homepage = '{$_POST['cf_req_homepage']}',
|
||||
cf_use_tel = '{$_POST['cf_use_tel']}',
|
||||
cf_req_tel = '{$_POST['cf_req_tel']}',
|
||||
cf_use_hp = '{$_POST['cf_use_hp']}',
|
||||
cf_req_hp = '{$_POST['cf_req_hp']}',
|
||||
cf_use_addr = '{$_POST['cf_use_addr']}',
|
||||
cf_req_addr = '{$_POST['cf_req_addr']}',
|
||||
cf_use_signature = '{$_POST['cf_use_signature']}',
|
||||
cf_req_signature = '{$_POST['cf_req_signature']}',
|
||||
cf_use_profile = '{$_POST['cf_use_profile']}',
|
||||
cf_req_profile = '{$_POST['cf_req_profile']}',
|
||||
cf_register_level = '{$_POST['cf_register_level']}',
|
||||
cf_register_point = '{$_POST['cf_register_point']}',
|
||||
cf_icon_level = '{$_POST['cf_icon_level']}',
|
||||
cf_use_recommend = '{$_POST['cf_use_recommend']}',
|
||||
cf_recommend_point = '{$_POST['cf_recommend_point']}',
|
||||
cf_leave_day = '{$_POST['cf_leave_day']}',
|
||||
cf_search_part = '{$_POST['cf_search_part']}',
|
||||
cf_email_use = '{$_POST['cf_email_use']}',
|
||||
cf_email_wr_super_admin = '{$_POST['cf_email_wr_super_admin']}',
|
||||
cf_email_wr_group_admin = '{$_POST['cf_email_wr_group_admin']}',
|
||||
cf_email_wr_board_admin = '{$_POST['cf_email_wr_board_admin']}',
|
||||
cf_email_wr_write = '{$_POST['cf_email_wr_write']}',
|
||||
cf_email_wr_comment_all = '{$_POST['cf_email_wr_comment_all']}',
|
||||
cf_email_mb_super_admin = '{$_POST['cf_email_mb_super_admin']}',
|
||||
cf_email_mb_member = '{$_POST['cf_email_mb_member']}',
|
||||
cf_email_po_super_admin = '{$_POST['cf_email_po_super_admin']}',
|
||||
cf_prohibit_id = '{$_POST['cf_prohibit_id']}',
|
||||
cf_prohibit_email = '{$_POST['cf_prohibit_email']}',
|
||||
cf_new_del = '{$_POST['cf_new_del']}',
|
||||
cf_memo_del = '{$_POST['cf_memo_del']}',
|
||||
cf_visit_del = '{$_POST['cf_visit_del']}',
|
||||
cf_popular_del = '{$_POST['cf_popular_del']}',
|
||||
cf_use_member_icon = '{$_POST['cf_use_member_icon']}',
|
||||
cf_member_icon_size = '{$_POST['cf_member_icon_size']}',
|
||||
cf_member_icon_width = '{$_POST['cf_member_icon_width']}',
|
||||
cf_member_icon_height = '{$_POST['cf_member_icon_height']}',
|
||||
cf_member_img_size = '{$_POST['cf_member_img_size']}',
|
||||
cf_member_img_width = '{$_POST['cf_member_img_width']}',
|
||||
cf_member_img_height = '{$_POST['cf_member_img_height']}',
|
||||
cf_login_minutes = '{$_POST['cf_login_minutes']}',
|
||||
cf_image_extension = '{$_POST['cf_image_extension']}',
|
||||
cf_flash_extension = '{$_POST['cf_flash_extension']}',
|
||||
cf_movie_extension = '{$_POST['cf_movie_extension']}',
|
||||
cf_formmail_is_member = '{$_POST['cf_formmail_is_member']}',
|
||||
cf_page_rows = '{$_POST['cf_page_rows']}',
|
||||
cf_mobile_page_rows = '{$_POST['cf_mobile_page_rows']}',
|
||||
cf_stipulation = '{$_POST['cf_stipulation']}',
|
||||
cf_privacy = '{$_POST['cf_privacy']}',
|
||||
cf_open_modify = '{$_POST['cf_open_modify']}',
|
||||
cf_memo_send_point = '{$_POST['cf_memo_send_point']}',
|
||||
cf_mobile_new_skin = '{$_POST['cf_mobile_new_skin']}',
|
||||
cf_mobile_search_skin = '{$_POST['cf_mobile_search_skin']}',
|
||||
cf_mobile_connect_skin = '{$_POST['cf_mobile_connect_skin']}',
|
||||
cf_mobile_faq_skin = '{$_POST['cf_mobile_faq_skin']}',
|
||||
cf_mobile_member_skin = '{$_POST['cf_mobile_member_skin']}',
|
||||
cf_captcha_mp3 = '{$_POST['cf_captcha_mp3']}',
|
||||
cf_editor = '{$_POST['cf_editor']}',
|
||||
cf_cert_use = '{$_POST['cf_cert_use']}',
|
||||
cf_cert_find = '{$_POST['cf_cert_find']}',
|
||||
cf_cert_ipin = '{$_POST['cf_cert_ipin']}',
|
||||
cf_cert_hp = '{$_POST['cf_cert_hp']}',
|
||||
cf_cert_simple = '{$_POST['cf_cert_simple']}',
|
||||
cf_cert_use_seed = '".(int)$_POST['cf_cert_use_seed']."',
|
||||
cf_cert_kg_cd = '{$_POST['cf_cert_kg_cd']}',
|
||||
cf_cert_kg_mid = '" . trim($_POST['cf_cert_kg_mid']) . "',
|
||||
cf_cert_kcb_cd = '{$_POST['cf_cert_kcb_cd']}',
|
||||
cf_cert_kcp_cd = '{$_POST['cf_cert_kcp_cd']}',
|
||||
cf_cert_kcp_enckey = '{$_POST['cf_cert_kcp_enckey']}',
|
||||
cf_cert_limit = '{$_POST['cf_cert_limit']}',
|
||||
cf_cert_req = '{$_POST['cf_cert_req']}',
|
||||
cf_sms_use = '{$_POST['cf_sms_use']}',
|
||||
cf_sms_type = '{$_POST['cf_sms_type']}',
|
||||
cf_icode_id = '{$_POST['cf_icode_id']}',
|
||||
cf_icode_pw = '{$_POST['cf_icode_pw']}',
|
||||
cf_icode_token_key = '{$_POST['cf_icode_token_key']}',
|
||||
cf_icode_server_ip = '{$_POST['cf_icode_server_ip']}',
|
||||
cf_icode_server_port = '{$_POST['cf_icode_server_port']}',
|
||||
cf_googl_shorturl_apikey = '{$_POST['cf_googl_shorturl_apikey']}',
|
||||
cf_kakao_js_apikey = '{$_POST['cf_kakao_js_apikey']}',
|
||||
cf_facebook_appid = '{$_POST['cf_facebook_appid']}',
|
||||
cf_facebook_secret = '{$_POST['cf_facebook_secret']}',
|
||||
cf_twitter_key = '{$_POST['cf_twitter_key']}',
|
||||
cf_twitter_secret = '{$_POST['cf_twitter_secret']}',
|
||||
cf_social_login_use = '{$_POST['cf_social_login_use']}',
|
||||
cf_naver_clientid = '{$_POST['cf_naver_clientid']}',
|
||||
cf_naver_secret = '{$_POST['cf_naver_secret']}',
|
||||
cf_google_clientid = '{$_POST['cf_google_clientid']}',
|
||||
cf_google_secret = '{$_POST['cf_google_secret']}',
|
||||
cf_kakao_rest_key = '{$_POST['cf_kakao_rest_key']}',
|
||||
cf_kakao_client_secret = '{$_POST['cf_kakao_client_secret']}',
|
||||
cf_social_servicelist = '{$cf_social_servicelist}',
|
||||
cf_captcha = '{$_POST['cf_captcha']}',
|
||||
cf_recaptcha_site_key = '{$_POST['cf_recaptcha_site_key']}',
|
||||
cf_recaptcha_secret_key = '{$_POST['cf_recaptcha_secret_key']}',
|
||||
cf_payco_clientid = '{$_POST['cf_payco_clientid']}',
|
||||
cf_payco_secret = '{$_POST['cf_payco_secret']}',
|
||||
cf_1_subj = '{$_POST['cf_1_subj']}',
|
||||
cf_2_subj = '{$_POST['cf_2_subj']}',
|
||||
cf_3_subj = '{$_POST['cf_3_subj']}',
|
||||
cf_4_subj = '{$_POST['cf_4_subj']}',
|
||||
cf_5_subj = '{$_POST['cf_5_subj']}',
|
||||
cf_6_subj = '{$_POST['cf_6_subj']}',
|
||||
cf_7_subj = '{$_POST['cf_7_subj']}',
|
||||
cf_8_subj = '{$_POST['cf_8_subj']}',
|
||||
cf_9_subj = '{$_POST['cf_9_subj']}',
|
||||
cf_10_subj = '{$_POST['cf_10_subj']}',
|
||||
cf_1 = '{$_POST['cf_1']}',
|
||||
cf_2 = '{$_POST['cf_2']}',
|
||||
cf_3 = '{$_POST['cf_3']}',
|
||||
cf_4 = '{$_POST['cf_4']}',
|
||||
cf_5 = '{$_POST['cf_5']}',
|
||||
cf_6 = '{$_POST['cf_6']}',
|
||||
cf_7 = '{$_POST['cf_7']}',
|
||||
cf_8 = '{$_POST['cf_8']}',
|
||||
cf_9 = '{$_POST['cf_9']}',
|
||||
cf_10 = '{$_POST['cf_10']}' ";
|
||||
sql_query($sql);
|
||||
|
||||
//sql_query(" OPTIMIZE TABLE `$g5[config_table]` ");
|
||||
|
||||
if (isset($_POST['cf_bbs_rewrite'])) {
|
||||
g5_delete_all_cache();
|
||||
}
|
||||
|
||||
if (function_exists('get_admin_captcha_by')) {
|
||||
get_admin_captcha_by('remove');
|
||||
}
|
||||
|
||||
run_event('admin_config_form_update');
|
||||
|
||||
update_rewrite_rules();
|
||||
|
||||
goto_url('./config_form.php', false);
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 관리 시스템 공통 파일 (관리자용)
|
||||
*/
|
||||
|
||||
define('G5_IS_ADMIN', true);
|
||||
include_once('../../common.php');
|
||||
include_once(G5_ADMIN_PATH . '/admin.lib.php');
|
||||
|
||||
// 💡 [개선] 공통 함수를 별도 파일로 분리하여 관리합니다.
|
||||
include_once(__DIR__ . '/functions.php');
|
||||
?>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) {
|
||||
// AJAX 요청 등으로 직접 접근했을 때, 그누보드 환경을 로드합니다.
|
||||
include_once('../../../common.php');
|
||||
}
|
||||
|
||||
// 💡 [개선] 공통 함수를 별도 파일로 분리하여 관리합니다.
|
||||
include_once(__DIR__ . '/functions.php');
|
||||
?>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 관리 시스템 메뉴
|
||||
*/
|
||||
|
||||
if (!defined('_GNUBOARD_'))
|
||||
exit;
|
||||
|
||||
// 메뉴 구조: array('메뉴코드', '메뉴명', '링크', '메뉴 ID', '아이콘 클래스 (옵션)')
|
||||
$menu['menu850'] = array(
|
||||
// 💡 [개선] 메뉴 그룹 대표 아이콘 추가 (예: fa-comments)
|
||||
array('850000', '상담관리', G5_ADMIN_URL . '/consultant_manage/dashboard.php', 'consultant_manage', 'fa-comments'),
|
||||
array('850100', '대시보드', G5_ADMIN_URL . '/consultant_manage/dashboard.php', 'consultant_dashboard'),
|
||||
array('850200', '예약 현황', G5_ADMIN_URL . '/consultant_manage/reservations.php', 'consultant_reservations'),
|
||||
array('850300', '빠른 스케줄 관리', G5_ADMIN_URL . '/consultant_manage/schedule_generate.php', 'consultant_schedule_quick'),
|
||||
array('850400', '통계 분석', G5_ADMIN_URL . '/consultant_manage/statistics.php', 'consultant_statistics'),
|
||||
// 💡 [개선] 설정 관련 메뉴를 '환경설정' 그룹으로 통합
|
||||
array('850500', '팝업 샘플', G5_ADMIN_URL . '/consultant_manage/sample_page.php', 'consultant_sample'),
|
||||
array('850600', '환경설정', G5_ADMIN_URL . '/consultant_manage/settings.php', 'consultant_settings_group', 'fa-cogs'),
|
||||
array('850610', '기본/운영 설정', G5_ADMIN_URL . '/consultant_manage/settings.php', 'consultant_settings'),
|
||||
array('850615', '리소스(상담사) 관리', G5_ADMIN_URL . '/consultant_manage/resources.php', 'consultant_resources'), // 💡 [추가] 리소스 관리 메뉴
|
||||
array('850620', '알림 템플릿', G5_ADMIN_URL . '/consultant_manage/templates.php', 'consultant_templates'),
|
||||
array('850630', '시스템 로그', G5_ADMIN_URL . '/consultant_manage/log_view.php', 'consultant_log_view'),
|
||||
array('850640', '설치/업데이트', G5_ADMIN_URL . '/consultant_manage/install.php', 'consultant_install'),
|
||||
// array('850900', '시스템 테스트', G5_ADMIN_URL . '/consultant_manage/test_system.php', 'consultant_test') // 개발용 메뉴는 주석 처리
|
||||
);
|
||||
?>
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* 스케줄 생성 AJAX 처리
|
||||
*/
|
||||
|
||||
include_once('./_common.php');
|
||||
|
||||
// 관리자 권한 확인
|
||||
if (!$is_admin) {
|
||||
die(json_encode(['success' => false, 'message' => '권한이 없습니다.']));
|
||||
}
|
||||
|
||||
// 설치 확인
|
||||
if (!is_consultant_installed()) {
|
||||
die(json_encode(['success' => false, 'message' => '상담 예약 시스템이 설치되지 않았습니다.']));
|
||||
}
|
||||
|
||||
$action = $_POST['action'] ?? '';
|
||||
$year = (int) ($_POST['year'] ?? date('Y'));
|
||||
$month = (int) ($_POST['month'] ?? date('n'));
|
||||
|
||||
require_once('classes/ScheduleGenerator.class.php');
|
||||
|
||||
try {
|
||||
$generator = new ScheduleGenerator();
|
||||
|
||||
switch ($action) {
|
||||
case 'generate_month':
|
||||
$result = $generator->generateMonth($year, $month);
|
||||
if ($result) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => "{$year}년 {$month}월 스케줄이 생성되었습니다."
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '스케줄 생성에 실패했습니다.'
|
||||
]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'check_conflicts':
|
||||
$conflicts = $generator->checkScheduleConflicts($year, $month);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'conflicts' => $conflicts,
|
||||
'message' => count($conflicts) > 0 ?
|
||||
count($conflicts) . '개의 충돌이 발견되었습니다.' :
|
||||
'충돌이 없습니다.'
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'regenerate_next_month':
|
||||
$nextMonth = date('n') == 12 ? 1 : date('n') + 1;
|
||||
$nextYear = date('n') == 12 ? date('Y') + 1 : date('Y');
|
||||
|
||||
$result = $generator->generateMonth($nextYear, $nextMonth);
|
||||
if ($result) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => "다음 달({$nextYear}년 {$nextMonth}월) 스케줄이 재생성되었습니다."
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '다음 달 스케줄 재생성에 실패했습니다.'
|
||||
]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['success' => false, 'message' => '잘못된 요청입니다.']);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '오류가 발생했습니다: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,470 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 관리 클래스
|
||||
*/
|
||||
|
||||
if (!defined('_GNUBOARD_'))
|
||||
exit;
|
||||
|
||||
class ReservationManager
|
||||
{
|
||||
|
||||
private $table_name = 'consultant_reservations';
|
||||
|
||||
/**
|
||||
* 예약 목록 조회
|
||||
*/
|
||||
public function getReservationList($status = '', $date = '', $page = 1, $per_page = 20, $search_type = '', $search_keyword = '')
|
||||
{
|
||||
try {
|
||||
$where_conditions = ["is_deleted = 0"];
|
||||
|
||||
// 상태 필터
|
||||
if (!empty($status)) {
|
||||
$where_conditions[] = "status = '" . sql_real_escape_string($status) . "'";
|
||||
}
|
||||
|
||||
// 날짜 필터
|
||||
if (!empty($date)) {
|
||||
$where_conditions[] = "reservation_date = '" . sql_real_escape_string($date) . "'";
|
||||
}
|
||||
|
||||
// 검색 조건
|
||||
if (!empty($search_keyword) && !empty($search_type)) {
|
||||
$search_keyword = sql_real_escape_string($search_keyword);
|
||||
switch ($search_type) {
|
||||
case 'customer_name':
|
||||
$where_conditions[] = "customer_name LIKE '%{$search_keyword}%'";
|
||||
break;
|
||||
case 'customer_phone':
|
||||
$where_conditions[] = "customer_phone LIKE '%{$search_keyword}%'";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$where_clause = implode(' AND ', $where_conditions);
|
||||
|
||||
// 전체 개수 조회
|
||||
$count_sql = "SELECT COUNT(*) as total FROM {$this->table_name} WHERE {$where_clause}";
|
||||
$count_result = sql_fetch($count_sql);
|
||||
$total = $count_result['total'];
|
||||
|
||||
// 페이징 계산
|
||||
$offset = ($page - 1) * $per_page;
|
||||
$total_pages = ceil($total / $per_page);
|
||||
|
||||
// 목록 조회
|
||||
$sql = "SELECT * FROM {$this->table_name}
|
||||
WHERE {$where_clause}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT {$offset}, {$per_page}";
|
||||
|
||||
$reservations = [];
|
||||
$result = sql_query($sql);
|
||||
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$reservations[] = $row;
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'reservations' => $reservations,
|
||||
'pagination' => [
|
||||
'current_page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'total' => $total,
|
||||
'total_pages' => $total_pages
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
consultant_log("예약 목록 조회 실패: " . $e->getMessage(), 'error');
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '예약 목록 조회 중 오류가 발생했습니다.'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 생성
|
||||
*/
|
||||
public function createReservation($data)
|
||||
{
|
||||
try {
|
||||
// 필수 필드 검증
|
||||
$required_fields = ['customer_name', 'customer_phone', 'reservation_date', 'reservation_time'];
|
||||
foreach ($required_fields as $field) {
|
||||
if (empty($data[$field])) {
|
||||
throw new Exception("필수 필드가 누락되었습니다: {$field}");
|
||||
}
|
||||
}
|
||||
|
||||
// 중복 예약 확인
|
||||
$check_sql = "SELECT COUNT(*) as count FROM {$this->table_name}
|
||||
WHERE reservation_date = '" . sql_real_escape_string($data['reservation_date']) . "'
|
||||
AND reservation_time = '" . sql_real_escape_string($data['reservation_time']) . "'
|
||||
AND status IN ('payment_pending', 'reserved')
|
||||
AND is_deleted = 0";
|
||||
|
||||
$check_result = sql_fetch($check_sql);
|
||||
$current_count = $check_result['count'];
|
||||
|
||||
// 최대 인원 확인 (기본값 2명)
|
||||
$max_persons = consultant_get_config('default_max_persons', 2);
|
||||
if ($current_count >= $max_persons) {
|
||||
throw new Exception('해당 시간대는 예약이 마감되었습니다.');
|
||||
}
|
||||
|
||||
// 데이터 준비
|
||||
$insert_data = [
|
||||
'customer_name' => sql_real_escape_string($data['customer_name']),
|
||||
'customer_phone' => sql_real_escape_string($data['customer_phone']),
|
||||
'customer_email' => sql_real_escape_string($data['customer_email'] ?? ''),
|
||||
'reservation_date' => sql_real_escape_string($data['reservation_date']),
|
||||
'reservation_time' => sql_real_escape_string($data['reservation_time']),
|
||||
'consultation_type' => sql_real_escape_string($data['consultation_type'] ?? 'onsite'),
|
||||
'status' => 'payment_pending',
|
||||
'payment_amount' => (int) ($data['payment_amount'] ?? consultant_get_config('consultation_fee', 50000)),
|
||||
'payment_status' => 'pending',
|
||||
'request_memo' => sql_real_escape_string($data['request_memo'] ?? ''),
|
||||
'wr_id' => (int) ($data['wr_id'] ?? 0),
|
||||
'temp_1' => sql_real_escape_string($data['temp_1'] ?? ''),
|
||||
'created_at' => 'NOW()',
|
||||
'updated_at' => 'NOW()'
|
||||
];
|
||||
|
||||
// SQL 생성
|
||||
$fields = implode(', ', array_keys($insert_data));
|
||||
$values = "'" . implode("', '", array_values($insert_data)) . "'";
|
||||
$values = str_replace("'NOW()'", "NOW()", $values); // NOW() 함수 처리
|
||||
|
||||
$sql = "INSERT INTO {$this->table_name} ({$fields}) VALUES ({$values})";
|
||||
|
||||
if (!sql_query($sql)) {
|
||||
throw new Exception('데이터베이스 저장 실패: ' . sql_error());
|
||||
}
|
||||
|
||||
$reservation_id = sql_insert_id();
|
||||
|
||||
// 알림 발송
|
||||
$this->sendReservationNotification($reservation_id, 'created');
|
||||
|
||||
consultant_log("새 예약 생성: ID {$reservation_id}, 고객: {$data['customer_name']}");
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => '예약이 성공적으로 접수되었습니다.',
|
||||
'data' => [
|
||||
'reservation_id' => $reservation_id
|
||||
]
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
consultant_log("예약 생성 실패: " . $e->getMessage(), 'error');
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 상태 변경
|
||||
*/
|
||||
public function updateReservationStatus($reservation_id, $new_status, $admin_memo = '')
|
||||
{
|
||||
try {
|
||||
// 예약 정보 조회
|
||||
$reservation = $this->getReservationById($reservation_id);
|
||||
if (!$reservation) {
|
||||
throw new Exception('예약 정보를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 상태 변경 가능 여부 확인
|
||||
$valid_transitions = [
|
||||
'payment_pending' => ['reserved', 'cancelled'],
|
||||
'reserved' => ['completed', 'cancelled'],
|
||||
'completed' => [],
|
||||
'cancelled' => []
|
||||
];
|
||||
|
||||
$current_status = $reservation['status'];
|
||||
if (!in_array($new_status, $valid_transitions[$current_status])) {
|
||||
throw new Exception('현재 상태에서 해당 상태로 변경할 수 없습니다.');
|
||||
}
|
||||
|
||||
// 상태 업데이트
|
||||
$sql = "UPDATE {$this->table_name}
|
||||
SET status = '" . sql_real_escape_string($new_status) . "',
|
||||
admin_memo = '" . sql_real_escape_string($admin_memo) . "',
|
||||
updated_at = NOW()
|
||||
WHERE id = {$reservation_id}";
|
||||
|
||||
if (!sql_query($sql)) {
|
||||
throw new Exception('상태 변경 실패: ' . sql_error());
|
||||
}
|
||||
|
||||
// 알림 발송
|
||||
$this->sendReservationNotification($reservation_id, 'status_changed', $new_status);
|
||||
|
||||
consultant_log("예약 상태 변경: ID {$reservation_id}, {$current_status} -> {$new_status}");
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => '예약 상태가 성공적으로 변경되었습니다.'
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
consultant_log("예약 상태 변경 실패: " . $e->getMessage(), 'error');
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 입금 확인
|
||||
*/
|
||||
public function confirmPayment($reservation_id, $admin_id)
|
||||
{
|
||||
try {
|
||||
// 예약 정보 조회
|
||||
$reservation = $this->getReservationById($reservation_id);
|
||||
if (!$reservation) {
|
||||
throw new Exception('예약 정보를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
if ($reservation['status'] !== 'payment_pending') {
|
||||
throw new Exception('입금 대기 상태의 예약만 확인할 수 있습니다.');
|
||||
}
|
||||
|
||||
// 입금 확인 처리
|
||||
$sql = "UPDATE {$this->table_name}
|
||||
SET status = 'reserved',
|
||||
payment_status = 'paid',
|
||||
payment_confirmed_at = NOW(),
|
||||
payment_confirmed_by = '" . sql_real_escape_string($admin_id) . "',
|
||||
updated_at = NOW()
|
||||
WHERE id = {$reservation_id}";
|
||||
|
||||
if (!sql_query($sql)) {
|
||||
throw new Exception('입금 확인 처리 실패: ' . sql_error());
|
||||
}
|
||||
|
||||
// 알림 발송
|
||||
$this->sendReservationNotification($reservation_id, 'payment_confirmed');
|
||||
|
||||
consultant_log("입금 확인: ID {$reservation_id}, 확인자: {$admin_id}");
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => '입금이 확인되어 예약이 확정되었습니다.'
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
consultant_log("입금 확인 실패: " . $e->getMessage(), 'error');
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 취소
|
||||
*/
|
||||
public function cancelReservation($reservation_id, $reason)
|
||||
{
|
||||
try {
|
||||
// 예약 정보 조회
|
||||
$reservation = $this->getReservationById($reservation_id);
|
||||
if (!$reservation) {
|
||||
throw new Exception('예약 정보를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
if ($reservation['status'] === 'cancelled') {
|
||||
throw new Exception('이미 취소된 예약입니다.');
|
||||
}
|
||||
|
||||
if ($reservation['status'] === 'completed') {
|
||||
throw new Exception('완료된 예약은 취소할 수 없습니다.');
|
||||
}
|
||||
|
||||
// 예약 취소 처리
|
||||
$sql = "UPDATE {$this->table_name}
|
||||
SET status = 'cancelled',
|
||||
admin_memo = '" . sql_real_escape_string($reason) . "',
|
||||
updated_at = NOW()
|
||||
WHERE id = {$reservation_id}";
|
||||
|
||||
if (!sql_query($sql)) {
|
||||
throw new Exception('예약 취소 처리 실패: ' . sql_error());
|
||||
}
|
||||
|
||||
// 알림 발송
|
||||
$this->sendReservationNotification($reservation_id, 'cancelled', $reason);
|
||||
|
||||
consultant_log("예약 취소: ID {$reservation_id}, 사유: {$reason}");
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => '예약이 성공적으로 취소되었습니다.'
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
consultant_log("예약 취소 실패: " . $e->getMessage(), 'error');
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 정보 조회
|
||||
*/
|
||||
public function getReservationById($reservation_id)
|
||||
{
|
||||
$sql = "SELECT * FROM {$this->table_name} WHERE id = {$reservation_id} AND is_deleted = 0";
|
||||
return sql_fetch($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 알림 발송
|
||||
*/
|
||||
private function sendReservationNotification($reservation_id, $type, $extra_data = null)
|
||||
{
|
||||
try {
|
||||
$reservation = $this->getReservationById($reservation_id);
|
||||
if (!$reservation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 알림 템플릿 키 결정
|
||||
$template_keys = [
|
||||
'created' => 'consultant_reservation_customer',
|
||||
'payment_confirmed' => 'consultant_confirmed_customer',
|
||||
'cancelled' => 'consultant_cancelled_customer',
|
||||
'status_changed' => 'consultant_status_changed_customer'
|
||||
];
|
||||
|
||||
$template_key = $template_keys[$type] ?? null;
|
||||
if (!$template_key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 템플릿 변수 준비
|
||||
$variables = [
|
||||
'customer_name' => $reservation['customer_name'],
|
||||
'customer_phone' => $reservation['customer_phone'],
|
||||
'customer_email' => $reservation['customer_email'],
|
||||
'reservation_date' => $reservation['reservation_date'],
|
||||
'reservation_time' => $reservation['reservation_time'],
|
||||
'payment_amount' => number_format($reservation['payment_amount']),
|
||||
'account_info' => consultant_get_config('account_info', ''),
|
||||
'cancel_reason' => $extra_data ?? ''
|
||||
];
|
||||
|
||||
// 이메일 발송
|
||||
if (!empty($reservation['customer_email'])) {
|
||||
$this->sendEmailNotification($reservation['customer_email'], $template_key, $variables);
|
||||
}
|
||||
|
||||
// SMS 발송
|
||||
if (!empty($reservation['customer_phone'])) {
|
||||
$this->sendSmsNotification($reservation['customer_phone'], $template_key, $variables);
|
||||
}
|
||||
|
||||
// 관리자 알림 (새 예약 시)
|
||||
if ($type === 'created') {
|
||||
$this->sendAdminNotification($reservation, $variables);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
consultant_log("알림 발송 실패: " . $e->getMessage(), 'error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 이메일 알림 발송
|
||||
*/
|
||||
private function sendEmailNotification($email, $template_key, $variables)
|
||||
{
|
||||
// 💡 [수정] consultant_send_notification 함수 사용으로 변경
|
||||
return consultant_send_notification('email', $template_key, array_merge($variables, ['customer_email' => $email]));
|
||||
}
|
||||
|
||||
/**
|
||||
* SMS 알림 발송
|
||||
*/
|
||||
private function sendSmsNotification($phone, $template_key, $variables)
|
||||
{
|
||||
// 💡 [수정] consultant_send_notification 함수 사용으로 변경
|
||||
return consultant_send_notification('sms', $template_key, array_merge($variables, ['customer_phone' => $phone]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 알림 발송
|
||||
*/
|
||||
private function sendAdminNotification($reservation, $variables)
|
||||
{
|
||||
// 관리자 이메일 알림
|
||||
$admin_template_key = 'consultant_reservation_admin';
|
||||
|
||||
// 관리자 이메일 주소 (설정에서 가져오거나 기본값 사용)
|
||||
$admin_email = consultant_get_config('admin_email', get_admin_email());
|
||||
|
||||
if ($admin_email) {
|
||||
// 💡 [수정] consultant_send_notification 함수 사용으로 변경
|
||||
consultant_send_notification('email', $admin_template_key, array_merge($variables, ['customer_email' => $admin_email]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 변수 치환
|
||||
*/
|
||||
private function replaceTemplateVariables($template, $variables)
|
||||
{
|
||||
foreach ($variables as $key => $value) {
|
||||
$template = str_replace('{' . $key . '}', $value, $template);
|
||||
}
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 통계 조회
|
||||
*/
|
||||
public function getReservationStats($start_date = null, $end_date = null)
|
||||
{
|
||||
try {
|
||||
if (!$start_date)
|
||||
$start_date = date('Y-m-01'); // 이번 달 첫날
|
||||
if (!$end_date)
|
||||
$end_date = date('Y-m-d'); // 오늘
|
||||
|
||||
$sql = "SELECT
|
||||
COUNT(*) as total,
|
||||
COUNT(CASE WHEN status = 'payment_pending' THEN 1 END) as pending,
|
||||
COUNT(CASE WHEN status = 'reserved' THEN 1 END) as confirmed,
|
||||
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed,
|
||||
COUNT(CASE WHEN status = 'cancelled' THEN 1 END) as cancelled,
|
||||
SUM(CASE WHEN status = 'completed' THEN payment_amount ELSE 0 END) as total_revenue
|
||||
FROM {$this->table_name}
|
||||
WHERE reservation_date BETWEEN '{$start_date}' AND '{$end_date}'
|
||||
AND is_deleted = 0";
|
||||
|
||||
return sql_fetch($sql);
|
||||
|
||||
} catch (Exception $e) {
|
||||
consultant_log("통계 조회 실패: " . $e->getMessage(), 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 시스템 - 월별 스케줄 자동 생성 클래스
|
||||
*
|
||||
* 요일별 설정을 기반으로 월별 상세 스케줄을 자동 생성하고 관리합니다.
|
||||
*/
|
||||
|
||||
class ScheduleGenerator
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $connect_db;
|
||||
$this->db = $connect_db;
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 월의 전체 스케줄 생성
|
||||
*
|
||||
* @param int $year 년도
|
||||
* @param int $month 월
|
||||
* @return bool 성공 여부
|
||||
*/
|
||||
public function generateMonth($year, $month)
|
||||
{
|
||||
try {
|
||||
// 기본 설정 조회
|
||||
$basicSettings = $this->getBasicSettings();
|
||||
|
||||
// 요일별 설정 조회
|
||||
$weeklySettings = $this->getWeeklySettings();
|
||||
|
||||
// 해당 월의 모든 날짜 생성
|
||||
$dates = self::getMonthDates($year, $month);
|
||||
|
||||
// 기존 스케줄 중 예약이 없는 것들 삭제 (재생성을 위해)
|
||||
$this->clearAutoGeneratedSchedules($year, $month);
|
||||
|
||||
// 각 날짜별로 스케줄 생성
|
||||
foreach ($dates as $date) {
|
||||
$dayOfWeek = date('N', strtotime($date)); // 1=월요일, 7=일요일
|
||||
$this->generateDay($date, $dayOfWeek, $weeklySettings, $basicSettings);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
consultant_log("스케줄 생성 실패: " . $e->getMessage(), 'error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 날짜의 스케줄 생성
|
||||
*/
|
||||
private function generateDay($date, $dayOfWeek, $weeklySettings, $basicSettings)
|
||||
{
|
||||
$dayNames = ['', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
||||
$dayName = $dayNames[$dayOfWeek];
|
||||
|
||||
// 해당 요일이 운영일인지 확인
|
||||
$isEnabled = $weeklySettings[$dayName . '_enabled'] ?? '0';
|
||||
|
||||
if ($isEnabled == '0') {
|
||||
// 휴무일 처리
|
||||
$this->insertHolidaySchedule($date);
|
||||
return;
|
||||
}
|
||||
|
||||
// 운영시간 정보
|
||||
$startTime = $weeklySettings[$dayName . '_start'] ?? '09:00';
|
||||
$endTime = $weeklySettings[$dayName . '_end'] ?? '18:00';
|
||||
$lunchStart = $weeklySettings[$dayName . '_lunch_start'] ?? '12:00';
|
||||
$lunchEnd = $weeklySettings[$dayName . '_lunch_end'] ?? '13:00';
|
||||
|
||||
// 시간 슬롯 생성
|
||||
$this->createTimeSlots($date, $startTime, $endTime, $basicSettings['consultation_duration'], $basicSettings['max_persons_per_slot']);
|
||||
|
||||
// 점심시간 블록 처리
|
||||
// 💡 [수정] 시작 시간과 종료 시간이 다를 때만 점심시간으로 처리합니다.
|
||||
if ($lunchStart && $lunchEnd && strtotime($lunchStart) < strtotime($lunchEnd)) {
|
||||
$this->blockLunchTime($date, $lunchStart, $lunchEnd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 시간 슬롯 생성
|
||||
*/
|
||||
private function createTimeSlots($date, $startTime, $endTime, $slotDuration, $maxPersons)
|
||||
{
|
||||
$currentTime = strtotime($startTime);
|
||||
$endTimeStamp = strtotime($endTime);
|
||||
$slotMinutes = (int) $slotDuration;
|
||||
|
||||
while ($currentTime < $endTimeStamp) {
|
||||
$slotStart = date('H:i', $currentTime);
|
||||
$slotEnd = date('H:i', $currentTime + ($slotMinutes * 60));
|
||||
|
||||
// 종료시간을 넘지 않는 경우만 생성
|
||||
if (strtotime($slotEnd) <= $endTimeStamp) {
|
||||
// 재생성 시 중복 생성을 방지하기 위해 기존 슬롯이 있는지 확인
|
||||
$check_sql = "SELECT id FROM consultant_schedule
|
||||
WHERE specific_date = '" . sql_real_escape_string($date) . "'
|
||||
AND start_time = '" . sql_real_escape_string($slotStart) . "'";
|
||||
|
||||
$existing_slot = sql_fetch($check_sql);
|
||||
|
||||
// 기존에 수동으로 추가되었거나 예약으로 보호된 슬롯이 없으면 새로 생성
|
||||
if (!$existing_slot) {
|
||||
$sql = "INSERT INTO consultant_schedule
|
||||
(specific_date, start_time, end_time, time_slot, max_persons, is_available, temp_1, created_at)
|
||||
VALUES (
|
||||
'" . sql_real_escape_string($date) . "',
|
||||
'" . sql_real_escape_string($slotStart) . "',
|
||||
'" . sql_real_escape_string($slotEnd) . "',
|
||||
" . (int) $slotMinutes . ",
|
||||
" . (int) $maxPersons . ",
|
||||
1,
|
||||
'auto_generated',
|
||||
NOW()
|
||||
)";
|
||||
|
||||
if (!sql_query($sql)) {
|
||||
// sql_query가 false를 반환하면 오류를 던짐
|
||||
throw new Exception("시간 슬롯 생성 실패: " . sql_error());
|
||||
}
|
||||
}
|
||||
}
|
||||
$currentTime += ($slotMinutes * 60);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 점심시간 블록 처리
|
||||
*/
|
||||
private function blockLunchTime($date, $lunchStart, $lunchEnd)
|
||||
{
|
||||
$sql = "UPDATE consultant_schedule
|
||||
SET is_available = 0, temp_1 = 'lunch_time', temp_2 = '점심시간', updated_at = NOW()
|
||||
WHERE specific_date = '" . sql_real_escape_string($date) . "'
|
||||
-- 💡 [수정] start_time이 휴게시간 범위에 포함되는 모든 슬롯을 대상으로 하도록 변경합니다.
|
||||
-- 이렇게 하면 상담 시간 단위(30분, 60분 등)에 상관없이 정확하게 동작합니다.
|
||||
AND start_time >= '" . sql_real_escape_string($lunchStart) . "' AND start_time < '" . sql_real_escape_string($lunchEnd) . "'";
|
||||
|
||||
sql_query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴무일 스케줄 삽입
|
||||
*/
|
||||
private function insertHolidaySchedule($date)
|
||||
{
|
||||
// 💡 [수정] 휴무일 데이터 중복 생성을 방지하기 위해, 해당 날짜에 이미 스케줄이 있는지 확인합니다.
|
||||
$check_sql = "SELECT id FROM consultant_schedule WHERE specific_date = '" . sql_real_escape_string($date) . "'";
|
||||
$existing_schedule = sql_fetch($check_sql);
|
||||
|
||||
// 해당 날짜에 아무 스케줄도 없을 때만 휴무일 데이터를 삽입합니다.
|
||||
if (!$existing_schedule) {
|
||||
$sql = "INSERT INTO consultant_schedule
|
||||
(specific_date, start_time, end_time, time_slot, max_persons, is_available, temp_1, temp_2, created_at)
|
||||
VALUES (
|
||||
'" . sql_real_escape_string($date) . "',
|
||||
'00:00',
|
||||
'23:59',
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
'holiday',
|
||||
'휴무일',
|
||||
NOW()
|
||||
)";
|
||||
|
||||
sql_query($sql);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 예약 보호 로직 - 예약이 있는 시간대는 삭제하지 않음
|
||||
*/
|
||||
private function protectExistingReservations($year, $month)
|
||||
{
|
||||
$startDate = sprintf('%04d-%02d-01', $year, $month);
|
||||
$endDate = date('Y-m-t', strtotime($startDate));
|
||||
|
||||
// 해당 월에 예약이 있는 스케줄 ID 조회
|
||||
$sql = "SELECT DISTINCT s.id
|
||||
FROM consultant_schedule s
|
||||
INNER JOIN consultant_reservations r ON (
|
||||
s.specific_date = r.reservation_date
|
||||
AND s.start_time = r.reservation_time
|
||||
)
|
||||
WHERE s.specific_date BETWEEN '{$startDate}' AND '{$endDate}'
|
||||
AND r.status NOT IN ('cancelled')
|
||||
AND r.is_deleted = 0";
|
||||
|
||||
$result = sql_query($sql);
|
||||
$protectedIds = [];
|
||||
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$protectedIds[] = $row['id'];
|
||||
}
|
||||
|
||||
return $protectedIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 변경으로 인한 기존 예약과의 충돌 감지 (향상된 로직)
|
||||
* - 휴무일로 변경된 경우
|
||||
* - 운영 시간 밖으로 밀려난 경우
|
||||
* - 점심시간과 겹치는 경우
|
||||
*/
|
||||
public function findConflictsWithNewSettings($year, $month)
|
||||
{
|
||||
$conflicts = [];
|
||||
$startDate = sprintf('%04d-%02d-01', $year, $month);
|
||||
$endDate = date('Y-m-t', strtotime($startDate));
|
||||
|
||||
// 해당 월의 모든 예약 조회
|
||||
$sql = "SELECT r.reservation_date, r.reservation_time, r.customer_name, r.customer_phone
|
||||
FROM consultant_reservations r
|
||||
WHERE r.reservation_date BETWEEN '{$startDate}' AND '{$endDate}'
|
||||
AND r.status NOT IN ('cancelled')
|
||||
AND r.is_deleted = 0";
|
||||
|
||||
$weeklySettings = $this->getWeeklySettings();
|
||||
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$dayOfWeek = date('N', strtotime($row['reservation_date']));
|
||||
$dayNames = ['', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
||||
$dayName = $dayNames[$dayOfWeek];
|
||||
|
||||
$isEnabled = $weeklySettings[$dayName . '_enabled'] ?? '0';
|
||||
$startTime = $weeklySettings[$dayName . '_start'] ?? '09:00';
|
||||
$endTime = $weeklySettings[$dayName . '_end'] ?? '18:00';
|
||||
$lunchStart = $weeklySettings[$dayName . '_lunch_start'] ?? '12:00';
|
||||
$lunchEnd = $weeklySettings[$dayName . '_lunch_end'] ?? '13:00';
|
||||
|
||||
$reservationTime = $row['reservation_time'];
|
||||
$conflictReason = '';
|
||||
|
||||
if ($isEnabled == '0') {
|
||||
$conflictReason = '휴무일로 변경됨';
|
||||
} elseif (strtotime($reservationTime) < strtotime($startTime) || strtotime($reservationTime) >= strtotime($endTime)) {
|
||||
$conflictReason = '운영 시간 벗어남';
|
||||
} elseif (strtotime($reservationTime) >= strtotime($lunchStart) && strtotime($reservationTime) < strtotime($lunchEnd)) {
|
||||
$conflictReason = '점심시간과 겹침';
|
||||
}
|
||||
|
||||
if ($conflictReason) {
|
||||
$conflicts[] = [
|
||||
'date' => $row['reservation_date'],
|
||||
'time' => $reservationTime,
|
||||
'customer' => $row['customer_name'],
|
||||
'phone' => $row['customer_phone'],
|
||||
'reason' => $conflictReason
|
||||
];
|
||||
}
|
||||
}
|
||||
return $conflicts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 자동 생성된 스케줄 삭제 (예약이 없는 것만)
|
||||
*/
|
||||
private function clearAutoGeneratedSchedules($year, $month)
|
||||
{
|
||||
$startDate = sprintf('%04d-%02d-01', (int)$year, (int)$month);
|
||||
$endDate = date('Y-m-t', strtotime($startDate));
|
||||
|
||||
// 예약이 있는 '시간 슬롯'의 ID를 보호
|
||||
$protectedIds = $this->protectExistingReservations($year, $month);
|
||||
|
||||
// 💡 [수정] 예약이 있는 날짜라도 'holiday' 타입의 스케줄은 삭제될 수 있도록 temp_1 조건만 사용합니다.
|
||||
$whereClause = "specific_date BETWEEN '{$startDate}' AND '{$endDate}'
|
||||
AND temp_1 IN ('auto_generated', 'lunch_time', 'holiday', 'manual_block')";
|
||||
|
||||
if (!empty($protectedIds)) {
|
||||
$whereClause .= " AND id NOT IN (" . implode(',', $protectedIds) . ")";
|
||||
}
|
||||
|
||||
$sql = "DELETE FROM consultant_schedule WHERE " . $whereClause;
|
||||
sql_query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 해당 월의 모든 날짜 배열 생성
|
||||
*/
|
||||
private static function getMonthDates($year, $month)
|
||||
{
|
||||
$dates = [];
|
||||
$daysInMonth = date('t', mktime(0, 0, 0, $month, 1, $year));
|
||||
|
||||
for ($day = 1; $day <= $daysInMonth; $day++) {
|
||||
$dates[] = sprintf('%04d-%02d-%02d', $year, $month, $day);
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본 설정 조회
|
||||
*/
|
||||
private function getBasicSettings()
|
||||
{
|
||||
return [
|
||||
'consultation_duration' => (int) consultant_get_config('consultation_duration', 60),
|
||||
'max_persons_per_slot' => (int) consultant_get_config('max_persons_per_slot', 2)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 요일별 설정 조회
|
||||
*/
|
||||
private function getWeeklySettings()
|
||||
{
|
||||
$days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
||||
$settings = [];
|
||||
|
||||
foreach ($days as $day) {
|
||||
$settings[$day . '_enabled'] = consultant_get_config($day . '_enabled', $day == 'saturday' || $day == 'sunday' ? '0' : '1');
|
||||
$settings[$day . '_start'] = consultant_get_config($day . '_start', '09:00');
|
||||
$settings[$day . '_end'] = consultant_get_config($day . '_end', '18:00');
|
||||
$settings[$day . '_lunch_start'] = consultant_get_config($day . '_lunch_start', '12:00');
|
||||
$settings[$day . '_lunch_end'] = consultant_get_config($day . '_lunch_end', '13:00');
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,384 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_'))
|
||||
exit;
|
||||
|
||||
// ❗ [핵심] 필요한 클래스와 라이브러리를 포함합니다.
|
||||
if (file_exists(G5_ADMIN_PATH . '/mail_manage/classes/MailSender.php')) {
|
||||
require_once(G5_ADMIN_PATH . '/mail_manage/classes/MailSender.php');
|
||||
}
|
||||
if (file_exists(G5_PLUGIN_PATH . '/sms5/sms5.lib.php')) {
|
||||
include_once(G5_PLUGIN_PATH . '/sms5/sms5.lib.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* 통합 메일/SMS 발송 클래스
|
||||
* 기존 mail_manage, sms_admin 시스템을 활용하여 발송 및 이력 기록
|
||||
*/
|
||||
class NotificationSender
|
||||
{
|
||||
private $g5;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $g5;
|
||||
$this->g5 = $g5;
|
||||
}
|
||||
|
||||
/**
|
||||
* ❗ [핵심 수정] 템플릿 기반 메인 발송 함수
|
||||
* @param array $params 발송 파라미터
|
||||
* @return array 발송 결과
|
||||
*/
|
||||
public function send($params)
|
||||
{
|
||||
// 파라미터 검증
|
||||
$validated = $this->validateParams($params);
|
||||
if (!$validated['success']) {
|
||||
return $validated;
|
||||
}
|
||||
|
||||
// 대상 회원 조회
|
||||
$members = $this->getTargetMembers($params);
|
||||
if (empty($members)) {
|
||||
return ['success' => false, 'message' => '발송 대상 회원이 없습니다.'];
|
||||
}
|
||||
|
||||
$results = [
|
||||
'success' => true,
|
||||
'total_targets' => count($members),
|
||||
'sms_success' => 0,
|
||||
'sms_fail' => 0,
|
||||
'email_success' => 0,
|
||||
'email_fail' => 0,
|
||||
'message' => ''
|
||||
];
|
||||
|
||||
// SMS 발송
|
||||
if (!empty($params['sms_template_key'])) {
|
||||
$sms_result = $this->sendSMS($members, $params['sms_template_key'], $params['vars'] ?? []);
|
||||
$results['sms_success'] = $sms_result['success'];
|
||||
$results['sms_fail'] = $sms_result['fail'];
|
||||
}
|
||||
|
||||
// 이메일 발송
|
||||
if (!empty($params['email_template_key'])) {
|
||||
$email_result = $this->sendEmail($members, $params['email_template_key'], $params['vars'] ?? []);
|
||||
$results['email_success'] = $email_result['success'];
|
||||
$results['email_fail'] = $email_result['fail'];
|
||||
}
|
||||
|
||||
$results['message'] = $this->generateResultMessage($results);
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* ❗ [핵심 수정] 템플릿 기반 파라미터 검증
|
||||
*/
|
||||
private function validateParams($params)
|
||||
{
|
||||
if (empty($params['target_type'])) {
|
||||
return ['success' => false, 'message' => "필수 파라미터 'target_type'이 누락되었습니다."];
|
||||
}
|
||||
if ($params['target_type'] === 'single' && empty($params['member_id'])) {
|
||||
return ['success' => false, 'message' => '단일 발송 시 회원 ID(member_id)가 필요합니다.'];
|
||||
}
|
||||
if ($params['target_type'] === 'bulk' && empty($params['member_levels'])) {
|
||||
return ['success' => false, 'message' => '대량 발송 시 회원 레벨(member_levels)이 필요합니다.'];
|
||||
}
|
||||
if (empty($params['sms_template_key']) && empty($params['email_template_key'])) {
|
||||
return ['success' => false, 'message' => 'SMS 또는 이메일 템플릿 키 중 하나는 반드시 필요합니다.'];
|
||||
}
|
||||
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
/**
|
||||
* 대상 회원 조회
|
||||
*/
|
||||
private function getTargetMembers($params)
|
||||
{
|
||||
$members = [];
|
||||
$member_table = $this->g5['member_table'];
|
||||
|
||||
if ($params['target_type'] === 'single') {
|
||||
$sql = "SELECT mb_id, mb_name, mb_hp, mb_email, mb_sms, mb_mailling FROM `{$member_table}` WHERE mb_id = '" . sql_real_escape_string($params['member_id']) . "' AND mb_leave_date = '' AND mb_intercept_date = ''";
|
||||
} else {
|
||||
$levels = array_map('intval', $params['member_levels']);
|
||||
$level_condition = implode(',', $levels);
|
||||
$sql = "SELECT mb_id, mb_name, mb_hp, mb_email, mb_sms, mb_mailling FROM `{$member_table}` WHERE mb_level IN ({$level_condition}) AND mb_leave_date = '' AND mb_intercept_date = ''";
|
||||
}
|
||||
$this->write_debug_log("[SMS 발송 시작] sql '{$sql}'");
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$members[] = $row;
|
||||
}
|
||||
return $members;
|
||||
}
|
||||
|
||||
/**
|
||||
* ❗ [핵심 수정] SMS 발송 (템플릿 및 변수 처리, 이력 기록 포함)
|
||||
*/
|
||||
private function sendSMS($members, $template_key, $common_vars)
|
||||
{
|
||||
$success = 0;
|
||||
$fail = 0;
|
||||
$notification_mode = get_order_config('notification_mode', 'log');
|
||||
$is_test_mode = ($notification_mode !== 'send');
|
||||
|
||||
$sizeof_members = count($members);
|
||||
|
||||
// 💡 [수정] 템플릿 조회 로직 변경: 지정 테이블(consultant_sms_templates) -> 기본 테이블(sms_templates) 순서로 조회
|
||||
$template = null;
|
||||
|
||||
// 1. 지정 테이블 조회
|
||||
$check_table = sql_query("SHOW TABLES LIKE 'consultant_sms_templates'", false);
|
||||
if (sql_num_rows($check_table) > 0) {
|
||||
$template = sql_fetch("SELECT * FROM `consultant_sms_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'");
|
||||
if ($template) {
|
||||
// 필드명 통일 (기본 테이블과 필드명이 다를 수 있음)
|
||||
$template['content'] = $template['template_content'];
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 기본 테이블 조회 (지정 테이블에 없을 경우)
|
||||
if (!$template) {
|
||||
$template = sql_fetch("SELECT * FROM `sms_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'");
|
||||
}
|
||||
|
||||
// 3. 둘 다 없으면 에러 처리
|
||||
if (!$template) {
|
||||
$this->write_debug_log("[SMS 발송 오류] 템플릿 '{$template_key}'을(를) 찾을 수 없습니다.");
|
||||
return ['success' => 0, 'fail' => count($members)];
|
||||
}
|
||||
|
||||
$count= count($members);
|
||||
if ($is_test_mode) {
|
||||
// --- 개발 모드: 로그 파일에만 기록 ---
|
||||
foreach ($members as $member) {
|
||||
if ($member['mb_sms'] && !empty($member['mb_hp'])) {
|
||||
$personal_vars = array_merge($common_vars, ['이름' => $member['mb_name'], 'agent_name' => $member['mb_name'], 'dealer_name' => $member['mb_name']]);
|
||||
$personal_message = $template['content'];
|
||||
foreach ($personal_vars as $key => $value) {
|
||||
$personal_message = str_replace('{' . $key . '}', $value, $personal_message);
|
||||
}
|
||||
$this->write_debug_log("[SMS LOG] To: {$member['mb_hp']}, Content: {$personal_message}");
|
||||
$success++;
|
||||
} else {
|
||||
$empty = !empty($member['mb_hp']);
|
||||
$this->write_debug_log("[SMS 발송오류] 사용자 '{$member['mb_sms']}' '{$member['mb_hp']}' '$empty'");
|
||||
$fail++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// --- 실제 발송 모드: DB 기록 및 실제 발송 ---
|
||||
$sms_config = sql_fetch("SELECT * FROM {$this->g5['sms5_config_table']}");
|
||||
$send_phone = $sms_config['cf_phone'];
|
||||
|
||||
$wr_message = sql_real_escape_string($template['content']);
|
||||
$wr_reply = sql_real_escape_string($send_phone);
|
||||
sql_query("INSERT INTO {$this->g5['sms5_write_table']} (wr_message, wr_reply, wr_total, wr_datetime) VALUES ('{$wr_message}', '{$wr_reply}', '" . count($members) . "', '" . G5_TIME_YMDHIS . "')");
|
||||
$wr_no = sql_insert_id();
|
||||
|
||||
$SMS = null;
|
||||
if (class_exists('SMS5')) {
|
||||
$SMS = new SMS5;
|
||||
$SMS->SMS_con($sms_config['cf_sms_ip'], $sms_config['cf_sms_id'], $sms_config['cf_sms_pw'], $sms_config['cf_sms_port']);
|
||||
} else {
|
||||
$this->write_debug_log("[SMS 발송 오류] SMS5 클래스를 찾을 수 없습니다. 실제 발송을 건너뜁니다.");
|
||||
}
|
||||
|
||||
foreach ($members as $member) {
|
||||
if ($member['mb_sms'] && !empty($member['mb_hp'])) {
|
||||
$personal_vars = array_merge($common_vars, ['이름' => $member['mb_name'], 'agent_name' => $member['mb_name'], 'dealer_name' => $member['mb_name']]);
|
||||
$personal_message = $template['content'];
|
||||
foreach ($personal_vars as $key => $value) {
|
||||
$personal_message = str_replace('{' . $key . '}', $value, $personal_message);
|
||||
}
|
||||
|
||||
$result_code = 'Fail';
|
||||
$result_msg = 'SMS5 클래스가 없어 발송할 수 없습니다.';
|
||||
$hs_status = '0';
|
||||
|
||||
if ($SMS) {
|
||||
$SMS->Add($member['mb_hp'], $send_phone, '', $personal_message);
|
||||
$SMS->Send();
|
||||
$result_arr = $SMS->Result;
|
||||
$result_code = 'Fail';
|
||||
$result_msg = '서버로부터 응답이 없습니다.';
|
||||
|
||||
if(!empty($result_arr)){
|
||||
$result_parts = explode(':', $result_arr[0]);
|
||||
if(count($result_parts) > 1 && strpos($result_parts[1], 'Error') === false) {
|
||||
$result_code = 'Success';
|
||||
$result_msg = $result_parts[1];
|
||||
} else {
|
||||
$result_msg = $result_arr[0];
|
||||
}
|
||||
}
|
||||
$hs_status = ($result_code == 'Success') ? '1' : '0';
|
||||
$SMS->Init();
|
||||
}
|
||||
|
||||
sql_query("INSERT INTO {$this->g5['sms5_history_table']} (wr_no, mb_id, hs_name, hs_hp, hs_datetime, hs_status, hs_message) VALUES ('{$wr_no}', '{$member['mb_id']}', '{$member['mb_name']}', '{$member['mb_hp']}', '" . G5_TIME_YMDHIS . "', '{$hs_status}', '{$result_msg}')");
|
||||
|
||||
if ($hs_status == '1') $success++;
|
||||
else $fail++;
|
||||
} else {
|
||||
$fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ['success' => $success, 'fail' => $fail];
|
||||
}
|
||||
|
||||
/**
|
||||
* ❗ [핵심 수정] 이메일 발송 (MailSender 클래스 활용)
|
||||
*/
|
||||
private function sendEmail($members, $template_key, $common_vars)
|
||||
{
|
||||
$success = 0;
|
||||
$fail = 0;
|
||||
|
||||
$notification_mode = get_order_config('notification_mode', 'log');
|
||||
$is_test_mode = ($notification_mode !== 'send');
|
||||
$sizeof_members = count($members);
|
||||
|
||||
// 💡 [수정] 템플릿 조회 로직 변경: 지정 테이블(consultant_mail_templates) -> 기본 테이블(mail_templates) 순서로 조회
|
||||
$template = null;
|
||||
|
||||
// 1. 지정 테이블 조회
|
||||
$check_table = sql_query("SHOW TABLES LIKE 'consultant_mail_templates'", false);
|
||||
if (sql_num_rows($check_table) > 0) {
|
||||
$template = sql_fetch("SELECT * FROM `consultant_mail_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'");
|
||||
if ($template) {
|
||||
// 필드명 통일
|
||||
$template['subject'] = $template['template_subject'];
|
||||
$template['content'] = $template['template_content'];
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 기본 테이블 조회 (지정 테이블에 없을 경우)
|
||||
if (!$template) {
|
||||
$template = sql_fetch("SELECT * FROM `mail_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'");
|
||||
}
|
||||
|
||||
// 3. 둘 다 없으면 에러 처리
|
||||
if (!$template) {
|
||||
$this->write_debug_log("[EMAIL 발송 오류] 템플릿 '{$template_key}'을(를) 찾을 수 없습니다.");
|
||||
return ['success' => 0, 'fail' => count($members)];
|
||||
}
|
||||
|
||||
if ($is_test_mode) {
|
||||
// --- 개발 모드: 로그 파일에만 기록 ---
|
||||
foreach ($members as $member) {
|
||||
if ($member['mb_mailling'] && !empty($member['mb_email'])) {
|
||||
$personal_vars = array_merge($common_vars, ['이름' => $member['mb_name'], 'agent_name' => $member['mb_name'], 'dealer_name' => $member['mb_name']]);
|
||||
$subject = $template['subject'];
|
||||
$content = $template['content'];
|
||||
foreach ($personal_vars as $key => $value) {
|
||||
$search = '{' . $key . '}';
|
||||
$subject = str_replace($search, $value, $subject);
|
||||
$content = str_replace($search, $value, $content);
|
||||
}
|
||||
$this->write_debug_log("[EMAIL LOG] To: {$member['mb_email']}, Subject: {$subject}, Content: {$content}");
|
||||
$success++;
|
||||
} else {
|
||||
$fail++;
|
||||
$empty = !empty($member['mb_email']);
|
||||
$this->write_debug_log("[EMAIL 발송 오류] 사용자 '{$member['mb_sms']}' '{$member['mb_hp']}' '$empty'");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// --- 실제 발송 모드: MailSender 호출 ---
|
||||
if (!class_exists('MailSender')) {
|
||||
$this->write_debug_log("[EMAIL 발송 오류] MailSender 클래스를 찾을 수 없습니다.");
|
||||
return ['success' => 0, 'fail' => count($members)];
|
||||
}
|
||||
$mailSender = new MailSender();
|
||||
|
||||
foreach ($members as $member) {
|
||||
if ($member['mb_mailling'] && !empty($member['mb_email'])) {
|
||||
$personal_vars = array_merge($common_vars, ['이름' => $member['mb_name'], 'agent_name' => $member['mb_name'], 'dealer_name' => $member['mb_name']]);
|
||||
|
||||
// 💡 [수정] MailSender가 템플릿 키로 조회하는 방식일 수 있으므로,
|
||||
// 커스텀 템플릿 내용을 직접 전달하거나 MailSender를 수정해야 할 수 있음.
|
||||
// 여기서는 MailSender가 템플릿 키를 받아서 내부적으로 처리한다고 가정하고,
|
||||
// 만약 커스텀 템플릿을 사용해야 한다면 MailSender의 동작 방식에 따라 수정이 필요함.
|
||||
// 현재 구조상 MailSender::send()는 템플릿 키를 받으므로,
|
||||
// MailSender 내부에서도 동일한 우선순위 로직이 필요하거나,
|
||||
// 여기서 내용을 다 만들어서 보내는 방식(sendDirect 등)이 있다면 그걸 써야 함.
|
||||
// 일단 기존 로직 유지하되, MailSender가 커스텀 테이블을 인지하지 못할 수 있음을 주석으로 남김.
|
||||
|
||||
// 만약 MailSender가 내용을 직접 받는 메소드가 없다면,
|
||||
// 여기서 내용을 치환해서 보내는 로직을 직접 구현해야 할 수도 있음.
|
||||
// 하지만 요청사항은 "지정 테이블을 읽고 없으면 기본 테이블을 읽어서 발송"이므로,
|
||||
// 위에서 $template을 구했으니, 내용을 직접 치환해서 메일 발송 함수(mailer)를 직접 호출하는 것이 안전함.
|
||||
|
||||
$subject = $template['subject'];
|
||||
$content = $template['content'];
|
||||
foreach ($personal_vars as $key => $value) {
|
||||
$search = '{' . $key . '}';
|
||||
$subject = str_replace($search, $value, $subject);
|
||||
$content = str_replace($search, $value, $content);
|
||||
}
|
||||
|
||||
// G5 기본 mailer 함수 사용 (MailSender 의존성 제거 또는 우회)
|
||||
include_once(G5_LIB_PATH.'/mailer.lib.php');
|
||||
mailer($this->g5['title'], $this->g5['admin_email'], $member['mb_email'], $subject, $content, 1);
|
||||
|
||||
$success++;
|
||||
} else {
|
||||
$fail++;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return ['success' => $success, 'fail' => $fail];
|
||||
}
|
||||
|
||||
/**
|
||||
* 결과 메시지 생성
|
||||
*/
|
||||
private function generateResultMessage($results)
|
||||
{
|
||||
$message = "발송이 완료되었습니다.\n\n";
|
||||
$message .= "전체 대상: " . $results['total_targets'] . "명\n\n";
|
||||
if (isset($results['sms_success'])) {
|
||||
$message .= "SMS 발송 결과: 성공 " . $results['sms_success'] . "건, 실패 " . $results['sms_fail'] . "건\n";
|
||||
}
|
||||
if (isset($results['email_success'])) {
|
||||
$message .= "이메일 발송 결과: 성공 " . $results['email_success'] . "건, 실패 " . $results['email_fail'] . "건\n";
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* ❗ [핵심 수정] 디버그 로그 기록 함수 (권한 문제 해결)
|
||||
*/
|
||||
private function write_debug_log($message)
|
||||
{
|
||||
$log_dir = G5_PATH . '/log';
|
||||
|
||||
// 1. 디렉토리 존재 여부 확인 및 생성
|
||||
if (!is_dir($log_dir)) {
|
||||
if (!@mkdir($log_dir, 0755, true) && !is_dir($log_dir)) {
|
||||
error_log("--- NotificationSender ERROR: 디버그 로그 디렉토리 생성 실패. '{$log_dir}' 경로를 확인하거나 수동으로 생성 후 웹서버 쓰기 권한을 부여해주세요.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 디렉토리 쓰기 권한 확인
|
||||
if (!is_writable($log_dir)) {
|
||||
error_log("--- NotificationSender ERROR: 디버그 로그 쓰기 오류. '{$log_dir}' 디렉토리에 쓰기 권한이 없습니다. 웹서버의 폴더 권한을 확인해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 로그 파일에 내용 기록
|
||||
$log_file = $log_dir . '/notification_debug.log';
|
||||
$log_message = date("[Y-m-d H:i:s]") . " " . $message . "\n";
|
||||
|
||||
if (file_put_contents($log_file, $log_message, FILE_APPEND | LOCK_EX) === false) {
|
||||
error_log("--- NotificationSender ERROR: 디버그 로그 파일 쓰기 실패. '{$log_file}' 파일에 내용을 쓸 수 없습니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
// 이 파일은 상담 예약 관련 팝업들을 한번에 쉽게 포함하기 위해 사용됩니다.
|
||||
|
||||
// 현재 파일의 경로를 기준으로 팝업 파일들의 경로를 정의합니다.
|
||||
$consultant_components_path = dirname(__FILE__);
|
||||
|
||||
// 상담 예약 신청 팝업 포함
|
||||
include_once($consultant_components_path . '/reservation_popup.php');
|
||||
|
||||
// 예약 확인/취소 팝업 포함
|
||||
include_once($consultant_components_path . '/reservation_check.php');
|
||||
?>
|
||||
@@ -0,0 +1,401 @@
|
||||
<?php
|
||||
if (isset($_POST['action'])) {
|
||||
include_once('../_common_con.php'); // 💡 [수정] 컴포넌트용 공통 파일 포함
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
// 예약 조회
|
||||
if ($action === 'find_reservations') {
|
||||
$customer_name = trim($_POST['customer_name'] ?? '');
|
||||
$customer_phone = trim($_POST['customer_phone'] ?? '');
|
||||
|
||||
if (!$customer_name || !$customer_phone) {
|
||||
echo json_encode(['success' => false, 'message' => '이름과 연락처를 모두 입력해주세요.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 💡 [수정] 리소스 정보를 함께 조회하기 위해 LEFT JOIN 추가
|
||||
$sql = " SELECT r.*, res.name as resource_name
|
||||
FROM consultant_reservations r
|
||||
LEFT JOIN consultant_resources res ON r.resource_id = res.id
|
||||
WHERE r.customer_name = '" . sql_real_escape_string($customer_name) . "'
|
||||
AND r.customer_phone = '" . sql_real_escape_string($customer_phone) . "'
|
||||
AND r.is_deleted = 0
|
||||
ORDER BY r.reservation_date DESC, r.reservation_time DESC ";
|
||||
|
||||
$result = sql_query($sql);
|
||||
$reservations = [];
|
||||
$cancel_deadline_hours = (int)consultant_get_config('cancel_deadline_hours', 24);
|
||||
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$reservation_timestamp = strtotime($row['reservation_date'] . ' ' . $row['reservation_time']);
|
||||
$cancellable_until = $reservation_timestamp - ($cancel_deadline_hours * 3600);
|
||||
|
||||
$row['is_cancellable'] = (time() < $cancellable_until && in_array($row['status'], ['payment_pending', 'reserved']));
|
||||
$reservations[] = $row;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'data' => $reservations]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 예약 취소
|
||||
if ($action === 'cancel_reservation') {
|
||||
$reservation_id = (int)($_POST['reservation_id'] ?? 0);
|
||||
$customer_name = trim($_POST['customer_name'] ?? '');
|
||||
$customer_phone = trim($_POST['customer_phone'] ?? '');
|
||||
|
||||
if (!$reservation_id || !$customer_name || !$customer_phone) {
|
||||
echo json_encode(['success' => false, 'message' => '필수 정보가 누락되었습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = " SELECT * FROM consultant_reservations
|
||||
WHERE id = '{$reservation_id}'
|
||||
AND customer_name = '" . sql_real_escape_string($customer_name) . "'
|
||||
AND customer_phone = '" . sql_real_escape_string($customer_phone) . "'
|
||||
AND is_deleted = 0 ";
|
||||
$reservation = sql_fetch($sql);
|
||||
|
||||
if (!$reservation) {
|
||||
echo json_encode(['success' => false, 'message' => '예약 정보를 찾을 수 없거나, 입력하신 정보와 일치하지 않습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$cancel_deadline_hours = (int)consultant_get_config('cancel_deadline_hours', 24);
|
||||
$reservation_timestamp = strtotime($reservation['reservation_date'] . ' ' . $reservation['reservation_time']);
|
||||
$cancellable_until = $reservation_timestamp - ($cancel_deadline_hours * 3600);
|
||||
|
||||
if (time() >= $cancellable_until) {
|
||||
echo json_encode(['success' => false, 'message' => '예약 취소 가능 시간이 지났습니다. 관리자에게 문의해주세요.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!in_array($reservation['status'], ['payment_pending', 'reserved'])) {
|
||||
echo json_encode(['success' => false, 'message' => '이미 처리되었거나 취소된 예약입니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql_update = " UPDATE consultant_reservations
|
||||
SET status = 'cancelled', updated_at = NOW()
|
||||
WHERE id = '{$reservation_id}' ";
|
||||
|
||||
if (sql_query($sql_update)) {
|
||||
consultant_log("고객 예약 취소: ID {$reservation_id} (고객: {$customer_name})");
|
||||
echo json_encode(['success' => true, 'message' => '예약이 성공적으로 취소되었습니다.']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => '예약 취소 중 오류가 발생했습니다. 다시 시도해주세요.']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
include_once(G5_ADMIN_PATH . '/consultant_manage/_common_con.php'); // 💡 [수정] 컴포넌트용 공통 파일 포함
|
||||
|
||||
// 💡 [추가] 상담 유형 한글 이름을 JavaScript에서 사용하기 위해 배열을 정의합니다.
|
||||
$consultant_types_map = json_decode(consultant_get_config('consultation_types', '{"onsite":"현장 상담"}'), true);
|
||||
if (!is_array($consultant_types_map)) {
|
||||
$consultant_types_map = ['onsite' => '현장 상담']; // 파싱 실패 시 기본값
|
||||
}
|
||||
|
||||
$ajax_url = G5_ADMIN_URL . '/consultant_manage/components/reservation_check.php';
|
||||
?>
|
||||
|
||||
<!-- 예약 확인/취소 팝업 -->
|
||||
<div id="reservation-check-popup" class="reservation-modal-overlay">
|
||||
<div class="reservation-modal-content">
|
||||
<div class="reservation-modal-header">
|
||||
<h2>예약 확인 및 취소</h2>
|
||||
<button type="button" class="reservation-modal-close" aria-label="팝업 닫기">×</button>
|
||||
</div>
|
||||
<div class="reservation-modal-body">
|
||||
<div class="loading-overlay" style="display: none;">
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
|
||||
<!-- 💡 [개선] 조회 폼과 결과 화면을 분리합니다. -->
|
||||
<div id="reservation-check-view-form">
|
||||
<div class="check-form-wrap">
|
||||
<p>예약 시 입력하신 이름과 연락처로 예약 내역을 조회할 수 있습니다.</p>
|
||||
<form id="reservation-check-form">
|
||||
<div class="form-group">
|
||||
<label for="check_customer_name">예약자명</label>
|
||||
<input type="text" id="check_customer_name" name="customer_name" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="check_customer_phone">연락처</label>
|
||||
<input type="tel" id="check_customer_phone" name="customer_phone" class="form-control" placeholder="010-1234-5678" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">예약 조회</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="reservation-check-view-results" style="display: none;">
|
||||
<div class="results-header">
|
||||
<h3>조회된 예약 내역</h3>
|
||||
<button type="button" class="btn btn-secondary btn-sm" id="back-to-search-btn">새로 조회</button>
|
||||
</div>
|
||||
<div id="reservation-results">
|
||||
<!-- 검색 결과가 여기에 표시됩니다. -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* 💡 [개선] 예약 신청 팝업과 스타일을 통일합니다. */
|
||||
.check-form-wrap { background: #fff; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
|
||||
.form-group { margin-bottom: 15px; }
|
||||
.form-group label { display: block; font-weight: 500; color: #495057; margin-bottom: 8px; }
|
||||
.form-control { width: 100%; padding: 12px; border: 1px solid #ced4da; border-radius: 6px; font-size: 16px; box-sizing: border-box; } /* iOS 줌 방지 */
|
||||
.form-control:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
|
||||
.btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.2s ease;
|
||||
/* 💡 [수정] 버튼 내 텍스트가 잘려보이는 현상을 해결합니다. */
|
||||
height: auto;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.btn-primary { background: #007bff; color: white; }
|
||||
.btn-primary:hover { background: #0056b3; }
|
||||
#reservation-results { margin-top: 20px; }
|
||||
.reservation-item { border: 1px solid #ddd; border-radius: 8px; padding: 20px; margin-bottom: 15px; }
|
||||
.reservation-item-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #eee; padding-bottom: 10px; margin-bottom: 10px; }
|
||||
.reservation-status { padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: bold; }
|
||||
.status-reserved { background: #d4edda; color: #155724; }
|
||||
/* 💡 [개선] 예약 상세 정보 스타일 */
|
||||
.reservation-details-grid {
|
||||
margin: 15px 0;
|
||||
font-size: 15px; /* 글씨 크기 키움 */
|
||||
line-height: 1.6; /* 줄 간격 확보 */
|
||||
}
|
||||
.detail-item { display: flex; margin-bottom: 10px; } /* 간격 조정 */
|
||||
.detail-item .label { font-weight: 600; color: #555; width: 90px; flex-shrink: 0; } /* 너비 조정 */
|
||||
.detail-item .value { color: #333; }
|
||||
/* 💡 [추가] 예약 취소 버튼/안내문 영역 스타일 */
|
||||
.reservation-actions {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
.reservation-actions .btn-danger {
|
||||
width: auto; /* 버튼이 전체 너비를 차지하지 않도록 */
|
||||
display: inline-block;
|
||||
}
|
||||
.cancel-notice { font-size: 13px !important; color: #666 !important; margin-top: 15px; text-align: right; padding: 10px; background-color: #fff; border-radius: 4px;}
|
||||
/* 💡 [개선] 예약 조회 결과 헤더 스타일 */
|
||||
.results-header { display: flex; align-items: center; gap: 1rem; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid #eee; }
|
||||
.results-header h3 { margin: 0; font-size: 18px; flex-grow: 1; } /* 제목이 남는 공간을 모두 차지하도록 설정 */
|
||||
.results-header .btn { width: auto; flex-shrink: 0; } /* 헤더 안의 버튼은 내용만큼만 너비를 갖도록 설정 */
|
||||
.btn-sm { padding: 5px 10px; font-size: 12px; }
|
||||
.status-payment_pending { background: #fff3cd; color: #856404; }
|
||||
.status-cancelled { background: #f8d7da; color: #721c24; }
|
||||
.status-completed { background: #cce5ff; color: #004085; }
|
||||
.btn-danger { background: #dc3545; color: white; }
|
||||
.no-results { text-align: center; color: #666; padding: 40px 0; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const ReservationCheckPopup = {
|
||||
elements: {},
|
||||
// 💡 [추가] PHP에서 전달받은 상담 유형 맵
|
||||
consultantTypes: <?php echo json_encode($consultant_types_map, JSON_UNESCAPED_UNICODE); ?>,
|
||||
state: {
|
||||
name: '',
|
||||
phone: ''
|
||||
},
|
||||
config: {
|
||||
ajaxUrl: '<?php echo $ajax_url; ?>'
|
||||
},
|
||||
|
||||
init() {
|
||||
this.elements.popup = document.getElementById('reservation-check-popup');
|
||||
if (!this.elements.popup) return;
|
||||
|
||||
this.elements.loading = this.elements.popup.querySelector('.loading-overlay');
|
||||
this.elements.form = this.elements.popup.querySelector('#reservation-check-form');
|
||||
this.elements.resultsContainer = this.elements.popup.querySelector('#reservation-results');
|
||||
|
||||
// 💡 [추가] 화면 전환용 요소
|
||||
this.elements.formView = this.elements.popup.querySelector('#reservation-check-view-form');
|
||||
this.elements.resultsView = this.elements.popup.querySelector('#reservation-check-view-results');
|
||||
this.elements.backBtn = this.elements.popup.querySelector('#back-to-search-btn');
|
||||
|
||||
this.addEventListeners();
|
||||
},
|
||||
|
||||
addEventListeners() {
|
||||
const closeBtn = this.elements.popup.querySelector('.reservation-modal-close');
|
||||
if (closeBtn) closeBtn.addEventListener('click', () => this.close());
|
||||
|
||||
this.elements.popup.addEventListener('click', e => {
|
||||
if (e.target === this.elements.popup) this.close();
|
||||
});
|
||||
|
||||
if (this.elements.form) {
|
||||
this.elements.form.addEventListener('submit', e => {
|
||||
e.preventDefault();
|
||||
// 💡 [개선] 조회 버튼을 누를 때만 state를 업데이트하고, 그 state를 기반으로 조회합니다.
|
||||
this.state.name = this.elements.form.querySelector('#check_customer_name').value;
|
||||
this.state.phone = this.elements.form.querySelector('#check_customer_phone').value;
|
||||
this.findReservations(); // state에 저장된 정보로 조회
|
||||
});
|
||||
}
|
||||
|
||||
if (this.elements.backBtn) {
|
||||
this.elements.backBtn.addEventListener('click', () => {
|
||||
this.elements.resultsView.style.display = 'none';
|
||||
this.elements.formView.style.display = 'block';
|
||||
this.elements.resultsContainer.innerHTML = '';
|
||||
});
|
||||
}
|
||||
|
||||
if (this.elements.resultsContainer) {
|
||||
this.elements.resultsContainer.addEventListener('click', e => {
|
||||
if (e.target.classList.contains('btn-cancel-reservation')) {
|
||||
const reservationId = e.target.dataset.id;
|
||||
if (confirm('정말 이 예약을 취소하시겠습니까?')) {
|
||||
this.cancelReservation(reservationId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
open() {
|
||||
if (!this.elements.popup) return;
|
||||
this.elements.popup.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
},
|
||||
|
||||
close() {
|
||||
if (!this.elements.popup) return;
|
||||
this.elements.popup.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
if (this.elements.form) this.elements.form.reset();
|
||||
|
||||
// 💡 [추가] 팝업을 닫을 때 화면 상태를 초기화합니다.
|
||||
if (this.elements.resultsContainer) this.elements.resultsContainer.innerHTML = '';
|
||||
if (this.elements.resultsView) this.elements.resultsView.style.display = 'none';
|
||||
if (this.elements.formView) this.elements.formView.style.display = 'block';
|
||||
},
|
||||
|
||||
async findReservations() {
|
||||
this.showLoading();
|
||||
|
||||
// 💡 [개선] state에 저장된 정보로 FormData를 생성하여 일관성을 유지합니다.
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'find_reservations');
|
||||
formData.append('customer_name', this.state.name);
|
||||
formData.append('customer_phone', this.state.phone);
|
||||
|
||||
try {
|
||||
const response = await fetch(this.config.ajaxUrl, { method: 'POST', body: formData });
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
// 💡 [추가] 조회 성공 시 화면을 전환합니다.
|
||||
this.elements.formView.style.display = 'none';
|
||||
this.elements.resultsView.style.display = 'block';
|
||||
this.renderResults(result.data);
|
||||
} else {
|
||||
this.elements.resultsContainer.innerHTML = `<p class="no-results">${result.message}</p>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("조회 오류:", error);
|
||||
this.elements.resultsContainer.innerHTML = '<p class="no-results">조회 중 오류가 발생했습니다.</p>';
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
renderResults(reservations) {
|
||||
if (reservations.length === 0) {
|
||||
this.elements.resultsContainer.innerHTML = '<p class="no-results">조회된 예약 내역이 없습니다.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 💡 [수정] h3 태그는 정적 HTML로 이동했으므로 제거합니다.
|
||||
let html = '';
|
||||
const statusLabels = {
|
||||
'payment_pending': '입금대기', 'reserved': '예약확정',
|
||||
'completed': '상담완료', 'cancelled': '예약취소'
|
||||
};
|
||||
|
||||
reservations.forEach(res => {
|
||||
const date = new Date(res.reservation_date + ' ' + res.reservation_time);
|
||||
const options = { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long', hour: '2-digit', minute: '2-digit' };
|
||||
const formattedDate = date.toLocaleDateString('ko-KR', options);
|
||||
|
||||
// 💡 [개선] 상담 유형의 한글 이름을 맵에서 찾아옵니다.
|
||||
const consultationTypeName = this.consultantTypes[res.consultation_type] || res.consultation_type;
|
||||
|
||||
// 💡 [추가] 리소스 이름 표시
|
||||
const resourceName = res.resource_name ? res.resource_name : '미지정 (빠른 배정)';
|
||||
|
||||
html += `
|
||||
<div class="reservation-item" id="reservation-${res.id}">
|
||||
<div class="reservation-item-header">
|
||||
<strong>예약 번호: #${res.id}</strong>
|
||||
<span class="reservation-status status-${res.status}">${statusLabels[res.status] || res.status}</span>
|
||||
</div>
|
||||
<div class="reservation-details-grid">
|
||||
<div class="detail-item"><span class="label">상담 일시:</span><span class="value">${formattedDate}</span></div>
|
||||
<div class="detail-item"><span class="label">상담 유형:</span><span class="value">${consultationTypeName}</span></div>
|
||||
<div class="detail-item"><span class="label">담당/공간:</span><span class="value">${resourceName}</span></div>
|
||||
</div>
|
||||
<div class="reservation-actions">`;
|
||||
|
||||
if (res.is_cancellable) {
|
||||
html += `<button type="button" class="btn btn-danger btn-cancel-reservation" data-id="${res.id}">예약 취소</button>`;
|
||||
} else if (res.status !== 'cancelled' && res.status !== 'completed') {
|
||||
html += `<p class="cancel-notice">예약 취소 가능 시간이 지났습니다. 변경/취소는 관리자에게 문의해주세요.</p>`;
|
||||
}
|
||||
html += `</div></div>`;
|
||||
});
|
||||
this.elements.resultsContainer.innerHTML = html;
|
||||
},
|
||||
|
||||
async cancelReservation(reservationId) {
|
||||
this.showLoading();
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'cancel_reservation');
|
||||
formData.append('reservation_id', reservationId);
|
||||
formData.append('customer_name', this.state.name);
|
||||
formData.append('customer_phone', this.state.phone);
|
||||
|
||||
try {
|
||||
const response = await fetch(this.config.ajaxUrl, { method: 'POST', body: formData });
|
||||
const result = await response.json();
|
||||
alert(result.message);
|
||||
if (result.success) {
|
||||
await this.findReservations(); // 💡 [수정] await를 추가하여 목록 새로고침이 완료될 때까지 기다립니다.
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("취소 오류:", error);
|
||||
alert('예약 취소 처리 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
// 💡 [제거] findReservations()가 자체적으로 로딩을 숨기므로 중복 호출을 제거합니다.
|
||||
}
|
||||
},
|
||||
|
||||
showLoading() { if (this.elements.loading) this.elements.loading.style.display = 'flex'; },
|
||||
hideLoading() { if (this.elements.loading) this.elements.loading.style.display = 'none'; },
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => ReservationCheckPopup.init());
|
||||
|
||||
function openReservationCheckPopup() {
|
||||
ReservationCheckPopup.open();
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,868 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 팝업 UI 및 데이터 처리
|
||||
*/
|
||||
|
||||
// AJAX 요청 처리
|
||||
if (isset($_POST['action'])) {
|
||||
include_once('../_common_con.php'); // 💡 [수정] 컴포넌트용 공통 파일 포함
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$action = $_POST['action'] ?? '';
|
||||
$response = ['success' => false, 'message' => '알 수 없는 요청입니다.'];
|
||||
|
||||
// 월별 예약 가능일 조회
|
||||
if ($action === 'get_month_availability') {
|
||||
$year = (int) ($_POST['year'] ?? 0);
|
||||
$month = (int) ($_POST['month'] ?? 0);
|
||||
|
||||
if ($year && $month) {
|
||||
$start_date = date('Y-m-d', mktime(0, 0, 0, $month, 1, $year));
|
||||
$end_date = date('Y-m-t', strtotime($start_date));
|
||||
|
||||
$max_advance_days = consultant_get_config('max_advance_days', 30);
|
||||
$max_date = date('Y-m-d', strtotime("+" . $max_advance_days . " days"));
|
||||
|
||||
$sql = "SELECT
|
||||
specific_date,
|
||||
SUM(CASE WHEN is_available = 1 AND max_persons > (SELECT COUNT(*) FROM consultant_reservations r WHERE r.reservation_date = s.specific_date AND r.reservation_time = s.start_time AND r.status != 'cancelled') THEN 1 ELSE 0 END) as available_slots,
|
||||
MAX(CASE WHEN temp_1 = 'holiday' THEN 1 ELSE 0 END) as is_holiday
|
||||
FROM consultant_schedule s
|
||||
WHERE specific_date BETWEEN '{$start_date}' AND '{$end_date}'
|
||||
GROUP BY specific_date";
|
||||
|
||||
$result = sql_query($sql);
|
||||
$availability = [];
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$is_bookable = true;
|
||||
$reason = '';
|
||||
|
||||
if ($row['is_holiday']) {
|
||||
$is_bookable = false;
|
||||
$reason = 'holiday';
|
||||
} elseif ($row['specific_date'] < date('Y-m-d')) {
|
||||
$is_bookable = false;
|
||||
$reason = 'past_date';
|
||||
} elseif ($row['specific_date'] > $max_date) {
|
||||
$is_bookable = false;
|
||||
$reason = 'too_far';
|
||||
} elseif ($row['available_slots'] == 0) {
|
||||
$is_bookable = false;
|
||||
$reason = 'full';
|
||||
}
|
||||
|
||||
$availability[date('j', strtotime($row['specific_date']))] = [
|
||||
'available' => $is_bookable,
|
||||
'reason' => $reason
|
||||
];
|
||||
}
|
||||
$response = ['success' => true, 'data' => $availability];
|
||||
}
|
||||
}
|
||||
|
||||
// 특정일의 예약 가능 시간 조회
|
||||
if ($action === 'get_time_slots') {
|
||||
$date = preg_replace('/[^0-9\-]/', '', $_POST['date'] ?? '');
|
||||
|
||||
if ($date) {
|
||||
$min_advance_hours = consultant_get_config('min_advance_hours', 24);
|
||||
$min_datetime = date('Y-m-d H:i:s', strtotime("+" . $min_advance_hours . " hours"));
|
||||
|
||||
$sql = "SELECT
|
||||
s.start_time,
|
||||
s.max_persons,
|
||||
(SELECT COUNT(*) FROM consultant_reservations r WHERE r.reservation_date = s.specific_date AND r.reservation_time = s.start_time AND r.status != 'cancelled') as reserved_count
|
||||
FROM consultant_schedule s
|
||||
WHERE s.specific_date = '{$date}' AND s.is_available = 1
|
||||
ORDER BY s.start_time";
|
||||
|
||||
$result = sql_query($sql);
|
||||
$slots = [];
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$slot_datetime = $date . ' ' . $row['start_time'];
|
||||
$is_too_soon = ($slot_datetime < $min_datetime);
|
||||
|
||||
$available_count = $row['max_persons'] - $row['reserved_count'];
|
||||
$is_full = ($available_count <= 0);
|
||||
|
||||
$slots[] = [
|
||||
'time' => substr($row['start_time'], 0, 5),
|
||||
'available' => !$is_too_soon && !$is_full,
|
||||
'reason' => $is_too_soon ? 'too_soon' : ($is_full ? 'full' : ''),
|
||||
'reserved_count' => (int)$row['reserved_count'],
|
||||
'max_persons' => (int)$row['max_persons']
|
||||
];
|
||||
}
|
||||
$response = ['success' => true, 'data' => $slots];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 💡 [수정] 아래는 팝업의 HTML/CSS/JS 부분입니다.
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
include_once(G5_ADMIN_PATH . '/consultant_manage/_common_con.php'); // 💡 [수정] 컴포넌트용 공통 파일 포함
|
||||
$consultant_installed = is_consultant_installed();
|
||||
|
||||
if($consultant_installed) { // 💡 [추가] 시스템이 설치된 경우에만 팝업을 렌더링합니다.
|
||||
// 현재 날짜 정보
|
||||
$current_year = date('Y');
|
||||
$current_month_num = date('n');
|
||||
|
||||
// 💡 [개선] 설정 값을 DB에서 직접 가져와 일관성을 유지합니다.
|
||||
$consultation_fee = consultant_get_config('consultation_fee', 50000);
|
||||
$account_info = consultant_get_config('account_info', '');
|
||||
$max_advance_days = consultant_get_config('max_advance_days', 30);
|
||||
|
||||
// 💡 [수정] 스킨 URL 및 AJAX 엔드포인트 설정
|
||||
$ajax_url = G5_ADMIN_URL . '/consultant_manage/components/reservation_popup.php';
|
||||
$form_action_url = G5_ADMIN_URL . '/consultant_manage/components/reservation_submit.php';
|
||||
|
||||
// 💡 [추가] 리소스 목록 조회
|
||||
$resources = [];
|
||||
$res_result = sql_query("SELECT id, name FROM consultant_resources WHERE is_active = 1 ORDER BY group_id, name");
|
||||
while ($row = sql_fetch_array($res_result)) {
|
||||
$resources[] = $row;
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- 예약 팝업 오버레이 -->
|
||||
<div id="reservation-popup" class="reservation-modal-overlay">
|
||||
<div class="reservation-modal-content">
|
||||
<!-- 팝업 헤더 -->
|
||||
<div class="reservation-modal-header">
|
||||
<h2>상담 예약 신청</h2>
|
||||
<button type="button" class="reservation-modal-close" aria-label="팝업 닫기">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 팝업 본문 -->
|
||||
<div class="reservation-modal-body">
|
||||
<!-- 💡 [개선] 로딩 스피너 추가 -->
|
||||
<div class="loading-overlay" style="display: none;">
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
|
||||
<!-- 단계 표시 -->
|
||||
<div class="reservation-steps">
|
||||
<div class="step active" data-step="1">
|
||||
<span class="step-number">1</span>
|
||||
<span class="step-text">날짜 선택</span>
|
||||
</div>
|
||||
<div class="step" data-step="2">
|
||||
<span class="step-number">2</span>
|
||||
<span class="step-text">시간 선택</span>
|
||||
</div>
|
||||
<div class="step" data-step="3">
|
||||
<span class="step-number">3</span>
|
||||
<span class="step-text">정보 입력</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 예약 폼 -->
|
||||
<form id="reservation-form" method="post"
|
||||
action="<?php echo $form_action_url; ?>">
|
||||
<!-- 1단계: 달력 -->
|
||||
<div class="reservation-step-content" data-step="1">
|
||||
<div class="step-description">
|
||||
<h4>📅 상담 날짜를 선택해주세요</h4>
|
||||
<p>최대 <?php echo $max_advance_days; ?>일 후까지 예약 가능합니다.</p>
|
||||
</div>
|
||||
<div class="calendar-container">
|
||||
<div class="calendar-header">
|
||||
<button type="button" class="calendar-nav prev-month" aria-label="이전 달">
|
||||
<span>‹</span>
|
||||
</button>
|
||||
<h3 class="calendar-title">
|
||||
<span id="calendar-year"><?php echo $current_year; ?></span>년
|
||||
<span id="calendar-month"><?php echo $current_month_num; ?></span>월
|
||||
</h3>
|
||||
<button type="button" class="calendar-nav next-month" aria-label="다음 달">
|
||||
<span>›</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="calendar-grid">
|
||||
<div class="calendar-weekdays">
|
||||
<div class="weekday">일</div>
|
||||
<div class="weekday">월</div>
|
||||
<div class="weekday">화</div>
|
||||
<div class="weekday">수</div>
|
||||
<div class="weekday">목</div>
|
||||
<div class="weekday">금</div>
|
||||
<div class="weekday">토</div>
|
||||
</div>
|
||||
<div class="calendar-days" id="calendar-days">
|
||||
<!-- 달력 날짜들이 JavaScript로 동적 생성됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="calendar-legend">
|
||||
<div class="legend-item"><span class="legend-color available"></span><span>예약 가능</span></div>
|
||||
<!-- 💡 [추가] 휴일/마감 상태를 구분하는 범례 추가 -->
|
||||
<div class="legend-item"><span class="legend-color holiday"></span><span>휴일</span></div>
|
||||
<div class="legend-item"><span class="legend-color full"></span><span>예약 마감</span></div>
|
||||
<div class="legend-item"><span class="legend-color unavailable"></span><span>지난날짜</span></div>
|
||||
<div class="legend-item"><span class="legend-color selected"></span><span>선택</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2단계: 시간 선택 -->
|
||||
<div class="reservation-step-content" data-step="2" style="display: none;">
|
||||
<div class="step-description">
|
||||
<h4>🕐 상담 시간을 선택해주세요</h4>
|
||||
<div class="selected-date-info"><strong>선택된 날짜: <span id="selected-date-display"></span></strong></div>
|
||||
</div>
|
||||
<div class="time-slots-container">
|
||||
<div class="time-slots-grid" id="time-slots-grid">
|
||||
<!-- 시간대들이 JavaScript로 동적 생성됩니다 -->
|
||||
</div>
|
||||
<div class="time-legend">
|
||||
<div class="legend-item"><span class="legend-color time-available"></span><span>예약 가능</span></div>
|
||||
<div class="legend-item"><span class="legend-color time-full"></span><span>예약 마감</span></div>
|
||||
<div class="legend-item"><span class="legend-color time-too-soon"></span><span>예약 임박</span></div>
|
||||
<div class="legend-item"><span class="legend-color time-selected"></span><span>선택</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3단계: 고객 정보 입력 -->
|
||||
<div class="reservation-step-content" data-step="3" style="display: none;">
|
||||
<div class="step-description">
|
||||
<h4>📝 고객 정보를 입력해주세요</h4>
|
||||
</div>
|
||||
|
||||
<div class="reservation-summary">
|
||||
<h5>📋 예약 정보 확인</h5>
|
||||
<div class="summary-grid">
|
||||
<div class="summary-item">
|
||||
<span class="label">📅 예약 날짜:</span>
|
||||
<span id="summary-date">-</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="label">🕐 예약 시간:</span>
|
||||
<span id="summary-time">-</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="label">💰 상담 비용:</span>
|
||||
<span><?php echo number_format($consultation_fee); ?>원</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="customer-info-form">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="customer-name">👤 이름 <span class="required">*</span></label>
|
||||
<input type="text" id="customer-name" name="customer_name" required placeholder="홍길동">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="customer-phone">📱 연락처 <span class="required">*</span></label>
|
||||
<input type="tel" id="customer-phone" name="customer_phone" required placeholder="010-1234-5678">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="customer-email">📧 이메일 <span class="required">*</span></label>
|
||||
<input type="email" id="customer-email" name="customer_email" required placeholder="example@email.com">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="consultation-type">🏠 상담 유형</label>
|
||||
<select id="consultation-type" name="consultation_type">
|
||||
<option value="onsite">현장 상담</option>
|
||||
<option value="online">온라인 상담</option>
|
||||
<option value="phone">전화 상담</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- 💡 [추가] 담당자/공간 선택 드롭다운 -->
|
||||
<div class="form-group">
|
||||
<label for="consultation-resource">👨⚕️ 담당자/공간 선택</label>
|
||||
<select id="consultation-resource" name="resource_id">
|
||||
<option value="">선택 안 함 (빠른 배정)</option>
|
||||
<?php foreach ($resources as $res): ?>
|
||||
<option value="<?php echo $res['id']; ?>"><?php echo htmlspecialchars($res['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="customer-request">📝 요청사항</label>
|
||||
<textarea id="customer-request" name="customer_request" rows="4" placeholder="상담 관련 요청사항이나 문의사항을 입력해주세요."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 숨겨진 필드들 -->
|
||||
<input type="hidden" id="selected-date" name="reservation_date">
|
||||
<input type="hidden" id="selected-time" name="reservation_time">
|
||||
<input type="hidden" name="payment_amount" value="<?php echo $consultation_fee; ?>">
|
||||
<input type="hidden" name="status" value="payment_pending">
|
||||
</div>
|
||||
|
||||
<!-- 네비게이션 버튼 -->
|
||||
<div class="reservation-nav-buttons">
|
||||
<button type="button" class="btn-prev" style="display: none;">← 이전</button>
|
||||
<button type="button" class="btn-next">다음 →</button>
|
||||
<button type="submit" class="btn-submit" style="display: none;">예약 신청</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 예약 팝업 스타일 -->
|
||||
<style>
|
||||
/* 💡 [추가] 로딩 오버레이 스타일 */
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
.loading-spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #007bff;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||||
|
||||
/* 팝업 오버레이 */
|
||||
.reservation-modal-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
z-index: 9999;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.reservation-modal-overlay.active {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.reservation-modal-content {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
.reservation-modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 25px;
|
||||
border-bottom: 1px solid #eee;
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
.reservation-modal-header h2 { margin: 0; font-size: 20px; font-weight: 600; color: #333; }
|
||||
.reservation-modal-close {
|
||||
background: none; border: none; font-size: 24px; cursor: pointer; color: #666;
|
||||
padding: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;
|
||||
border-radius: 50%; transition: all 0.2s ease;
|
||||
}
|
||||
.reservation-modal-close:hover { background: #e9ecef; color: #333; }
|
||||
.reservation-modal-body { padding: 25px; }
|
||||
|
||||
/* 단계 표시 */
|
||||
.reservation-steps { display: flex; justify-content: center; margin-bottom: 30px; position: relative; }
|
||||
.reservation-steps::before {
|
||||
content: ''; position: absolute; top: 15px; left: 25%; right: 25%;
|
||||
height: 2px; background: #e9ecef; z-index: 1;
|
||||
}
|
||||
.step { display: flex; flex-direction: column; align-items: center; position: relative; z-index: 2; background: #fff; padding: 0 15px; }
|
||||
.step-number {
|
||||
width: 30px; height: 30px; border-radius: 50%; background: #e9ecef; color: #6c757d;
|
||||
display: flex; align-items: center; justify-content: center; font-weight: 600; margin-bottom: 8px; transition: all 0.3s ease;
|
||||
}
|
||||
.step.active .step-number { background: #007bff; color: #fff; }
|
||||
.step.completed .step-number { background: #28a745; color: #fff; }
|
||||
.step-text { font-size: 12px; color: #6c757d; font-weight: 500; }
|
||||
.step.active .step-text { color: #007bff; font-weight: 600; }
|
||||
|
||||
/* 달력 */
|
||||
.calendar-container { max-width: 100%; }
|
||||
.calendar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.calendar-nav {
|
||||
background: none; border: 1px solid #ddd; width: 35px; height: 35px; border-radius: 50%;
|
||||
cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease;
|
||||
}
|
||||
.calendar-nav:hover { background: #f8f9fa; border-color: #007bff; }
|
||||
.calendar-title { margin: 0; font-size: 18px; font-weight: 600; color: #333; }
|
||||
.calendar-grid { border: 1px solid #e9ecef; border-radius: 8px; overflow: hidden; }
|
||||
.calendar-weekdays { display: grid; grid-template-columns: repeat(7, 1fr); background: #f8f9fa; }
|
||||
.weekday { padding: 12px 8px; text-align: center; font-weight: 600; color: #495057; font-size: 14px; border-right: 1px solid #e9ecef; }
|
||||
.weekday:last-child { border-right: none; }
|
||||
.calendar-days { display: grid; grid-template-columns: repeat(7, 1fr); }
|
||||
.calendar-day {
|
||||
aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
|
||||
border-right: 1px solid #e9ecef; border-bottom: 1px solid #e9ecef; cursor: pointer;
|
||||
transition: all 0.2s ease; font-size: 14px; position: relative;
|
||||
}
|
||||
.calendar-day:nth-child(7n) { border-right: none; }
|
||||
.calendar-day.other-month { color: #ccc; background: #f8f9fa; cursor: default; }
|
||||
.calendar-day.available { background: #fff; color: #333; }
|
||||
.calendar-day.available:hover { background: #e3f2fd; color: #1976d2; }
|
||||
.calendar-day.unavailable { background: #f5f5f5; color: #999; cursor: not-allowed; }
|
||||
.calendar-day.full { background: #fbe9e7; color: #c62828; cursor: not-allowed; } /* 예약 마감 */
|
||||
.calendar-day.holiday { background: #e8eaf6; color: #3f51b5; cursor: not-allowed; } /* 휴일 */
|
||||
.calendar-day.selected { background: #007bff; color: #fff; font-weight: 600; }
|
||||
.calendar-day.today { font-weight: 600; color: #dc3545; }
|
||||
.calendar-loading, .loading, .error, .no-slots { grid-column: 1 / -1; text-align: center; padding: 40px 20px; color: #666; font-style: italic; }
|
||||
.error { color: #dc3545; }
|
||||
|
||||
/* 범례 */
|
||||
.calendar-legend, .time-legend { display: flex; justify-content: center; gap: 20px; margin-top: 15px; flex-wrap: wrap; }
|
||||
.legend-item { display: flex; align-items: center; gap: 5px; font-size: 12px; color: #666; }
|
||||
.legend-color { width: 12px; height: 12px; border-radius: 2px; border: 1px solid #ddd; }
|
||||
.legend-color.available { background: #fff; }
|
||||
.legend-color.unavailable { background: #f5f5f5; }
|
||||
.legend-color.full { background: #fbe9e7; }
|
||||
.legend-color.holiday { background: #e8eaf6; }
|
||||
.legend-color.selected { background: #007bff; }
|
||||
.legend-color.time-available { background: #e8f5e8; border-color: #28a745; }
|
||||
.legend-color.time-full { background: #f8d7da; border-color: #dc3545; }
|
||||
.legend-color.time-too-soon { background: #f1f3f5; border-color: #dee2e6; }
|
||||
.legend-color.time-selected { background: #007bff; border-color: #007bff; }
|
||||
|
||||
/* 시간 선택 */
|
||||
.selected-date-info { margin-bottom: 10px; }
|
||||
.step-description { margin-bottom: 20px; }
|
||||
.step-description h4 { margin: 0 0 10px 0; font-size: 18px; }
|
||||
.step-description p { margin: 0; font-size: 14px; color: #666; }
|
||||
.selected-date-info h4 { margin: 0; color: #333; font-size: 16px; }
|
||||
.time-slots-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin-bottom: 20px; }
|
||||
.time-slot {
|
||||
padding: 12px 8px; border: 2px solid #e9ecef; border-radius: 6px; text-align: center; cursor: pointer;
|
||||
transition: all 0.2s ease; font-size: 14px; font-weight: 500; display: flex; flex-direction: column; gap: 4px;
|
||||
}
|
||||
.time-text { font-weight: 600; }
|
||||
.slot-info { font-size: 11px; opacity: 0.8; }
|
||||
/* 💡 [수정] 예약 가능 슬롯 스타일 변경 */
|
||||
.time-slot.available { background: #e7f3ff; border-color: #007bff; color: #004085; }
|
||||
.time-slot.available:hover { background: #d4edda; transform: translateY(-1px); }
|
||||
.time-slot.full { background: #f8d7da; border-color: #dc3545; color: #721c24; cursor: not-allowed; }
|
||||
.time-slot.too-soon { background: #f1f3f5; color: #868e96; cursor: not-allowed; border-color: #dee2e6; }
|
||||
.time-slot.selected { background: #007bff; border-color: #007bff; color: #fff; }
|
||||
|
||||
/* 고객 정보 폼 */
|
||||
.reservation-summary { background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px; }
|
||||
.reservation-summary h5 { margin: 0 0 10px 0; font-size: 16px; }
|
||||
.summary-grid { display: grid; grid-template-columns: 1fr; gap: 8px; }
|
||||
.summary-item { display: flex; justify-content: space-between; font-size: 14px; }
|
||||
.summary-item .label { font-weight: 500; color: #495057; }
|
||||
.summary-item span { color: #333; }
|
||||
.customer-info-form h4 { margin: 0 0 15px 0; color: #333; font-size: 16px; }
|
||||
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
|
||||
.form-group { margin-bottom: 15px; }
|
||||
.form-group label { display: block; margin-bottom: 8px; font-weight: 500; color: #495057; font-size: 14px; }
|
||||
.required { color: #dc3545; font-weight: bold; }
|
||||
.form-group input, .form-group textarea, .form-group select {
|
||||
width: 100%; padding: 12px; border: 1px solid #ced4da; border-radius: 6px;
|
||||
font-size: 14px; transition: border-color 0.2s ease; box-sizing: border-box;
|
||||
}
|
||||
.form-group textarea { resize: vertical; min-height: 80px; }
|
||||
.form-group input:focus, .form-group textarea:focus, .form-group select:focus {
|
||||
outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
/* 네비게이션 버튼 */
|
||||
.reservation-nav-buttons {
|
||||
display: flex; justify-content: space-between; gap: 10px; margin-top: 25px;
|
||||
padding-top: 20px; border-top: 1px solid #e9ecef;
|
||||
}
|
||||
.reservation-nav-buttons button {
|
||||
padding: 12px 24px; border: none; border-radius: 6px; font-size: 14px;
|
||||
font-weight: 600; cursor: pointer; transition: all 0.2s ease; min-width: 100px;
|
||||
}
|
||||
.btn-prev { background: #6c757d; color: #fff; }
|
||||
.btn-prev:hover { background: #5a6268; }
|
||||
.btn-next, .btn-submit { background: #007bff; color: #fff; }
|
||||
.btn-next:hover, .btn-submit:hover { background: #0056b3; }
|
||||
|
||||
/* 반응형 디자인 - 모바일 최적화 */
|
||||
@media (max-width: 768px) {
|
||||
.reservation-modal-content { margin: 10px; max-height: 95vh; border-radius: 8px; }
|
||||
.reservation-modal-header, .reservation-modal-body { padding: 15px; }
|
||||
.reservation-modal-header h2 { font-size: 18px; }
|
||||
.reservation-steps { margin-bottom: 20px; }
|
||||
.step { padding: 0 10px; }
|
||||
.step-number { width: 28px; height: 28px; font-size: 13px; }
|
||||
.step-text { font-size: 11px; }
|
||||
.calendar-day { font-size: 13px; }
|
||||
.time-slots-grid { grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); }
|
||||
.form-row { grid-template-columns: 1fr; gap: 0; }
|
||||
.form-group input, .form-group textarea, .form-group select { font-size: 16px; /* iOS 줌 방지 */ }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.reservation-modal-overlay { padding: 0; align-items: flex-end; }
|
||||
.reservation-modal-content { margin: 0; width: 100%; max-height: 90vh; border-radius: 10px 10px 0 0; }
|
||||
.reservation-nav-buttons { flex-direction: column-reverse; gap: 10px; }
|
||||
.reservation-nav-buttons button { width: 100%; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 💡 [개선] JavaScript 로직 전면 수정 -->
|
||||
<script>
|
||||
const ReservationPopup = {
|
||||
elements: {},
|
||||
state: {
|
||||
currentStep: 1,
|
||||
selectedDate: null,
|
||||
selectedTime: null,
|
||||
currentYear: new Date().getFullYear(),
|
||||
currentMonth: new Date().getMonth() + 1,
|
||||
},
|
||||
config: {
|
||||
ajaxUrl: '<?php echo $ajax_url; ?>',
|
||||
maxAdvanceDays: <?php echo (int)$max_advance_days; ?>,
|
||||
},
|
||||
|
||||
init() {
|
||||
this.elements.popup = document.getElementById('reservation-popup');
|
||||
if (!this.elements.popup) return;
|
||||
|
||||
this.elements.loading = this.elements.popup.querySelector('.loading-overlay');
|
||||
this.elements.form = this.elements.popup.querySelector('#reservation-form');
|
||||
this.elements.calendar = {
|
||||
year: this.elements.popup.querySelector('#calendar-year'),
|
||||
month: this.elements.popup.querySelector('#calendar-month'),
|
||||
days: this.elements.popup.querySelector('#calendar-days'),
|
||||
prevBtn: this.elements.popup.querySelector('.prev-month'),
|
||||
nextBtn: this.elements.popup.querySelector('.next-month'),
|
||||
};
|
||||
this.elements.timeSlotsGrid = this.elements.popup.querySelector('#time-slots-grid');
|
||||
this.elements.nav = {
|
||||
prevBtn: this.elements.popup.querySelector('.btn-prev'),
|
||||
nextBtn: this.elements.popup.querySelector('.btn-next'),
|
||||
submitBtn: this.elements.popup.querySelector('.btn-submit'),
|
||||
};
|
||||
|
||||
this.addEventListeners();
|
||||
},
|
||||
|
||||
addEventListeners() {
|
||||
// 💡 [수정] 닫기 버튼을 더 안정적으로 찾아 이벤트를 추가하고, null 체크를 추가합니다.
|
||||
const closeBtn = this.elements.popup.querySelector('.reservation-modal-close');
|
||||
if (closeBtn) closeBtn.addEventListener('click', () => this.close());
|
||||
|
||||
this.elements.popup.addEventListener('click', e => {
|
||||
if (e.target === this.elements.popup) this.close();
|
||||
});
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape' && this.elements.popup.classList.contains('active')) this.close();
|
||||
});
|
||||
|
||||
// 💡 [수정] 모든 이벤트 리스너에 null 체크를 추가하여 스크립트 오류를 방지합니다.
|
||||
if (this.elements.calendar.prevBtn) this.elements.calendar.prevBtn.addEventListener('click', () => this.changeMonth(-1));
|
||||
if (this.elements.calendar.nextBtn) this.elements.calendar.nextBtn.addEventListener('click', () => this.changeMonth(1));
|
||||
|
||||
if (this.elements.nav.prevBtn) this.elements.nav.prevBtn.addEventListener('click', () => this.goToStep(this.state.currentStep - 1));
|
||||
if (this.elements.nav.nextBtn) this.elements.nav.nextBtn.addEventListener('click', () => this.goToNextStep());
|
||||
if (this.elements.form) {
|
||||
this.elements.form.addEventListener('submit', e => {
|
||||
e.preventDefault();
|
||||
this.submitForm();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
open() {
|
||||
this.state.currentYear = new Date().getFullYear();
|
||||
this.state.currentMonth = new Date().getMonth() + 1;
|
||||
this.goToStep(1);
|
||||
this.renderCalendar();
|
||||
this.elements.popup.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
},
|
||||
|
||||
close() {
|
||||
this.elements.popup.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
this.elements.form.reset();
|
||||
},
|
||||
|
||||
changeMonth(delta) {
|
||||
this.state.currentMonth += delta;
|
||||
if (this.state.currentMonth < 1) {
|
||||
this.state.currentMonth = 12;
|
||||
this.state.currentYear--;
|
||||
} else if (this.state.currentMonth > 12) {
|
||||
this.state.currentMonth = 1;
|
||||
this.state.currentYear++;
|
||||
}
|
||||
this.renderCalendar();
|
||||
},
|
||||
|
||||
async renderCalendar() {
|
||||
this.elements.calendar.year.textContent = this.state.currentYear;
|
||||
this.elements.calendar.month.textContent = this.state.currentMonth;
|
||||
this.elements.calendar.days.innerHTML = '<div class="loading">달력 정보를 불러오는 중...</div>';
|
||||
|
||||
const availability = await this.fetchMonthAvailability();
|
||||
if (!availability) {
|
||||
this.elements.calendar.days.innerHTML = '<div class="error">달력 정보를 불러올 수 없습니다.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
this.elements.calendar.days.innerHTML = '';
|
||||
const firstDay = new Date(this.state.currentYear, this.state.currentMonth - 1, 1);
|
||||
const daysInMonth = new Date(this.state.currentYear, this.state.currentMonth, 0).getDate();
|
||||
const startDayOfWeek = firstDay.getDay();
|
||||
|
||||
for (let i = 0; i < startDayOfWeek; i++) {
|
||||
this.elements.calendar.days.appendChild(this.createDayElement(0, true));
|
||||
}
|
||||
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
this.elements.calendar.days.appendChild(this.createDayElement(day, false, availability[day]));
|
||||
}
|
||||
},
|
||||
|
||||
createDayElement(day, isOtherMonth, availability = null) {
|
||||
const dayElement = document.createElement('div');
|
||||
dayElement.className = 'calendar-day';
|
||||
if (isOtherMonth) {
|
||||
dayElement.classList.add('other-month');
|
||||
return dayElement;
|
||||
}
|
||||
|
||||
|
||||
dayElement.textContent = day;
|
||||
|
||||
const dateStr = `${this.state.currentYear}-${String(this.state.currentMonth).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const currentDate = new Date(dateStr);
|
||||
currentDate.setHours(0, 0, 0, 0);
|
||||
if (currentDate.getTime() === today.getTime()) {
|
||||
dayElement.classList.add('today');
|
||||
} else if (currentDate.getTime() < today.getTime()){
|
||||
dayElement.classList.add('unavailable');
|
||||
} else if (availability && availability.available) {
|
||||
dayElement.classList.add('available');
|
||||
dayElement.addEventListener('click', () => this.selectDate(dateStr, dayElement));
|
||||
} else {
|
||||
// 💡 [개선] 예약 불가 사유에 따라 다른 스타일을 적용합니다.
|
||||
if (availability && availability.reason === 'holiday') {
|
||||
dayElement.classList.add('holiday');
|
||||
} else if (availability && availability.reason === 'full') {
|
||||
dayElement.classList.add('full');
|
||||
} else {
|
||||
dayElement.classList.add('unavailable');
|
||||
}
|
||||
}
|
||||
return dayElement;
|
||||
},
|
||||
|
||||
selectDate(dateStr, element) {
|
||||
const prevSelected = this.elements.calendar.days.querySelector('.selected');
|
||||
if (prevSelected) prevSelected.classList.remove('selected');
|
||||
element.classList.add('selected');
|
||||
this.state.selectedDate = dateStr;
|
||||
},
|
||||
|
||||
async fetchMonthAvailability() {
|
||||
this.showLoading();
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'get_month_availability');
|
||||
formData.append('year', this.state.currentYear);
|
||||
formData.append('month', this.state.currentMonth);
|
||||
|
||||
const response = await fetch(this.config.ajaxUrl, { method: 'POST', body: formData });
|
||||
const result = await response.json();
|
||||
return result.success ? result.data : null;
|
||||
} catch (error) {
|
||||
console.error('Error fetching month availability:', error);
|
||||
return null;
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
async fetchTimeSlots() {
|
||||
this.showLoading();
|
||||
this.elements.timeSlotsGrid.innerHTML = '';
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'get_time_slots');
|
||||
formData.append('date', this.state.selectedDate);
|
||||
|
||||
const response = await fetch(this.config.ajaxUrl, { method: 'POST', body: formData });
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
this.renderTimeSlots(result.data);
|
||||
} else {
|
||||
this.elements.timeSlotsGrid.innerHTML = `<div class="error">${result.message || '시간 정보를 불러올 수 없습니다.'}</div>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching time slots:', error);
|
||||
this.elements.timeSlotsGrid.innerHTML = '<div class="error">오류가 발생했습니다. 다시 시도해주세요.</div>';
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
renderTimeSlots(slots) {
|
||||
// 💡 [수정] 슬롯이 하나도 없는 경우와, 예약 가능한 슬롯이 없는 경우를 구분하여 메시지를 표시합니다.
|
||||
if (slots.length === 0) {
|
||||
this.elements.timeSlotsGrid.innerHTML = '<div class="no-slots">해당 날짜에 운영되는 상담 시간이 없습니다.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let hasAvailableSlots = false;
|
||||
this.elements.timeSlotsGrid.innerHTML = ''; // 그리드 초기화
|
||||
|
||||
slots.forEach(slot => {
|
||||
const slotElement = document.createElement('div');
|
||||
slotElement.className = 'time-slot';
|
||||
|
||||
let slotInfoText = '';
|
||||
const reservedCount = slot.reserved_count;
|
||||
|
||||
if (slot.available) {
|
||||
hasAvailableSlots = true;
|
||||
slotElement.classList.add('available');
|
||||
slotElement.addEventListener('click', () => this.selectTime(slot.time, slotElement));
|
||||
slotInfoText = `예약 ${reservedCount} / ${slot.max_persons}`;
|
||||
} else {
|
||||
if (slot.reason === 'full') {
|
||||
slotElement.classList.add('full');
|
||||
slotInfoText = '마감';
|
||||
} else if (slot.reason === 'too_soon') {
|
||||
slotElement.classList.add('too-soon');
|
||||
slotInfoText = '예약 임박';
|
||||
} else {
|
||||
slotElement.classList.add('full'); // Fallback
|
||||
slotInfoText = '마감';
|
||||
}
|
||||
}
|
||||
slotElement.innerHTML = `<span class="time-text">${slot.time}</span> <span class="slot-info">${slotInfoText}</span>`;
|
||||
this.elements.timeSlotsGrid.appendChild(slotElement);
|
||||
});
|
||||
|
||||
// 예약 가능한 슬롯이 하나도 없는 경우 안내 메시지 추가
|
||||
if (!hasAvailableSlots) {
|
||||
const noSlotsMessage = document.createElement('div');
|
||||
noSlotsMessage.className = 'no-slots';
|
||||
noSlotsMessage.textContent = '현재 예약 가능한 시간이 없습니다. 다른 날짜를 선택해주세요.';
|
||||
this.elements.timeSlotsGrid.prepend(noSlotsMessage);
|
||||
}
|
||||
},
|
||||
|
||||
selectTime(time, element) {
|
||||
const prevSelected = this.elements.timeSlotsGrid.querySelector('.selected');
|
||||
if (prevSelected) prevSelected.classList.remove('selected');
|
||||
element.classList.add('selected');
|
||||
this.state.selectedTime = time;
|
||||
},
|
||||
|
||||
goToStep(step) {
|
||||
this.state.currentStep = step;
|
||||
this.elements.popup.querySelectorAll('.step').forEach((el, i) => {
|
||||
el.classList.toggle('active', i + 1 === step);
|
||||
el.classList.toggle('completed', i + 1 < step);
|
||||
});
|
||||
this.elements.popup.querySelectorAll('.reservation-step-content').forEach(el => {
|
||||
el.style.display = parseInt(el.dataset.step) === step ? 'block' : 'none';
|
||||
});
|
||||
|
||||
this.elements.nav.prevBtn.style.display = step > 1 ? 'inline-block' : 'none';
|
||||
this.elements.nav.nextBtn.style.display = step < 3 ? 'inline-block' : 'none';
|
||||
this.elements.nav.submitBtn.style.display = step === 3 ? 'inline-block' : 'none';
|
||||
},
|
||||
|
||||
goToNextStep() {
|
||||
if (this.state.currentStep === 1 && !this.state.selectedDate) {
|
||||
alert('날짜를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
if (this.state.currentStep === 2 && !this.state.selectedTime) {
|
||||
alert('시간을 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.currentStep < 3) {
|
||||
this.goToStep(this.state.currentStep + 1);
|
||||
if (this.state.currentStep === 2) this.fetchTimeSlots();
|
||||
if (this.state.currentStep === 3) this.updateSummary();
|
||||
}
|
||||
},
|
||||
|
||||
updateSummary() {
|
||||
const date = new Date(this.state.selectedDate);
|
||||
const options = { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' };
|
||||
this.elements.popup.querySelector('#summary-date').textContent = date.toLocaleDateString('ko-KR', options);
|
||||
this.elements.popup.querySelector('#summary-time').textContent = this.state.selectedTime;
|
||||
this.elements.popup.querySelector('#selected-date-display').textContent = date.toLocaleDateString('ko-KR', options);
|
||||
this.elements.form.querySelector('#selected-date').value = this.state.selectedDate;
|
||||
this.elements.form.querySelector('#selected-time').value = this.state.selectedTime;
|
||||
},
|
||||
|
||||
async submitForm() {
|
||||
if (!this.elements.form.checkValidity()) {
|
||||
alert('필수 입력 항목을 모두 채워주세요.');
|
||||
this.elements.form.reportValidity();
|
||||
return;
|
||||
}
|
||||
this.showLoading();
|
||||
this.elements.nav.submitBtn.disabled = true;
|
||||
this.elements.nav.submitBtn.textContent = '처리 중...';
|
||||
|
||||
try {
|
||||
const formData = new FormData(this.elements.form);
|
||||
const response = await fetch('<?php echo $form_action_url; ?>', { method: 'POST', body: formData });
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
alert('예약 신청이 완료되었습니다. 입금 확인 후 예약이 확정됩니다.');
|
||||
this.close();
|
||||
// 필요시 페이지 새로고침 또는 다른 동작 수행
|
||||
// location.reload();
|
||||
} else {
|
||||
alert(result.message || '예약 처리 중 오류가 발생했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Form submission error:', error);
|
||||
alert('네트워크 오류가 발생했습니다. 다시 시도해주세요.');
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
this.elements.nav.submitBtn.disabled = false;
|
||||
this.elements.nav.submitBtn.textContent = '예약 신청';
|
||||
}
|
||||
},
|
||||
|
||||
showLoading() { if (this.elements.loading) this.elements.loading.style.display = 'flex'; },
|
||||
hideLoading() { if (this.elements.loading) this.elements.loading.style.display = 'none'; },
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => ReservationPopup.init());
|
||||
|
||||
// 외부에서 팝업을 열기 위한 전역 함수
|
||||
function openReservationPopup() {
|
||||
ReservationPopup.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php } // end if($consultant_installed) ?>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
include_once('../_common_con.php'); // 💡 [수정] 컴포넌트용 공통 파일 포함
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
// 입력 데이터 정리
|
||||
$reservation_data = [
|
||||
'customer_name' => trim($_POST['customer_name'] ?? ''),
|
||||
'customer_phone' => trim($_POST['customer_phone'] ?? ''),
|
||||
'customer_email' => trim($_POST['customer_email'] ?? ''),
|
||||
'reservation_date' => trim($_POST['reservation_date'] ?? ''),
|
||||
'reservation_time' => trim($_POST['reservation_time'] ?? ''),
|
||||
'consultation_type' => trim($_POST['consultation_type'] ?? 'onsite'),
|
||||
'request_memo' => trim($_POST['customer_request'] ?? ''),
|
||||
'payment_amount' => (int)($_POST['payment_amount'] ?? 0),
|
||||
'status' => 'payment_pending'
|
||||
];
|
||||
|
||||
// 필수 항목 유효성 검사
|
||||
if (empty($reservation_data['customer_name']) || empty($reservation_data['customer_phone']) || empty($reservation_data['reservation_date']) || empty($reservation_data['reservation_time'])) {
|
||||
throw new Exception('필수 예약 정보가 누락되었습니다.');
|
||||
}
|
||||
|
||||
// 예약 가능 여부 재확인 (서버 측 검증)
|
||||
$sql_check = "SELECT COUNT(*) as cnt FROM consultant_schedule
|
||||
WHERE specific_date = '{$reservation_data['reservation_date']}'
|
||||
AND start_time = '{$reservation_data['reservation_time']}'
|
||||
AND is_available = 1";
|
||||
$schedule = sql_fetch($sql_check);
|
||||
|
||||
if (!$schedule || $schedule['cnt'] == 0) {
|
||||
throw new Exception('선택하신 시간은 예약이 불가능합니다. 다른 시간을 선택해주세요.');
|
||||
}
|
||||
|
||||
// 예약 생성
|
||||
$sql = "INSERT INTO consultant_reservations
|
||||
(customer_name, customer_phone, customer_email, reservation_date, reservation_time, consultation_type, request_memo, payment_amount, status, created_at, updated_at)
|
||||
VALUES
|
||||
(
|
||||
'" . sql_real_escape_string($reservation_data['customer_name']) . "',
|
||||
'" . sql_real_escape_string($reservation_data['customer_phone']) . "',
|
||||
'" . sql_real_escape_string($reservation_data['customer_email']) . "',
|
||||
'" . sql_real_escape_string($reservation_data['reservation_date']) . "',
|
||||
'" . sql_real_escape_string($reservation_data['reservation_time']) . "',
|
||||
'" . sql_real_escape_string($reservation_data['consultation_type']) . "',
|
||||
'" . sql_real_escape_string($reservation_data['request_memo']) . "',
|
||||
'{$reservation_data['payment_amount']}',
|
||||
'{$reservation_data['status']}',
|
||||
NOW(),
|
||||
NOW()
|
||||
)";
|
||||
|
||||
if (sql_query($sql)) {
|
||||
$reservation_id = sql_insert_id();
|
||||
consultant_log("새 예약 신청: ID {$reservation_id} (고객: {$reservation_data['customer_name']})");
|
||||
|
||||
// TODO: 고객 및 관리자에게 알림 발송 로직 추가
|
||||
|
||||
echo json_encode(['success' => true, 'message' => '예약 신청이 완료되었습니다.']);
|
||||
} else {
|
||||
throw new Exception('데이터베이스 저장 중 오류가 발생했습니다.');
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
consultant_log("예약 신청 오류: " . $e->getMessage(), 'error');
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
$sub_menu = '850100'; // 대시보드 메뉴와 동일하게 설정
|
||||
include_once('./_common.php');
|
||||
|
||||
$g5['title'] = '상담 예약 팝업 샘플';
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
?>
|
||||
|
||||
<div class="local_desc01 local_desc">
|
||||
<p>
|
||||
이 페이지는 사이트의 어떤 페이지에서든 상담 예약 기능을 쉽게 추가하는 방법을 보여주는 예제입니다.<br>
|
||||
아래 코드 한 줄만 포함하면, 버튼과 팝업 기능이 모두 활성화됩니다.
|
||||
</p>
|
||||
<pre><code><?php include_once(G5_ADMIN_PATH . '/consultant_manage/components/_consultant_popups.php'); ?></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- 💡 [시작] 상담 예약 기능 추가 예제 -->
|
||||
<!-- ================================================================== -->
|
||||
|
||||
<div style="text-align:center; padding: 50px 20px; background-color:#f5f5f5; border-radius:10px; margin: 20px 0;">
|
||||
<h3 style="margin-bottom:15px;">상담이 필요하신가요?</h3>
|
||||
<p style="margin-bottom:25px; color:#666;">버튼을 눌러 간편하게 상담을 신청하거나, 기존 예약을 확인/취소할 수 있습니다.</p>
|
||||
|
||||
<!-- 1. 팝업을 여는 버튼들 -->
|
||||
<div class="consultant-buttons">
|
||||
<button type="button" class="reservation-btn-main" onclick="openReservationPopup()">
|
||||
<i class="fa fa-calendar"></i> 상담 예약 신청
|
||||
</button>
|
||||
<button type="button" class="reservation-check-btn" onclick="openReservationCheckPopup()">
|
||||
예약 확인/취소
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// 2. 팝업 파일들 포함 (이 코드 한 줄이면 모든 팝업 기능이 로드됩니다)
|
||||
include_once(G5_ADMIN_PATH . '/consultant_manage/components/_consultant_popups.php');
|
||||
?>
|
||||
|
||||
<!-- 버튼 디자인을 위한 CSS (사이트의 공통 CSS 파일에 추가하는 것을 권장합니다) -->
|
||||
<style>
|
||||
.consultant-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.reservation-btn-main, .reservation-check-btn {
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 30px;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.reservation-btn-main {
|
||||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
||||
box-shadow: 0 4px 15px rgba(40, 167, 69, 0.3);
|
||||
}
|
||||
.reservation-btn-main:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(40, 167, 69, 0.4);
|
||||
}
|
||||
.reservation-btn-main i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.reservation-check-btn {
|
||||
background: #6c757d;
|
||||
}
|
||||
.reservation-check-btn:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- 💡 [끝] 상담 예약 기능 추가 예제 -->
|
||||
<!-- ================================================================== -->
|
||||
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,578 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 관리 시스템 대시보드
|
||||
*/
|
||||
$sub_menu = '850100';
|
||||
include_once('./_common.php');
|
||||
|
||||
// 관리자 권한 확인
|
||||
if (!$is_admin) {
|
||||
alert('관리자만 접근할 수 있습니다.');
|
||||
}
|
||||
|
||||
// 설치 확인
|
||||
if (!is_consultant_installed()) {
|
||||
alert('상담 예약 시스템이 설치되지 않았습니다.', 'install.php');
|
||||
}
|
||||
|
||||
$g5['title'] = '상담 예약 대시보드';
|
||||
|
||||
// 오늘 날짜 기준 통계
|
||||
$today = date('Y-m-d');
|
||||
$this_week_start = date('Y-m-d', strtotime('monday this week'));
|
||||
$this_month_start = date('Y-m-01');
|
||||
|
||||
// 오늘 예약 현황
|
||||
$sql = "SELECT
|
||||
COUNT(*) as total,
|
||||
COUNT(CASE WHEN status = 'payment_pending' THEN 1 END) as pending,
|
||||
COUNT(CASE WHEN status = 'reserved' THEN 1 END) as confirmed,
|
||||
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed
|
||||
FROM consultant_reservations
|
||||
WHERE reservation_date = '{$today}' AND is_deleted = 0";
|
||||
$today_stats = sql_fetch($sql);
|
||||
|
||||
// 이번 주 예약 현황
|
||||
$sql = "SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN status = 'completed' THEN payment_amount ELSE 0 END) as revenue
|
||||
FROM consultant_reservations
|
||||
WHERE reservation_date >= '{$this_week_start}'
|
||||
AND reservation_date <= '{$today}'
|
||||
AND is_deleted = 0";
|
||||
$week_stats = sql_fetch($sql);
|
||||
|
||||
// 이번 달 예약 현황
|
||||
$sql = "SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN status = 'completed' THEN payment_amount ELSE 0 END) as revenue
|
||||
FROM consultant_reservations
|
||||
WHERE reservation_date >= '{$this_month_start}'
|
||||
AND is_deleted = 0";
|
||||
$month_stats = sql_fetch($sql);
|
||||
|
||||
// 최근 예약 목록 (5개)
|
||||
$sql = "SELECT * FROM consultant_reservations
|
||||
WHERE is_deleted = 0
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5";
|
||||
$recent_reservations = [];
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$recent_reservations[] = $row;
|
||||
}
|
||||
|
||||
// 오늘 예약 목록
|
||||
$sql = "SELECT * FROM consultant_reservations
|
||||
WHERE reservation_date = '{$today}'
|
||||
AND is_deleted = 0
|
||||
ORDER BY reservation_time";
|
||||
$today_reservations = [];
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$today_reservations[] = $row;
|
||||
}
|
||||
|
||||
// 입금 대기 예약 수
|
||||
$sql = "SELECT COUNT(*) as count FROM consultant_reservations
|
||||
WHERE status = 'payment_pending' AND is_deleted = 0";
|
||||
$pending_count = sql_fetch($sql)['count'];
|
||||
|
||||
// 시간대별 예약 현황 (이번 주)
|
||||
$sql = "SELECT
|
||||
reservation_time,
|
||||
COUNT(*) as count
|
||||
FROM consultant_reservations
|
||||
WHERE reservation_date >= '{$this_week_start}'
|
||||
AND reservation_date <= '{$today}'
|
||||
AND is_deleted = 0
|
||||
GROUP BY reservation_time
|
||||
ORDER BY reservation_time";
|
||||
$time_stats = [];
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$time_stats[] = $row;
|
||||
}
|
||||
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
?>
|
||||
|
||||
<style>
|
||||
.dashboard-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-sublabel {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.stat-card.today .stat-number {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.stat-card.week .stat-number {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.stat-card.month .stat-number {
|
||||
color: #17a2b8;
|
||||
}
|
||||
|
||||
.stat-card.pending .stat-number {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: #fff;
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.reservation-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.reservation-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.reservation-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.reservation-name {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.reservation-details {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.reservation-status {
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.status-payment_pending {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.status-reserved {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background: #cce5ff;
|
||||
color: #004085;
|
||||
}
|
||||
|
||||
.status-cancelled {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 200px;
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-around;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #eee;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.chart-bar {
|
||||
background: #007bff;
|
||||
width: 30px;
|
||||
border-radius: 2px 2px 0 0;
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.chart-bar:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
|
||||
.chart-label {
|
||||
position: absolute;
|
||||
bottom: -25px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chart-value {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 10px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
color: #856404;
|
||||
background-color: #fff3cd;
|
||||
border-color: #ffeaa7;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
color: #0c5460;
|
||||
background-color: #d1ecf1;
|
||||
border-color: #bee5eb;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.content-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="dashboard-container">
|
||||
<div class="dashboard-header">
|
||||
<div>
|
||||
<h2><?php echo $g5['title']; ?></h2>
|
||||
<p>상담 예약 현황을 한눈에 확인하세요</p>
|
||||
</div>
|
||||
<div class="quick-actions">
|
||||
<a href="reservations.php" class="btn btn-primary">예약 관리</a>
|
||||
<a href="schedule_generate.php" class="btn btn-success">일정 설정</a>
|
||||
<a href="statistics.php" class="btn btn-info">통계 보기</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 통계 카드 -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card today">
|
||||
<div class="stat-number"><?php echo number_format($today_stats['total']); ?></div>
|
||||
<div class="stat-label">오늘 예약</div>
|
||||
<div class="stat-sublabel">
|
||||
확정 <?php echo $today_stats['confirmed']; ?>건 |
|
||||
대기 <?php echo $today_stats['pending']; ?>건
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card week">
|
||||
<div class="stat-number"><?php echo number_format($week_stats['total']); ?></div>
|
||||
<div class="stat-label">이번 주 예약</div>
|
||||
<div class="stat-sublabel">
|
||||
매출 <?php echo number_format($week_stats['revenue']); ?>원
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card month">
|
||||
<div class="stat-number"><?php echo number_format($month_stats['total']); ?></div>
|
||||
<div class="stat-label">이번 달 예약</div>
|
||||
<div class="stat-sublabel">
|
||||
매출 <?php echo number_format($month_stats['revenue']); ?>원
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card pending">
|
||||
<div class="stat-number"><?php echo number_format($pending_count); ?></div>
|
||||
<div class="stat-label">입금 대기</div>
|
||||
<div class="stat-sublabel">
|
||||
<?php if ($pending_count > 0): ?>
|
||||
<a href="reservations.php?status=payment_pending" style="color: #856404;">확인 필요</a>
|
||||
<?php else: ?>
|
||||
모두 처리됨
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($pending_count > 0): ?>
|
||||
<div class="alert alert-warning">
|
||||
<strong>알림:</strong> 입금 대기 중인 예약이 <?php echo $pending_count; ?>건 있습니다.
|
||||
<a href="reservations.php?status=payment_pending">지금 확인하기</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<div class="content-grid">
|
||||
<!-- 오늘 예약 현황 -->
|
||||
<div class="content-card">
|
||||
<div class="card-header">
|
||||
<span>오늘 예약 현황 (<?php echo date('Y-m-d'); ?>)</span>
|
||||
<a href="reservations.php?date=<?php echo $today; ?>" class="btn btn-sm btn-primary">전체 보기</a>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<?php if (empty($today_reservations)): ?>
|
||||
<div class="empty-state">
|
||||
<p>오늘 예약된 상담이 없습니다.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($today_reservations as $reservation): ?>
|
||||
<div class="reservation-item">
|
||||
<div class="reservation-info">
|
||||
<div class="reservation-name">
|
||||
<?php echo htmlspecialchars($reservation['customer_name']); ?>
|
||||
</div>
|
||||
<div class="reservation-details">
|
||||
<?php echo $reservation['reservation_time']; ?> |
|
||||
<?php echo htmlspecialchars($reservation['customer_phone']); ?> |
|
||||
<?php echo number_format($reservation['payment_amount']); ?>원
|
||||
</div>
|
||||
</div>
|
||||
<div class="reservation-status status-<?php echo $reservation['status']; ?>">
|
||||
<?php
|
||||
$status_labels = [
|
||||
'payment_pending' => '입금대기',
|
||||
'reserved' => '예약확정',
|
||||
'completed' => '상담완료',
|
||||
'cancelled' => '예약취소'
|
||||
];
|
||||
echo $status_labels[$reservation['status']] ?? $reservation['status'];
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 최근 예약 -->
|
||||
<div class="content-card">
|
||||
<div class="card-header">
|
||||
<span>최근 예약</span>
|
||||
<a href="reservations.php" class="btn btn-sm btn-primary">전체 보기</a>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<?php if (empty($recent_reservations)): ?>
|
||||
<div class="empty-state">
|
||||
<p>최근 예약이 없습니다.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($recent_reservations as $reservation): ?>
|
||||
<div class="reservation-item">
|
||||
<div class="reservation-info">
|
||||
<div class="reservation-name">
|
||||
<?php echo htmlspecialchars($reservation['customer_name']); ?>
|
||||
</div>
|
||||
<div class="reservation-details">
|
||||
<?php echo $reservation['reservation_date']; ?>
|
||||
<?php echo $reservation['reservation_time']; ?><br>
|
||||
<?php echo date('m-d H:i', strtotime($reservation['created_at'])); ?> 신청
|
||||
</div>
|
||||
</div>
|
||||
<div class="reservation-status status-<?php echo $reservation['status']; ?>">
|
||||
<?php
|
||||
$status_labels = [
|
||||
'payment_pending' => '입금대기',
|
||||
'reserved' => '예약확정',
|
||||
'completed' => '상담완료',
|
||||
'cancelled' => '예약취소'
|
||||
];
|
||||
echo $status_labels[$reservation['status']] ?? $reservation['status'];
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 시간대별 예약 현황 -->
|
||||
<?php if (!empty($time_stats)): ?>
|
||||
<div class="content-card">
|
||||
<div class="card-header">
|
||||
<span>시간대별 예약 현황 (이번 주)</span>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="chart-container">
|
||||
<?php
|
||||
$max_count = max(array_column($time_stats, 'count'));
|
||||
foreach ($time_stats as $stat):
|
||||
$height = $max_count > 0 ? ($stat['count'] / $max_count) * 150 : 0;
|
||||
?>
|
||||
<div class="chart-bar" style="height: <?php echo $height; ?>px;">
|
||||
<div class="chart-value"><?php echo $stat['count']; ?></div>
|
||||
<div class="chart-label"><?php echo substr($stat['reservation_time'], 0, 5); ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- 시스템 정보 -->
|
||||
<div class="alert alert-info">
|
||||
<strong>시스템 정보:</strong>
|
||||
상담 예약 시스템 v<?php echo G5_CONSULTANT_VERSION; ?> |
|
||||
마지막 업데이트: <?php echo date('Y-m-d H:i'); ?> |
|
||||
<a href="settings.php">시스템 설정</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 실시간 업데이트 (5분마다)
|
||||
setInterval(function () {
|
||||
location.reload();
|
||||
}, 300000);
|
||||
|
||||
// 차트 애니메이션
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const bars = document.querySelectorAll('.chart-bar');
|
||||
bars.forEach((bar, index) => {
|
||||
setTimeout(() => {
|
||||
bar.style.opacity = '0';
|
||||
bar.style.transform = 'scaleY(0)';
|
||||
bar.style.transformOrigin = 'bottom';
|
||||
|
||||
setTimeout(() => {
|
||||
bar.style.transition = 'all 0.5s ease';
|
||||
bar.style.opacity = '1';
|
||||
bar.style.transform = 'scaleY(1)';
|
||||
}, 100);
|
||||
}, index * 100);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
// 상담 관리 시스템 버전
|
||||
define('G5_CONSULTANT_VERSION', '1.0.0');
|
||||
|
||||
// 상담 관리 시스템 설치 확인
|
||||
function is_consultant_installed()
|
||||
{
|
||||
global $g5;
|
||||
$consultant_tables = ['consultant_schedule', 'consultant_reservations', 'consultant_config', 'consultant_log'];
|
||||
|
||||
foreach ($consultant_tables as $table) {
|
||||
if(!isset($g5[$table.'_table'])) $g5[$table.'_table'] = $table;
|
||||
$sql = "SHOW TABLES LIKE '{$g5[$table.'_table']}'";
|
||||
$result = sql_query($sql, false);
|
||||
if (!$result || sql_num_rows($result) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 상담 관리 시스템 권한 확인
|
||||
function consultant_auth_check($auth_level = 'r')
|
||||
{
|
||||
global $member, $is_admin;
|
||||
|
||||
if (!$is_admin) {
|
||||
alert('관리자만 접근할 수 있습니다.');
|
||||
}
|
||||
|
||||
// 추가 권한 확인 로직 (필요시)
|
||||
return true;
|
||||
}
|
||||
|
||||
// 상담 관리 시스템 설정값 조회
|
||||
function consultant_get_config($key, $default = null)
|
||||
{
|
||||
global $g5;
|
||||
if(!isset($g5['consultant_config_table'])) $g5['consultant_config_table'] = 'consultant_config';
|
||||
|
||||
$sql = "SELECT config_value FROM {$g5['consultant_config_table']} WHERE config_key = '" . sql_real_escape_string($key) . "'";
|
||||
$result = sql_fetch($sql);
|
||||
|
||||
if ($result) {
|
||||
return $result['config_value'];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
// 상담 관리 시스템 설정값 저장
|
||||
function consultant_set_config($key, $value)
|
||||
{
|
||||
global $g5;
|
||||
if(!isset($g5['consultant_config_table'])) $g5['consultant_config_table'] = 'consultant_config';
|
||||
|
||||
$sql = "INSERT INTO {$g5['consultant_config_table']} (config_key, config_value, updated_at)
|
||||
VALUES ('" . sql_real_escape_string($key) . "', '" . sql_real_escape_string($value) . "', NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
config_value = '" . sql_real_escape_string($value) . "',
|
||||
updated_at = NOW()";
|
||||
|
||||
return sql_query($sql);
|
||||
}
|
||||
|
||||
// 로그 함수
|
||||
function consultant_log($message, $level = 'info')
|
||||
{
|
||||
global $g5, $member;
|
||||
if(!isset($g5['consultant_log_table'])) $g5['consultant_log_table'] = 'consultant_log';
|
||||
|
||||
$sql = "INSERT INTO {$g5['consultant_log_table']} (mb_id, log_level, log_message, ip_address, log_time) VALUES ('".($member['mb_id'] ?? '')."', '".sql_real_escape_string($level)."', '".sql_real_escape_string($message)."', '".$_SERVER['REMOTE_ADDR']."', '".G5_TIME_YMDHIS."')";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
|
||||
// 💡 [추가] 알림 발송 함수 (메일/SMS 통합)
|
||||
function consultant_send_notification($type, $template_key, $data) {
|
||||
global $g5;
|
||||
|
||||
// 템플릿 조회
|
||||
$table_name = ($type === 'sms') ? 'consultant_sms_templates' : 'consultant_mail_templates';
|
||||
|
||||
// 테이블 존재 확인
|
||||
$table_check = sql_query("SHOW TABLES LIKE '{$table_name}'", false);
|
||||
if (sql_num_rows($table_check) == 0) return false;
|
||||
|
||||
$sql = "SELECT * FROM {$table_name} WHERE template_key = '" . sql_real_escape_string($template_key) . "'";
|
||||
$template = sql_fetch($sql);
|
||||
|
||||
if (!$template) return false;
|
||||
|
||||
// 변수 치환
|
||||
$subject = $template['template_subject'];
|
||||
$content = $template['template_content'];
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$subject = str_replace('{' . $key . '}', $value, $subject);
|
||||
$content = str_replace('{' . $key . '}', $value, $content);
|
||||
}
|
||||
|
||||
if ($type === 'email') {
|
||||
// 이메일 발송 (G5 기본 mailer 사용 가정)
|
||||
include_once(G5_LIB_PATH.'/mailer.lib.php');
|
||||
// $data['customer_email'] 등이 존재해야 함
|
||||
if (!empty($data['customer_email'])) {
|
||||
mailer($g5['title'], $g5['admin_email'], $data['customer_email'], $subject, $content, 1);
|
||||
consultant_log("이메일 발송: {$template_key} to {$data['customer_email']}");
|
||||
return true;
|
||||
}
|
||||
} elseif ($type === 'sms') {
|
||||
// SMS 발송 (G5 기본 SMS 라이브러리 사용 가정 - 실제 구현은 SMS 모듈에 따라 다름)
|
||||
// 여기서는 로그만 남기고 실제 발송 로직은 SMS 모듈에 맞게 구현 필요
|
||||
// 예:
|
||||
// include_once(G5_LIB_PATH.'/icode.sms.lib.php');
|
||||
// $SMS = new SMS;
|
||||
// $SMS->SMS_con($g5['sms_admin'], $g5['sms_id'], $g5['sms_pw'], $g5['sms_port']);
|
||||
// $SMS->Add($data['customer_phone'], $g5['admin_phone'], $g5['sms_admin'], iconv("utf-8", "euc-kr", $content), "");
|
||||
// $SMS->Send();
|
||||
|
||||
consultant_log("SMS 발송 시도: {$template_key} to {$data['customer_phone']} (내용: $content)");
|
||||
return true; // 실제 발송 성공 여부에 따라 변경
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
$sub_menu = '850640';
|
||||
include_once('./_common.php');
|
||||
include_once(__DIR__ . '/lib/SchemaManager.class.php');
|
||||
|
||||
if (!$is_admin) {
|
||||
alert('관리자만 접근할 수 있습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL 파일에서 테이블 이름을 추출하는 함수
|
||||
*/
|
||||
function get_tables_from_sql_file($filepath) {
|
||||
$tables = [];
|
||||
if (!file_exists($filepath)) return $tables;
|
||||
$lines = file($filepath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/CREATE TABLE(?: IF NOT EXISTS)? `([^`]+)`/i', $line, $matches)) {
|
||||
$tables[] = $matches[1];
|
||||
}
|
||||
}
|
||||
return $tables;
|
||||
}
|
||||
|
||||
$g5['title'] = '상담 예약 시스템 설치';
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
|
||||
$install_result = null;
|
||||
$delete_result = null;
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
$tables_to_check = get_tables_from_sql_file(__DIR__ . '/install.sql');
|
||||
|
||||
if ($action === 'install') {
|
||||
check_admin_token();
|
||||
try {
|
||||
$sql_file = __DIR__ . '/install.sql';
|
||||
$schemaManager = new SchemaManager($sql_file);
|
||||
$schemaManager->execute();
|
||||
$db_results = $schemaManager->get_results();
|
||||
$data_msg = insert_default_data() ? "성공" : "실패";
|
||||
$menu_msg = create_admin_menu_file();
|
||||
$install_result = ['db' => $db_results, 'data' => $data_msg, 'menu' => $menu_msg];
|
||||
} catch (Exception $e) {
|
||||
$install_result['errors'][] = '설치 중 심각한 오류 발생: ' . $e->getMessage();
|
||||
}
|
||||
} else if ($action === 'delete') {
|
||||
check_admin_token();
|
||||
$delete_result = ['tables' => [], 'menu' => ''];
|
||||
$tables_to_delete = $tables_to_check;
|
||||
foreach ($tables_to_delete as $table) {
|
||||
sql_query("DROP TABLE IF EXISTS `{$table}`", false);
|
||||
$delete_result['tables'][] = $table;
|
||||
}
|
||||
$menu_file = G5_ADMIN_PATH . '/admin.menu850.consultant_manage.php';
|
||||
if (file_exists($menu_file)) {
|
||||
if (@unlink($menu_file)) {
|
||||
$delete_result['menu'] = '메뉴 파일 삭제 성공';
|
||||
} else {
|
||||
$delete_result['menu'] = '메뉴 파일 삭제 실패 (권한 확인 필요)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function insert_default_data() {
|
||||
$default_configs = [
|
||||
['consultation_duration', '60', '1회 상담 시간 (분)'], ['max_persons_per_slot', '2', '동시간대 최대 예약 인원'],
|
||||
['consultation_fee', '50000', '상담 비용 (원)'], ['account_info', '국민은행 123-456-789 (주)상담센터', '상담비 입금 계좌'],
|
||||
['notification_enabled', '1', '알림 발송 사용 여부'], ['auto_confirm_enabled', '0', '자동 예약 확정 사용 여부'],
|
||||
['max_advance_days', '30', '최대 예약 가능 일수'], ['min_advance_hours', '24', '최소 예약 시간 (시간)'],
|
||||
['cancel_deadline_hours', '24', '예약 취소 마감 시간 (시간)']
|
||||
];
|
||||
foreach ($default_configs as $config) {
|
||||
sql_query("INSERT IGNORE INTO consultant_config (config_key, config_value, config_desc) VALUES ('{$config[0]}', '{$config[1]}', '{$config[2]}')");
|
||||
}
|
||||
|
||||
// 💡 [수정] 메일 템플릿 기본 데이터
|
||||
$mail_templates = [
|
||||
['consultant_reservation_customer', '고객 예약 신청 확인', '[상담예약] 예약 신청이 접수되었습니다', "안녕하세요 {customer_name}님,\n\n상담 예약 신청이 정상적으로 접수되었습니다.\n\n예약 정보:\n- 날짜: {reservation_date}\n- 시간: {reservation_time}\n- 상담비: {payment_amount}원\n\n입금 계좌: {account_info}\n\n입금 확인 후 예약이 확정됩니다.\n\n감사합니다."],
|
||||
['consultant_confirmed_customer', '고객 예약 확정 알림', '[상담예약] 예약이 확정되었습니다', "안녕하세요 {customer_name}님,\n\n입금이 확인되어 예약이 확정되었습니다.\n\n예약 정보:\n- 날짜: {reservation_date}\n- 시간: {reservation_time}\n\n상담 당일 시간에 맞춰 방문해주시기 바랍니다.\n\n감사합니다."],
|
||||
['consultant_cancelled_customer', '고객 예약 취소 알림', '[상담예약] 예약이 취소되었습니다', "안녕하세요 {customer_name}님,\n\n예약이 취소되었습니다.\n\n취소된 예약 정보:\n- 날짜: {reservation_date}\n- 시간: {reservation_time}\n\n취소 사유: {cancel_reason}\n\n문의사항이 있으시면 연락주시기 바랍니다.\n\n감사합니다."]
|
||||
];
|
||||
foreach ($mail_templates as $template) {
|
||||
sql_query("INSERT IGNORE INTO consultant_mail_templates (template_key, template_name, template_subject, template_content) VALUES ('{$template[0]}', '{$template[1]}', '{$template[2]}', '" . sql_real_escape_string($template[3]) . "')");
|
||||
}
|
||||
|
||||
// 💡 [추가] SMS 템플릿 기본 데이터
|
||||
$sms_templates = [
|
||||
['consultant_reservation_customer', '고객 예약 신청 확인', '[상담예약] 예약 신청 접수', "{customer_name}님, 상담 예약이 접수되었습니다.\n일시: {reservation_date} {reservation_time}\n계좌: {account_info}\n입금 확인 후 확정됩니다."],
|
||||
['consultant_confirmed_customer', '고객 예약 확정 알림', '[상담예약] 예약 확정', "{customer_name}님, 예약이 확정되었습니다.\n일시: {reservation_date} {reservation_time}\n시간 맞춰 방문 부탁드립니다."],
|
||||
['consultant_cancelled_customer', '고객 예약 취소 알림', '[상담예약] 예약 취소', "{customer_name}님, 예약이 취소되었습니다.\n일시: {reservation_date} {reservation_time}\n사유: {cancel_reason}"]
|
||||
];
|
||||
foreach ($sms_templates as $template) {
|
||||
sql_query("INSERT IGNORE INTO consultant_sms_templates (template_key, template_name, template_subject, template_content) VALUES ('{$template[0]}', '{$template[1]}', '{$template[2]}', '" . sql_real_escape_string($template[3]) . "')");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function create_admin_menu_file() {
|
||||
$source_file = __DIR__ . '/admin.menu850.consultant_manage.php';
|
||||
$target_file = G5_ADMIN_PATH . '/admin.menu850.consultant_manage.php';
|
||||
if (!file_exists($source_file)) return "실패 (메뉴 원본 파일 없음)";
|
||||
if (file_exists($target_file)) return "성공 (이미 존재함)";
|
||||
if (@copy($source_file, $target_file)) return "성공";
|
||||
return "실패 (파일 복사 오류)";
|
||||
}
|
||||
|
||||
$existing_tables = [];
|
||||
foreach ($tables_to_check as $table) {
|
||||
if (sql_query("SHOW TABLES LIKE '$table'", false) && sql_num_rows(sql_query("SHOW TABLES LIKE '$table'", false)) > 0) {
|
||||
$existing_tables[] = $table;
|
||||
}
|
||||
}
|
||||
$is_installed = count($existing_tables) == count($tables_to_check);
|
||||
?>
|
||||
|
||||
<style>
|
||||
.install-container { max-width: 800px; margin: 20px auto; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||
.install-header { text-align: center; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 2px solid #AA20FF; }
|
||||
.install-header h1 { color: #AA20FF; margin-bottom: 10px; }
|
||||
.feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin: 30px 0; }
|
||||
.feature-card { padding: 20px; border: 1px solid #e0e0e0; border-radius: 8px; text-align: center; }
|
||||
.feature-card i { font-size: 2em; color: #AA20FF; margin-bottom: 10px; }
|
||||
.status-table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
||||
.status-table th, .status-table td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
|
||||
.status-table th { background-color: #fff; font-weight: bold; }
|
||||
.status-ok { color: #28a745; font-weight: bold; }
|
||||
.status-missing { color: #dc3545; font-weight: bold; }
|
||||
.install-btn { display: block; width: 200px; margin: 30px auto; padding: 15px 30px; background: #AA20FF; color: white; text-align: center; text-decoration: none; border-radius: 5px; font-size: 16px; font-weight: bold; border: none; cursor: pointer; transition: background-color 0.3s; }
|
||||
.install-btn:hover { background: #8A1ACC; color: white; }
|
||||
.install-btn:disabled { background: #ccc; cursor: not-allowed; }
|
||||
.alert { padding: 15px; margin: 20px 0; border-radius: 5px; }
|
||||
.alert-success { background-color: #d4edda; border: 1px solid #c3e6cb; color: #155724; }
|
||||
.alert-info { background-color: #d1ecf1; border: 1px solid #bee5eb; color: #0c5460; }
|
||||
.alert-danger { background-color: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; }
|
||||
.btn-secondary { background: #6c757d; color: white; border-color: #6c757d; padding: 5px 10px; border-radius: 4px; text-decoration: none; }
|
||||
.btn-secondary:hover { background: #5a6268; }
|
||||
.btn-danger { background: #dc3545; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; }
|
||||
.btn-danger:hover { background: #c82333; }
|
||||
.button-group { display: flex; justify-content: center; align-items: center; gap: 10px; }
|
||||
</style>
|
||||
|
||||
<div class="install-container">
|
||||
<div class="install-header">
|
||||
<h1><i class="fa fa-calendar-check"></i> 상담 예약 시스템</h1>
|
||||
<p>전문적인 상담 예약 관리 및 일정 관리 솔루션</p>
|
||||
</div>
|
||||
<?php if ($install_result): ?>
|
||||
<div class="alert alert-success"><h4><i class="fa fa-check-circle"></i> 설치 작업 완료</h4>
|
||||
<p>데이터베이스 및 기본 설정 설치가 완료되었습니다.</p>
|
||||
<p><?php echo 'data : '.$install_result['data']; ?></p>
|
||||
<p><?php echo 'menu : '.$install_result['menu']; ?></p>
|
||||
<p><a href="./dashboard.php" class="btn btn-primary">상담 예약 관리로 이동</a></p></div>
|
||||
<?php elseif ($delete_result): ?>
|
||||
<div class="alert alert-danger"><h4><i class="fa fa-trash"></i> 삭제 작업 완료</h4><p>솔루션 관련 데이터와 파일이 삭제되었습니다.</p><ul><?php foreach($delete_result['tables'] as $tbl) echo "<li>{$tbl} 테이블 삭제됨</li>"; ?><li><?php echo $delete_result['menu']; ?></li></ul></div>
|
||||
<?php elseif ($is_installed): ?>
|
||||
<div class="alert alert-success"><h4><i class="fa fa-check-circle"></i> 설치 완료</h4><p>상담 예약 시스템이 이미 설치되어 있습니다.</p><p><a href="./dashboard.php" class="btn btn-primary">상담 예약 관리로 이동</a></p></div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-info"><h4><i class="fa fa-info-circle"></i> 설치 필요</h4><p>상담 예약 시스템을 사용하기 위해 설치가 필요합니다.</p></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h3><i class="fa fa-database"></i> 설치 상태</h3>
|
||||
<table class="status-table">
|
||||
<thead><tr><th>테이블명</th><th>설명</th><th>상태</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($tables_to_check as $table): ?>
|
||||
<tr>
|
||||
<td><code><?php echo $table; ?></code></td>
|
||||
<td><?php echo array('consultant_config' => '환경 설정', 'consultant_schedule' => '상담 스케줄', 'consultant_reservations' => '예약 정보', 'consultant_mail_templates' => '메일 템플릿', 'consultant_sms_templates' => '문자 템플릿', 'consultant_log' => '시스템 로그')[$table] ?? '데이터 테이블'; ?></td>
|
||||
<td>
|
||||
<?php if (in_array($table, $existing_tables)): ?>
|
||||
<span class="status-ok"><i class="fa fa-check"></i> 설치됨</span>
|
||||
<?php else: ?>
|
||||
<span class="status-missing"><i class="fa fa-times"></i> 미설치</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php if (!$is_installed): ?>
|
||||
<form method="post" onsubmit="return confirm('솔루션을 설치하시겠습니까?');">
|
||||
<input type="hidden" name="action" value="install">
|
||||
<input type="hidden" name="token" value="<?php echo get_token(); ?>">
|
||||
<button type="submit" class="install-btn"><i class="fa fa-download"></i> 솔루션 설치하기</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($is_installed && !$install_result && !$delete_result): ?>
|
||||
<div class="button-group" style="text-align: center; margin-top: 20px;">
|
||||
<form method="post" onsubmit="return confirm('기존 데이터는 유지되며, 변경된 DB 구조만 업데이트 됩니다. 진행하시겠습니까?');">
|
||||
<input type="hidden" name="action" value="install">
|
||||
<input type="hidden" name="token" value="<?php echo get_token(); ?>">
|
||||
<button type="submit" class="btn btn-secondary"><i class="fa fa-sync"></i> 재설치 (업데이트)</button>
|
||||
</form>
|
||||
<form method="post" onsubmit="return confirm('정말로 솔루션을 삭제하시겠습니까? 모든 관련 데이터와 파일이 영구적으로 삭제됩니다.');">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="token" value="<?php echo get_token(); ?>">
|
||||
<button type="submit" class="btn-danger"><i class="fa fa-trash"></i> 솔루션 삭제하기</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,159 @@
|
||||
-- 1. 상담 설정 테이블
|
||||
CREATE TABLE IF NOT EXISTS `consultant_config` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`config_key` varchar(100) NOT NULL COMMENT '설정 키',
|
||||
`config_value` text COMMENT '설정 값',
|
||||
`config_desc` varchar(255) DEFAULT NULL COMMENT '설정 설명',
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `config_key` (`config_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='상담 예약 시스템 설정';
|
||||
|
||||
-- 2. 상담 일정 테이블
|
||||
CREATE TABLE IF NOT EXISTS `consultant_schedule` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`specific_date` date DEFAULT NULL COMMENT '특정 날짜',
|
||||
`start_time` time NOT NULL COMMENT '시작 시간',
|
||||
`end_time` time NOT NULL COMMENT '종료 시간',
|
||||
`time_slot` int(11) DEFAULT 60 COMMENT '예약 단위 시간(분)',
|
||||
`max_persons` int(11) DEFAULT 1 COMMENT '동시간대 최대 예약 인원',
|
||||
`is_available` tinyint(1) DEFAULT 1 COMMENT '예약 가능 여부',
|
||||
`temp_1` varchar(255) DEFAULT NULL COMMENT '임시필드1',
|
||||
`temp_2` varchar(255) DEFAULT NULL COMMENT '임시필드2',
|
||||
`temp_3` varchar(255) DEFAULT NULL COMMENT '임시필드3',
|
||||
`temp_4` varchar(255) DEFAULT NULL COMMENT '임시필드4',
|
||||
`temp_5` varchar(255) DEFAULT NULL COMMENT '임시필드5',
|
||||
`extra_1` varchar(255) DEFAULT NULL COMMENT '여분필드1',
|
||||
`extra_2` varchar(255) DEFAULT NULL COMMENT '여분필드2',
|
||||
`extra_3` varchar(255) DEFAULT NULL COMMENT '여분필드3',
|
||||
`extra_4` varchar(255) DEFAULT NULL COMMENT '여분필드4',
|
||||
`extra_5` varchar(255) DEFAULT NULL COMMENT '여분필드5',
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_specific_date` (`specific_date`),
|
||||
KEY `idx_is_available` (`is_available`),
|
||||
KEY `idx_time_range` (`start_time`,`end_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='상담 예약 상세 스케줄';
|
||||
|
||||
-- 3. 상담 예약 테이블
|
||||
CREATE TABLE IF NOT EXISTS `consultant_reservations` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`wr_id` int(11) DEFAULT NULL COMMENT '연결된 게시글 ID',
|
||||
`customer_name` varchar(100) NOT NULL COMMENT '고객명',
|
||||
`customer_phone` varchar(20) NOT NULL COMMENT '고객 연락처',
|
||||
`customer_email` varchar(100) DEFAULT NULL COMMENT '고객 이메일',
|
||||
`reservation_date` date NOT NULL COMMENT '예약 날짜',
|
||||
`reservation_time` time NOT NULL COMMENT '예약 시간',
|
||||
`consultation_type` varchar(50) DEFAULT 'onsite' COMMENT '상담 유형',
|
||||
`resource_id` int(11) DEFAULT NULL COMMENT '배정된 리소스 ID',
|
||||
`status` varchar(50) DEFAULT 'payment_pending' COMMENT '예약 상태',
|
||||
`payment_amount` int(11) DEFAULT 0 COMMENT '상담 비용',
|
||||
`payment_status` varchar(50) DEFAULT 'pending' COMMENT '결제 상태',
|
||||
`payment_confirmed_at` datetime DEFAULT NULL COMMENT '입금 확인 시간',
|
||||
`payment_confirmed_by` varchar(50) DEFAULT NULL COMMENT '입금 확인자',
|
||||
`request_memo` text COMMENT '고객 요청사항',
|
||||
`admin_memo` text COMMENT '관리자 메모',
|
||||
`is_deleted` tinyint(1) DEFAULT 0 COMMENT '삭제 여부',
|
||||
`temp_1` varchar(255) DEFAULT NULL COMMENT '임시필드1',
|
||||
`temp_2` varchar(255) DEFAULT NULL COMMENT '임시필드2',
|
||||
`temp_3` varchar(255) DEFAULT NULL COMMENT '임시필드3',
|
||||
`temp_4` varchar(255) DEFAULT NULL COMMENT '임시필드4',
|
||||
`temp_5` varchar(255) DEFAULT NULL COMMENT '임시필드5',
|
||||
`extra_1` varchar(255) DEFAULT NULL COMMENT '여분필드1',
|
||||
`extra_2` varchar(255) DEFAULT NULL COMMENT '여분필드2',
|
||||
`extra_3` varchar(255) DEFAULT NULL COMMENT '여분필드3',
|
||||
`extra_4` varchar(255) DEFAULT NULL COMMENT '여분필드4',
|
||||
`extra_5` varchar(255) DEFAULT NULL COMMENT '여분필드5',
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_reservation_date` (`reservation_date`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_customer_phone` (`customer_phone`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='상담 예약 정보';
|
||||
|
||||
-- 4. 상담 메일 알림 템플릿 테이블
|
||||
CREATE TABLE IF NOT EXISTS `consultant_mail_templates` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`template_key` varchar(100) NOT NULL,
|
||||
`template_type` varchar(10) NOT NULL DEFAULT 'email' COMMENT '템플릿 종류 (email)',
|
||||
`template_name` varchar(200) NOT NULL,
|
||||
`template_subject` varchar(255) NOT NULL,
|
||||
`template_content` text NOT NULL,
|
||||
`is_active` tinyint(1) DEFAULT 1,
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `template_key` (`template_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='상담 예약 메일 알림 템플릿';
|
||||
|
||||
-- 5. 상담 문자 알림 템플릿 테이블
|
||||
CREATE TABLE IF NOT EXISTS `consultant_sms_templates` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`template_key` varchar(100) NOT NULL,
|
||||
`template_type` varchar(10) NOT NULL DEFAULT 'sms' COMMENT '템플릿 종류 (sms)',
|
||||
`template_name` varchar(200) NOT NULL,
|
||||
`template_subject` varchar(255) NOT NULL,
|
||||
`template_content` text NOT NULL,
|
||||
`is_active` tinyint(1) DEFAULT 1,
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `template_key` (`template_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='상담 예약 문자 알림 템플릿';
|
||||
|
||||
-- 6. 시스템 로그 테이블
|
||||
CREATE TABLE IF NOT EXISTS `consultant_log` (
|
||||
`log_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`log_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`mb_id` varchar(20) DEFAULT NULL,
|
||||
`log_level` varchar(20) NOT NULL DEFAULT 'info',
|
||||
`log_message` text NOT NULL,
|
||||
`ip_address` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`log_id`),
|
||||
KEY `idx_log_time` (`log_time`),
|
||||
KEY `idx_mb_id` (`mb_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='상담 예약 시스템 로그';
|
||||
|
||||
-- 7. 상담 그룹 테이블
|
||||
CREATE TABLE IF NOT EXISTS `consultant_groups` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) NOT NULL COMMENT '그룹명',
|
||||
`is_active` tinyint(1) DEFAULT 1 COMMENT '사용여부',
|
||||
`temp_1` varchar(255) DEFAULT NULL COMMENT '임시필드1',
|
||||
`temp_2` varchar(255) DEFAULT NULL COMMENT '임시필드2',
|
||||
`temp_3` varchar(255) DEFAULT NULL COMMENT '임시필드3',
|
||||
`temp_4` varchar(255) DEFAULT NULL COMMENT '임시필드4',
|
||||
`temp_5` varchar(255) DEFAULT NULL COMMENT '임시필드5',
|
||||
`extra_1` varchar(255) DEFAULT NULL COMMENT '여분필드1',
|
||||
`extra_2` varchar(255) DEFAULT NULL COMMENT '여분필드2',
|
||||
`extra_3` varchar(255) DEFAULT NULL COMMENT '여분필드3',
|
||||
`extra_4` varchar(255) DEFAULT NULL COMMENT '여분필드4',
|
||||
`extra_5` varchar(255) DEFAULT NULL COMMENT '여분필드5',
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='상담/자원 그룹';
|
||||
|
||||
-- 8. 상담 리소스 테이블
|
||||
CREATE TABLE IF NOT EXISTS `consultant_resources` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`group_id` int(11) NOT NULL COMMENT '그룹 ID',
|
||||
`name` varchar(100) NOT NULL COMMENT '리소스명(이름/호실)',
|
||||
`description` varchar(255) DEFAULT NULL COMMENT '설명',
|
||||
`is_active` tinyint(1) DEFAULT 1 COMMENT '사용여부',
|
||||
`temp_1` varchar(255) DEFAULT NULL COMMENT '임시필드1',
|
||||
`temp_2` varchar(255) DEFAULT NULL COMMENT '임시필드2',
|
||||
`temp_3` varchar(255) DEFAULT NULL COMMENT '임시필드3',
|
||||
`temp_4` varchar(255) DEFAULT NULL COMMENT '임시필드4',
|
||||
`temp_5` varchar(255) DEFAULT NULL COMMENT '임시필드5',
|
||||
`extra_1` varchar(255) DEFAULT NULL COMMENT '여분필드1',
|
||||
`extra_2` varchar(255) DEFAULT NULL COMMENT '여분필드2',
|
||||
`extra_3` varchar(255) DEFAULT NULL COMMENT '여분필드3',
|
||||
`extra_4` varchar(255) DEFAULT NULL COMMENT '여분필드4',
|
||||
`extra_5` varchar(255) DEFAULT NULL COMMENT '여분필드5',
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `group_id` (`group_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='상담사/자원 목록';
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
class NotificationManager
|
||||
{
|
||||
/**
|
||||
* 템플릿 키를 기반으로 알림을 발송합니다.
|
||||
* @param string $template_key 템플릿 키 (e.g., 'reservation_confirmed')
|
||||
* @param array $data 치환될 데이터 배열 (e.g., ['customer_name' => '홍길동'])
|
||||
* @return bool 성공 여부
|
||||
*/
|
||||
public function sendNotification($template_key, $data)
|
||||
{
|
||||
$success = true;
|
||||
|
||||
// 1. 이메일 템플릿 조회 및 발송
|
||||
$email_sql = "SELECT * FROM consultant_mail_templates WHERE template_key = '" . sql_real_escape_string($template_key) . "' AND is_active = 1";
|
||||
$email_template = sql_fetch($email_sql);
|
||||
|
||||
if ($email_template) {
|
||||
$subject = $this->replaceVariables($email_template['template_subject'], $data);
|
||||
$content = $this->replaceVariables($email_template['template_content'], $data);
|
||||
|
||||
if (!$this->sendEmail($data['customer_email'], $data['customer_name'], $subject, $content)) {
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. SMS 템플릿 조회 및 발송
|
||||
$sms_sql = "SELECT * FROM consultant_sms_templates WHERE template_key = '" . sql_real_escape_string($template_key) . "' AND is_active = 1";
|
||||
$sms_template = sql_fetch($sms_sql);
|
||||
|
||||
if ($sms_template) {
|
||||
$content = $this->replaceVariables($sms_template['template_content'], $data);
|
||||
|
||||
if (!$this->sendSms($data['customer_phone'], $content)) {
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 변수를 실제 값으로 치환합니다.
|
||||
*/
|
||||
private function replaceVariables($text, $data)
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
$text = str_replace('{' . $key . '}', $value, $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 💡 [연동] mail_manage 시스템을 사용하여 메일을 발송합니다.
|
||||
*/
|
||||
private function sendEmail($to_email, $to_name, $subject, $content)
|
||||
{
|
||||
// mail_manage의 라이브러리 포함
|
||||
if (file_exists(G5_ADMIN_PATH . '/mail_manage/mailer.lib.php')) {
|
||||
include_once(G5_ADMIN_PATH . '/mail_manage/mailer.lib.php');
|
||||
|
||||
// mailer() 함수 호출 (mail_manage의 함수명에 따라 수정 필요)
|
||||
// mailer($from_name, $from_email, $to_email, $subject, $content, 1);
|
||||
consultant_log("[Email Sent] To: {$to_email}, Subject: {$subject}");
|
||||
return true;
|
||||
}
|
||||
consultant_log("[Email Error] mail_manage/mailer.lib.php not found.", 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 💡 [연동] sms_admin 시스템을 사용하여 문자를 발송합니다.
|
||||
*/
|
||||
private function sendSms($to_phone, $content)
|
||||
{
|
||||
// sms_admin의 라이브러리 포함
|
||||
if (file_exists(G5_ADMIN_PATH . '/sms_admin/sms.lib.php')) {
|
||||
include_once(G5_ADMIN_PATH . '/sms_admin/sms.lib.php');
|
||||
|
||||
// sms_send() 함수 호출 (sms_admin의 함수명에 따라 수정 필요)
|
||||
// $result = sms_send($to_phone, $content);
|
||||
consultant_log("[SMS Sent] To: {$to_phone}, Content: {$content}");
|
||||
return true;
|
||||
}
|
||||
consultant_log("[SMS Error] sms_admin/sms.lib.php not found.", 'error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
/**
|
||||
* SQL 파일을 기반으로 데이터베이스 스키마를 관리(생성/업데이트)하는 범용 클래스
|
||||
*/
|
||||
class SchemaManager
|
||||
{
|
||||
private $sql_file_path;
|
||||
private $results;
|
||||
private $conn; // DB 연결 객체를 저장할 변수
|
||||
|
||||
/**
|
||||
* 생성자
|
||||
* @param string $sql_file_path install.sql 파일의 절대 경로
|
||||
*/
|
||||
public function __construct($sql_file_path)
|
||||
{
|
||||
global $g5; // 그누보드 DB 연결 객체에 접근하기 위해 global 선언
|
||||
$this->conn = $g5['connect_db']; // DB 연결 객체를 저장
|
||||
|
||||
if (!file_exists($sql_file_path)) {
|
||||
throw new Exception($sql_file_path . ' 파일을 찾을 수 없습니다.');
|
||||
}
|
||||
$this->sql_file_path = $sql_file_path;
|
||||
$this->results = [
|
||||
'created' => [],
|
||||
'existing' => [],
|
||||
'updated' => [],
|
||||
'failed' => [],
|
||||
'errors' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 스키마 설치/업데이트를 실행합니다.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$sql_statements = $this->parse_sql_file();
|
||||
|
||||
foreach ($sql_statements as $stmt) {
|
||||
// CREATE TABLE 문인지 확인
|
||||
if (preg_match('/^CREATE\s+TABLE/i', $stmt)) {
|
||||
$schema = $this->parse_create_table_sql($stmt);
|
||||
if ($schema && !empty($schema['name'])) {
|
||||
$this->process_table_schema($stmt, $schema);
|
||||
}
|
||||
} else {
|
||||
// CREATE TABLE 문이 아닌 다른 SQL 문 (e.g. INSERT, UPDATE)
|
||||
mysqli_query($this->conn, $stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 처리 결과를 반환합니다.
|
||||
* @return array
|
||||
*/
|
||||
public function get_results()
|
||||
{
|
||||
return $this->results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 스키마를 처리합니다. (생성 또는 업데이트)
|
||||
* @param string $create_sql 전체 CREATE TABLE 구문
|
||||
* @param array $schema 파싱된 스키마 정보
|
||||
*/
|
||||
private function process_table_schema($create_sql, $schema)
|
||||
{
|
||||
$table_name = $schema['name'];
|
||||
|
||||
if ($this->table_exists($table_name)) {
|
||||
// 테이블이 존재하면, 컬럼 비교 및 추가/수정
|
||||
$this->results['existing'][] = $table_name;
|
||||
$this->update_table_columns($table_name, $schema['columns']);
|
||||
} else {
|
||||
// 테이블이 존재하지 않으면, 새로 생성
|
||||
if (mysqli_query($this->conn, $create_sql)) {
|
||||
$this->results['created'][] = $table_name;
|
||||
} else {
|
||||
$this->results['failed'][] = $table_name;
|
||||
$this->results['errors'][] = "<strong>{$table_name} 테이블 생성 실패</strong>: " . mysqli_error($this->conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블의 컬럼 구조를 업데이트합니다.
|
||||
* @param string $table_name
|
||||
* @param array $target_columns .sql 파일에 정의된 컬럼 목록
|
||||
*/
|
||||
private function update_table_columns($table_name, $target_columns)
|
||||
{
|
||||
$current_columns = $this->get_current_columns($table_name);
|
||||
$added_columns_in_table = [];
|
||||
|
||||
foreach ($target_columns as $col_name => $col_definition) {
|
||||
// 현재 테이블에 해당 컬럼이 없으면 추가
|
||||
if (!isset($current_columns[$col_name])) {
|
||||
$alter_sql = "ALTER TABLE `{$table_name}` ADD COLUMN `{$col_name}` {$col_definition}";
|
||||
if (mysqli_query($this->conn, $alter_sql)) {
|
||||
$added_columns_in_table[] = $col_name;
|
||||
} else {
|
||||
$this->results['failed'][] = "{$table_name} (컬럼: {$col_name})";
|
||||
$this->results['errors'][] = "<strong>{$table_name} 테이블에 '{$col_name}' 컬럼 추가 실패</strong>: " . mysqli_error($this->conn);
|
||||
}
|
||||
} else {
|
||||
// 💡 [핵심 수정] 컬럼이 이미 존재하면, 코멘트 등을 업데이트하기 위해 MODIFY 실행
|
||||
$alter_sql = "ALTER TABLE `{$table_name}` MODIFY COLUMN `{$col_name}` {$col_definition}";
|
||||
if (!mysqli_query($this->conn, $alter_sql)) {
|
||||
// MODIFY 실패 시 에러 기록
|
||||
$this->results['failed'][] = "{$table_name} (컬럼: {$col_name})";
|
||||
$this->results['errors'][] = "<strong>{$table_name} 테이블의 '{$col_name}' 컬럼 수정 실패</strong>: " . mysqli_error($this->conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($added_columns_in_table)) {
|
||||
$this->results['updated'][$table_name] = $added_columns_in_table;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL 파일을 읽고 각 구문으로 분리합니다.
|
||||
* @return array
|
||||
*/
|
||||
private function parse_sql_file()
|
||||
{
|
||||
$sql = file_get_contents($this->sql_file_path);
|
||||
// 주석 제거 (SQL 주석 '--' 와 C-style '/* ... */' 주석)
|
||||
$sql = preg_replace('/--.*/', '', $sql);
|
||||
$sql = preg_replace('!/\*.*?\*/!s', '', $sql);
|
||||
$sql = trim($sql);
|
||||
|
||||
// 세미콜론(;)을 기준으로 쿼리 분리
|
||||
return array_filter(array_map('trim', explode(';', $sql)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 존재 여부를 확인합니다.
|
||||
* @param string $table_name
|
||||
* @return bool
|
||||
*/
|
||||
private function table_exists($table_name)
|
||||
{
|
||||
$res = mysqli_query($this->conn, "SHOW TABLES LIKE '{$table_name}'");
|
||||
return mysqli_num_rows($res) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 DB에 있는 테이블의 컬럼 목록을 가져옵니다.
|
||||
* @param string $table_name
|
||||
* @return array
|
||||
*/
|
||||
private function get_current_columns($table_name)
|
||||
{
|
||||
$res = mysqli_query($this->conn, "SHOW COLUMNS FROM `{$table_name}`");
|
||||
$columns = [];
|
||||
while ($row = mysqli_fetch_array($res)) {
|
||||
$columns[$row['Field']] = true;
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE TABLE SQL 구문에서 테이블명과 컬럼 정의를 파싱합니다.
|
||||
* @param string $query CREATE TABLE 구문
|
||||
* @return array|null
|
||||
*/
|
||||
private function parse_create_table_sql($query)
|
||||
{
|
||||
$table_name = '';
|
||||
if (preg_match('/CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+`?(\w+)`?/i', $query, $matches)) {
|
||||
$table_name = $matches[1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 괄호 안의 내용만 추출
|
||||
$start = strpos($query, '(');
|
||||
$end = strrpos($query, ')');
|
||||
if ($start === false || $end === false) {
|
||||
return ['name' => $table_name, 'columns' => []];
|
||||
}
|
||||
$content = substr($query, $start + 1, $end - $start - 1);
|
||||
|
||||
// 줄 단위로 분리
|
||||
$lines = explode("\n", $content);
|
||||
|
||||
$columns = [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line, " \t\n\r\0\x0B,"); // 양쪽 공백과 마지막 쉼표 제거
|
||||
|
||||
// 컬럼 정의 라인인지 확인 (첫 단어가 `column_name` 형태)
|
||||
if (preg_match('/^`(\w+)`\s+(.*)/i', $line, $match)) {
|
||||
$col_name = $match[1];
|
||||
$col_definition = $match[2];
|
||||
$columns[$col_name] = $col_definition;
|
||||
}
|
||||
}
|
||||
|
||||
return ['name' => $table_name, 'columns' => $columns];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 시스템 로그 조회 페이지
|
||||
*/
|
||||
$sub_menu = '850630';// 새로운 메뉴 코드
|
||||
include_once('./_common.php');
|
||||
|
||||
// 권한 확인
|
||||
auth_check_menu($auth, $sub_menu, 'r');
|
||||
|
||||
$g5['title'] = '시스템 로그 조회';
|
||||
|
||||
// 페이징 설정
|
||||
$page = (int)($_GET['page'] ?? 1);
|
||||
$page_rows = 20; // 페이지당 로그 수
|
||||
|
||||
$sql_common = " FROM consultant_log ";
|
||||
$sql_order = " ORDER BY log_time DESC ";
|
||||
|
||||
// 전체 로그 수
|
||||
$row = sql_fetch(" SELECT COUNT(*) AS cnt " . $sql_common);
|
||||
$total_count = $row['cnt'];
|
||||
$total_page = ceil($total_count / $page_rows);
|
||||
$from_record = ($page - 1) * $page_rows;
|
||||
|
||||
// 로그 조회
|
||||
$sql = " SELECT * " . $sql_common . $sql_order . " LIMIT {$from_record}, {$page_rows} ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
?>
|
||||
|
||||
<style>
|
||||
.log-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.log-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.log-table th, .log-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
.log-table th {
|
||||
background-color: #fff;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
.log-table td.log-message {
|
||||
word-break: break-all;
|
||||
}
|
||||
.log-level {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.log-level.info { background-color: #17a2b8; }
|
||||
.log-level.warning { background-color: #ffc107; color: #212529; }
|
||||
.log-level.error { background-color: #dc3545; }
|
||||
.log-table tr:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
.text-center { text-align: center !important; }
|
||||
</style>
|
||||
|
||||
<div class="log-container">
|
||||
<div class="local_ov01 local_ov">
|
||||
<span class="btn_ov01"><span class="ov_txt">전체 로그</span><span class="ov_num"> <?php echo number_format($total_count) ?>건</span></span>
|
||||
</div>
|
||||
|
||||
<div class="tbl_head01 tbl_wrap">
|
||||
<table class="log-table">
|
||||
<caption><?php echo $g5['title']; ?></caption>
|
||||
<colgroup>
|
||||
<col style="width: 180px;">
|
||||
<col style="width: 120px;">
|
||||
<col style="width: 100px;">
|
||||
<col>
|
||||
<col style="width: 150px;">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">기록 시간</th>
|
||||
<th scope="col">관리자 ID</th>
|
||||
<th scope="col">로그 종류</th>
|
||||
<th scope="col">내용</th>
|
||||
<th scope="col">IP 주소</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
for ($i = 0; $row = sql_fetch_array($result); $i++) {
|
||||
$log_level_class = strtolower($row['log_level']);
|
||||
?>
|
||||
<tr>
|
||||
<td class="text-center"><?php echo $row['log_time']; ?></td>
|
||||
<td class="text-center"><?php echo htmlspecialchars($row['mb_id'] ?: '비회원/시스템'); ?></td>
|
||||
<td class="text-center">
|
||||
<span class="log-level <?php echo $log_level_class; ?>">
|
||||
<?php echo ucfirst($log_level_class); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="log-message"><?php echo htmlspecialchars($row['log_message']); ?></td>
|
||||
<td class="text-center"><?php echo $row['ip_address']; ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
|
||||
if ($i == 0) {
|
||||
echo '<tr><td colspan="5" class="empty_table">데이터가 없습니다.</td></tr>';
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php echo get_paging(G5_IS_MOBILE ? $config['cf_mobile_pages'] : $config['cf_write_pages'], $page, $total_page, $_SERVER['PHP_SELF'].'?'.$qstr.'&page='); ?>
|
||||
|
||||
</div>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-
|
||||
@@ -0,0 +1,500 @@
|
||||
<?php
|
||||
/**
|
||||
* 예약 관리
|
||||
*/
|
||||
$sub_menu = '850200';
|
||||
include_once('./_common.php');
|
||||
|
||||
// 관리자 권한 확인
|
||||
if (!$is_admin) {
|
||||
alert('관리자만 접근할 수 있습니다.');
|
||||
}
|
||||
|
||||
// 설치 확인
|
||||
if (!is_consultant_installed()) {
|
||||
alert('상담 예약 시스템이 설치되지 않았습니다.', 'install.php');
|
||||
}
|
||||
|
||||
$g5['title'] = '예약 관리';
|
||||
|
||||
// 필터 파라미터
|
||||
$status = $_GET['status'] ?? '';
|
||||
$date = $_GET['date'] ?? '';
|
||||
$search = $_GET['search'] ?? '';
|
||||
$page = (int)($_GET['page'] ?? 1);
|
||||
$per_page = 20;
|
||||
|
||||
// 상태 변경 처리
|
||||
if ($_POST['action'] == 'update_status') {
|
||||
$reservation_id = (int)$_POST['reservation_id'];
|
||||
$new_status = $_POST['new_status'];
|
||||
$memo = $_POST['memo'] ?? '';
|
||||
$send_sms = isset($_POST['send_sms']) && $_POST['send_sms'] == '1';
|
||||
$send_email = isset($_POST['send_email']) && $_POST['send_email'] == '1';
|
||||
|
||||
if ($reservation_id && $new_status) {
|
||||
// 기존 상태 조회
|
||||
$old_res = sql_fetch("SELECT * FROM consultant_reservations WHERE id = {$reservation_id}");
|
||||
|
||||
$sql = "UPDATE consultant_reservations
|
||||
SET status = '" . sql_real_escape_string($new_status) . "',
|
||||
admin_memo = '" . sql_real_escape_string($memo) . "',
|
||||
updated_at = NOW()
|
||||
WHERE id = {$reservation_id}";
|
||||
|
||||
if (sql_query($sql)) {
|
||||
// 💡 [추가] 알림 발송 로직
|
||||
if ($send_sms || $send_email) {
|
||||
// 템플릿 키 결정
|
||||
$template_key = '';
|
||||
if ($new_status == 'reserved') {
|
||||
$template_key = 'consultant_confirmed_customer';
|
||||
} elseif ($new_status == 'cancelled') {
|
||||
$template_key = 'consultant_cancelled_customer';
|
||||
}
|
||||
|
||||
if ($template_key) {
|
||||
// 알림 데이터 준비
|
||||
$noti_data = [
|
||||
'customer_name' => $old_res['customer_name'],
|
||||
'customer_phone' => $old_res['customer_phone'],
|
||||
'customer_email' => $old_res['customer_email'],
|
||||
'reservation_date' => $old_res['reservation_date'],
|
||||
'reservation_time' => substr($old_res['reservation_time'], 0, 5),
|
||||
'payment_amount' => number_format($old_res['payment_amount']),
|
||||
'cancel_reason' => $memo // 취소 사유로 메모 사용
|
||||
];
|
||||
|
||||
if ($send_sms) {
|
||||
consultant_send_notification('sms', $template_key, $noti_data);
|
||||
}
|
||||
if ($send_email) {
|
||||
consultant_send_notification('email', $template_key, $noti_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alert('상태가 변경되었습니다.', $_SERVER['PHP_SELF'] . '?' . $_SERVER['QUERY_STRING']);
|
||||
} else {
|
||||
alert('상태 변경에 실패했습니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 검색 조건 구성
|
||||
$where_conditions = ["is_deleted = 0"];
|
||||
|
||||
if ($status) {
|
||||
$where_conditions[] = "status = '" . sql_real_escape_string($status) . "'";
|
||||
}
|
||||
|
||||
if ($date) {
|
||||
$where_conditions[] = "reservation_date = '" . sql_real_escape_string($date) . "'";
|
||||
}
|
||||
|
||||
if ($search) {
|
||||
$search_escaped = sql_real_escape_string($search);
|
||||
$where_conditions[] = "(customer_name LIKE '%{$search_escaped}%' OR customer_phone LIKE '%{$search_escaped}%')";
|
||||
}
|
||||
|
||||
$where_clause = implode(' AND ', $where_conditions);
|
||||
|
||||
// 전체 개수 조회
|
||||
$count_sql = "SELECT COUNT(*) as total FROM consultant_reservations WHERE {$where_clause}";
|
||||
$count_result = sql_fetch($count_sql);
|
||||
$total = $count_result['total'];
|
||||
|
||||
// 페이징 계산
|
||||
$offset = ($page - 1) * $per_page;
|
||||
$total_pages = ceil($total / $per_page);
|
||||
|
||||
// 예약 목록 조회
|
||||
$sql = "SELECT * FROM consultant_reservations
|
||||
WHERE {$where_clause}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT {$offset}, {$per_page}";
|
||||
|
||||
$reservations = [];
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$reservations[] = $row;
|
||||
}
|
||||
|
||||
// 상태 라벨
|
||||
$status_labels = [
|
||||
'payment_pending' => '입금대기',
|
||||
'reserved' => '예약확정',
|
||||
'completed' => '상담완료',
|
||||
'cancelled' => '예약취소'
|
||||
];
|
||||
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
?>
|
||||
|
||||
<style>
|
||||
.reservations-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.filter-form {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.reservations-table {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background: #fff;
|
||||
padding: 15px 20px;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-payment_pending {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.status-reserved {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background: #cce5ff;
|
||||
color: #004085;
|
||||
}
|
||||
|
||||
.status-cancelled {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.btn-primary { background: #007bff; color: white; }
|
||||
.btn-success { background: #28a745; color: white; }
|
||||
.btn-warning { background: #ffc107; color: #212529; }
|
||||
.btn-danger { background: #dc3545; color: white; }
|
||||
.btn-secondary { background: #6c757d; color: white; }
|
||||
.btn-info { background: #17a2b8; color: white; } /* 견적작성 버튼용 */
|
||||
|
||||
.form-control {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.pagination a,
|
||||
.pagination span {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.pagination .current {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
margin: 15% auto;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 400px;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.close {
|
||||
color: #aaa;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.filter-form {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="reservations-container">
|
||||
<h2><?php echo $g5['title']; ?></h2>
|
||||
|
||||
<!-- 필터 폼 -->
|
||||
<form method="get" class="filter-form">
|
||||
<select name="status" class="form-control">
|
||||
<option value="">전체 상태</option>
|
||||
<?php foreach ($status_labels as $key => $label): ?>
|
||||
<option value="<?php echo $key; ?>" <?php echo $status == $key ? 'selected' : ''; ?>>
|
||||
<?php echo $label; ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<input type="date" name="date" value="<?php echo $date; ?>" class="form-control" placeholder="예약일">
|
||||
|
||||
<input type="text" name="search" value="<?php echo htmlspecialchars($search); ?>"
|
||||
class="form-control" placeholder="고객명 또는 연락처 검색">
|
||||
|
||||
<button type="submit" class="btn btn-primary">검색</button>
|
||||
<a href="<?php echo $_SERVER['PHP_SELF']; ?>" class="btn btn-secondary">초기화</a>
|
||||
<a href="dashboard.php" class="btn btn-secondary">대시보드로</a>
|
||||
</form>
|
||||
|
||||
<!-- 예약 목록 -->
|
||||
<div class="reservations-table">
|
||||
<div class="table-header">
|
||||
<span>예약 목록 (총 <?php echo number_format($total); ?>건)</span>
|
||||
<span>페이지 <?php echo $page; ?> / <?php echo $total_pages; ?></span>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($reservations)): ?>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>예약번호</th>
|
||||
<th>고객정보</th>
|
||||
<th>예약일시</th>
|
||||
<th>상담유형</th>
|
||||
<th>상담비</th>
|
||||
<th>상태</th>
|
||||
<th>신청일</th>
|
||||
<th>관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($reservations as $reservation): ?>
|
||||
<tr>
|
||||
<td>#<?php echo $reservation['id']; ?></td>
|
||||
<td>
|
||||
<strong><?php echo htmlspecialchars($reservation['customer_name']); ?></strong><br>
|
||||
<small><?php echo htmlspecialchars($reservation['customer_phone']); ?></small><br>
|
||||
<small><?php echo htmlspecialchars($reservation['customer_email']); ?></small>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo $reservation['reservation_date']; ?><br>
|
||||
<small><?php echo substr($reservation['reservation_time'], 0, 5); ?></small>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
$types = ['onsite' => '현장', 'online' => '온라인', 'phone' => '전화'];
|
||||
echo $types[$reservation['consultation_type']] ?? $reservation['consultation_type'];
|
||||
?>
|
||||
</td>
|
||||
<td><?php echo number_format($reservation['payment_amount']); ?>원</td>
|
||||
<td>
|
||||
<span class="status-badge status-<?php echo $reservation['status']; ?>">
|
||||
<?php echo $status_labels[$reservation['status']] ?? $reservation['status']; ?>
|
||||
</span>
|
||||
</td>
|
||||
<td><?php echo date('m-d H:i', strtotime($reservation['created_at'])); ?></td>
|
||||
<td>
|
||||
<button onclick="openStatusModal(<?php echo $reservation['id']; ?>, '<?php echo $reservation['status']; ?>')" class="btn btn-warning btn-sm">상태변경</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="no-data">
|
||||
검색 조건에 맞는 예약이 없습니다.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- 페이징 -->
|
||||
<?php if ($total_pages > 1): ?>
|
||||
<div class="pagination">
|
||||
<?php if ($page > 1): ?>
|
||||
<a href="?<?php echo http_build_query(array_merge($_GET, ['page' => $page - 1])); ?>">이전</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$start_page = max(1, $page - 5);
|
||||
$end_page = min($total_pages, $page + 5);
|
||||
|
||||
for ($i = $start_page; $i <= $end_page; $i++):
|
||||
?>
|
||||
<?php if ($i == $page): ?>
|
||||
<span class="current"><?php echo $i; ?></span>
|
||||
<?php else: ?>
|
||||
<a href="?<?php echo http_build_query(array_merge($_GET, ['page' => $i])); ?>"><?php echo $i; ?></a>
|
||||
<?php endif; ?>
|
||||
<?php endfor; ?>
|
||||
|
||||
<?php if ($page < $total_pages): ?>
|
||||
<a href="?<?php echo http_build_query(array_merge($_GET, ['page' => $page + 1])); ?>">다음</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- 상태 변경 모달 -->
|
||||
<div id="statusModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h3>예약 상태 변경</h3>
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="update_status">
|
||||
<input type="hidden" name="reservation_id" id="modal_reservation_id">
|
||||
|
||||
<div style="margin: 15px 0;">
|
||||
<label for="modal_new_status">변경할 상태</label>
|
||||
<select name="new_status" id="modal_new_status" class="form-control" required>
|
||||
<?php foreach ($status_labels as $key => $label): ?>
|
||||
<option value="<?php echo $key; ?>"><?php echo $label; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div style="margin: 15px 0;">
|
||||
<label for="modal_memo">관리자 메모 (선택)</label>
|
||||
<textarea name="memo" rows="3" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 💡 [추가] 알림 발송 옵션 -->
|
||||
<div style="margin: 15px 0; background: #f8f9fa; padding: 10px; border-radius: 4px;">
|
||||
<div style="font-weight: bold; margin-bottom: 5px;">알림 발송</div>
|
||||
<label style="margin-right: 15px;">
|
||||
<input type="checkbox" name="send_sms" value="1" checked> 문자(SMS) 발송
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" name="send_email" value="1" checked> 이메일 발송
|
||||
</label>
|
||||
<div style="font-size: 12px; color: #666; margin-top: 5px;">
|
||||
* 예약확정/취소 상태 변경 시에만 발송됩니다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-top: 20px;">
|
||||
<button type="submit" class="btn btn-primary">확인</button>
|
||||
<button type="button" onclick="closeModal()" class="btn btn-secondary">취소</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openStatusModal(reservationId, currentStatus) {
|
||||
document.getElementById('modal_reservation_id').value = reservationId;
|
||||
document.getElementById('modal_new_status').value = currentStatus;
|
||||
document.getElementById('statusModal').style.display = 'block';
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('statusModal').style.display = 'none';
|
||||
}
|
||||
|
||||
// 모달 외부 클릭시 닫기
|
||||
window.onclick = function(event) {
|
||||
const modal = document.getElementById('statusModal');
|
||||
if (event.target == modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 닫기 버튼
|
||||
document.querySelector('.close').onclick = function() {
|
||||
closeModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 리소스(상담사/공간) 관리
|
||||
*/
|
||||
$sub_menu = '850615';
|
||||
include_once('./_common.php');
|
||||
|
||||
// 관리자 권한 확인
|
||||
if (!$is_admin) {
|
||||
alert('관리자만 접근할 수 있습니다.');
|
||||
}
|
||||
|
||||
$g5['title'] = '리소스(상담사) 관리';
|
||||
|
||||
// --- 액션 처리 ---
|
||||
|
||||
// 그룹 추가/수정
|
||||
if ($_POST['action'] == 'save_group') {
|
||||
$group_name = trim($_POST['group_name']);
|
||||
$group_id = (int)$_POST['group_id'];
|
||||
|
||||
if ($group_name) {
|
||||
if ($group_id > 0) {
|
||||
$sql = "UPDATE consultant_groups SET name = '{$group_name}' WHERE id = {$group_id}";
|
||||
} else {
|
||||
$sql = "INSERT INTO consultant_groups (name, is_active) VALUES ('{$group_name}', 1)";
|
||||
}
|
||||
sql_query($sql);
|
||||
alert('저장되었습니다.', $_SERVER['PHP_SELF']);
|
||||
}
|
||||
}
|
||||
|
||||
// 그룹 삭제
|
||||
if ($_POST['action'] == 'delete_group') {
|
||||
$group_id = (int)$_POST['group_id'];
|
||||
// 리소스가 있는지 확인
|
||||
$cnt = sql_fetch("SELECT COUNT(*) as cnt FROM consultant_resources WHERE group_id = {$group_id}")['cnt'];
|
||||
if ($cnt > 0) {
|
||||
alert('해당 그룹에 속한 리소스가 있어 삭제할 수 없습니다.');
|
||||
} else {
|
||||
sql_query("DELETE FROM consultant_groups WHERE id = {$group_id}");
|
||||
alert('삭제되었습니다.', $_SERVER['PHP_SELF']);
|
||||
}
|
||||
}
|
||||
|
||||
// 리소스 추가/수정
|
||||
if ($_POST['action'] == 'save_resource') {
|
||||
$resource_id = (int)$_POST['resource_id'];
|
||||
$group_id = (int)$_POST['group_id'];
|
||||
$name = trim($_POST['resource_name']);
|
||||
$desc = trim($_POST['resource_desc']);
|
||||
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
||||
|
||||
if ($name && $group_id) {
|
||||
if ($resource_id > 0) {
|
||||
$sql = "UPDATE consultant_resources
|
||||
SET group_id = {$group_id}, name = '{$name}', description = '{$desc}', is_active = {$is_active}
|
||||
WHERE id = {$resource_id}";
|
||||
} else {
|
||||
$sql = "INSERT INTO consultant_resources
|
||||
(group_id, name, description, is_active)
|
||||
VALUES ({$group_id}, '{$name}', '{$desc}', {$is_active})";
|
||||
}
|
||||
sql_query($sql);
|
||||
alert('저장되었습니다.', $_SERVER['PHP_SELF']);
|
||||
}
|
||||
}
|
||||
|
||||
// 리소스 삭제
|
||||
if ($_POST['action'] == 'delete_resource') {
|
||||
$resource_id = (int)$_POST['resource_id'];
|
||||
// 예약 내역 확인 (안전 삭제)
|
||||
$cnt = sql_fetch("SELECT COUNT(*) as cnt FROM consultant_reservations WHERE resource_id = {$resource_id}")['cnt'];
|
||||
if ($cnt > 0) {
|
||||
// 실제 삭제 대신 비활성화 처리 권장하지만, 여기서는 경고 후 삭제 방지
|
||||
alert('해당 리소스로 접수된 예약이 있어 삭제할 수 없습니다. 대신 사용 안 함으로 설정해주세요.');
|
||||
} else {
|
||||
sql_query("DELETE FROM consultant_resources WHERE id = {$resource_id}");
|
||||
alert('삭제되었습니다.', $_SERVER['PHP_SELF']);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 데이터 조회 ---
|
||||
$groups = [];
|
||||
$result = sql_query("SELECT * FROM consultant_groups ORDER BY id");
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$groups[$row['id']] = $row;
|
||||
}
|
||||
|
||||
$resources = [];
|
||||
$result = sql_query("SELECT r.*, g.name as group_name
|
||||
FROM consultant_resources r
|
||||
LEFT JOIN consultant_groups g ON r.group_id = g.id
|
||||
ORDER BY r.group_id, r.id");
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$resources[] = $row;
|
||||
}
|
||||
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
?>
|
||||
|
||||
<style>
|
||||
.resource-container { max-width: 1200px; margin: 0 auto; padding: 20px; display: grid; grid-template-columns: 300px 1fr; gap: 20px; }
|
||||
.card { background: #fff; border: 1px solid #ddd; border-radius: 8px; overflow: hidden; }
|
||||
.card-header { padding: 15px; background: #f8f9fa; border-bottom: 1px solid #ddd; font-weight: bold; display: flex; justify-content: space-between; align-items: center; }
|
||||
.card-body { padding: 20px; }
|
||||
|
||||
.group-list { list-style: none; padding: 0; margin: 0; }
|
||||
.group-item { padding: 10px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; }
|
||||
.group-item:last-child { border-bottom: none; }
|
||||
.group-item:hover { background: #f1f3f5; }
|
||||
|
||||
.resource-table { width: 100%; border-collapse: collapse; }
|
||||
.resource-table th, .resource-table td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
|
||||
.resource-table th { background: #f8f9fa; font-weight: bold; }
|
||||
|
||||
.btn-xs { padding: 2px 6px; font-size: 11px; border-radius: 3px; }
|
||||
.badge { padding: 3px 8px; border-radius: 10px; font-size: 11px; color: #fff; }
|
||||
.badge-success { background: #28a745; }
|
||||
.badge-secondary { background: #6c757d; }
|
||||
|
||||
.form-control { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.resource-container { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="resource-container">
|
||||
<!-- 좌측: 그룹 관리 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span>그룹 관리</span>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="openGroupModal()">+ 추가</button>
|
||||
</div>
|
||||
<div class="card-body" style="padding:0;">
|
||||
<ul class="group-list">
|
||||
<?php if (empty($groups)): ?>
|
||||
<li class="group-item" style="justify-content:center; color:#999;">등록된 그룹이 없습니다.</li>
|
||||
<?php else: ?>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<li class="group-item">
|
||||
<span><?php echo htmlspecialchars($group['name']); ?></span>
|
||||
<div>
|
||||
<button type="button" class="btn btn-secondary btn-xs" onclick="openGroupModal(<?php echo $group['id']; ?>, '<?php echo htmlspecialchars($group['name']); ?>')">수정</button>
|
||||
<button type="button" class="btn btn-danger btn-xs" onclick="deleteGroup(<?php echo $group['id']; ?>)">삭제</button>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 우측: 리소스 목록 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span>리소스(상담사/공간) 목록</span>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="openResourceModal()">+ 리소스 추가</button>
|
||||
</div>
|
||||
<div class="card-body" style="padding:0;">
|
||||
<table class="resource-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>그룹</th>
|
||||
<th>이름</th>
|
||||
<th>설명</th>
|
||||
<th>상태</th>
|
||||
<th>관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($resources)): ?>
|
||||
<tr><td colspan="5" style="text-align:center; padding:30px; color:#999;">등록된 리소스가 없습니다.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($resources as $res): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($res['group_name']); ?></td>
|
||||
<td><strong><?php echo htmlspecialchars($res['name']); ?></strong></td>
|
||||
<td><?php echo htmlspecialchars($res['description']); ?></td>
|
||||
<td>
|
||||
<?php if ($res['is_active']): ?>
|
||||
<span class="badge badge-success">사용중</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge-secondary">중지</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-secondary btn-xs"
|
||||
onclick="openResourceModal(<?php echo $res['id']; ?>, <?php echo $res['group_id']; ?>, '<?php echo htmlspecialchars($res['name']); ?>', '<?php echo htmlspecialchars($res['description']); ?>', <?php echo $res['is_active']; ?>)">
|
||||
수정
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger btn-xs" onclick="deleteResource(<?php echo $res['id']; ?>)">삭제</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 그룹 추가/수정 모달 -->
|
||||
<div id="groupModal" class="modal" style="display:none;">
|
||||
<div class="modal-content" style="width:350px;">
|
||||
<div class="modal-header">
|
||||
<h3 id="groupModalTitle">그룹 추가</h3>
|
||||
<span class="close" onclick="closeModal('groupModal')">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="save_group">
|
||||
<input type="hidden" name="group_id" id="modal_group_id" value="0">
|
||||
<div class="form-group">
|
||||
<label>그룹명 (예: 내과, 회의실)</label>
|
||||
<input type="text" name="group_name" id="modal_group_name" class="form-control" required>
|
||||
</div>
|
||||
<div style="text-align:right; margin-top:15px;">
|
||||
<button type="submit" class="btn btn-primary">저장</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 리소스 추가/수정 모달 -->
|
||||
<div id="resourceModal" class="modal" style="display:none;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 id="resourceModalTitle">리소스 추가</h3>
|
||||
<span class="close" onclick="closeModal('resourceModal')">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="save_resource">
|
||||
<input type="hidden" name="resource_id" id="modal_res_id" value="0">
|
||||
|
||||
<div class="form-group">
|
||||
<label>그룹 선택</label>
|
||||
<select name="group_id" id="modal_res_group" class="form-control" required>
|
||||
<option value="">선택하세요</option>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<option value="<?php echo $group['id']; ?>"><?php echo htmlspecialchars($group['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>이름 (예: 김의사, A회의실)</label>
|
||||
<input type="text" name="resource_name" id="modal_res_name" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>설명 (선택)</label>
|
||||
<input type="text" name="resource_desc" id="modal_res_desc" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label><input type="checkbox" name="is_active" id="modal_res_active" value="1" checked> 사용함</label>
|
||||
</div>
|
||||
|
||||
<div style="text-align:right; margin-top:15px;">
|
||||
<button type="submit" class="btn btn-primary">저장</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 삭제 폼 (히든) -->
|
||||
<form id="deleteForm" method="post">
|
||||
<input type="hidden" name="action" id="deleteAction">
|
||||
<input type="hidden" name="group_id" id="deleteGroupId">
|
||||
<input type="hidden" name="resource_id" id="deleteResourceId">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function openGroupModal(id = 0, name = '') {
|
||||
document.getElementById('groupModalTitle').textContent = id ? '그룹 수정' : '그룹 추가';
|
||||
document.getElementById('modal_group_id').value = id;
|
||||
document.getElementById('modal_group_name').value = name;
|
||||
document.getElementById('groupModal').style.display = 'block';
|
||||
}
|
||||
|
||||
function openResourceModal(id = 0, groupId = '', name = '', desc = '', active = 1) {
|
||||
document.getElementById('resourceModalTitle').textContent = id ? '리소스 수정' : '리소스 추가';
|
||||
document.getElementById('modal_res_id').value = id;
|
||||
document.getElementById('modal_res_group').value = groupId;
|
||||
document.getElementById('modal_res_name').value = name;
|
||||
document.getElementById('modal_res_desc').value = desc;
|
||||
document.getElementById('modal_res_active').checked = (active == 1);
|
||||
document.getElementById('resourceModal').style.display = 'block';
|
||||
}
|
||||
|
||||
function closeModal(id) {
|
||||
document.getElementById(id).style.display = 'none';
|
||||
}
|
||||
|
||||
function deleteGroup(id) {
|
||||
if(confirm('정말 삭제하시겠습니까?')) {
|
||||
document.getElementById('deleteAction').value = 'delete_group';
|
||||
document.getElementById('deleteGroupId').value = id;
|
||||
document.getElementById('deleteForm').submit();
|
||||
}
|
||||
}
|
||||
|
||||
function deleteResource(id) {
|
||||
if(confirm('정말 삭제하시겠습니까?')) {
|
||||
document.getElementById('deleteAction').value = 'delete_resource';
|
||||
document.getElementById('deleteResourceId').value = id;
|
||||
document.getElementById('deleteForm').submit();
|
||||
}
|
||||
}
|
||||
|
||||
// 모달 외부 클릭 닫기
|
||||
window.onclick = function(event) {
|
||||
if (event.target.classList.contains('modal')) {
|
||||
event.target.style.display = 'none';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
$sub_menu = '850500'; // 💡 메뉴 코드 수정
|
||||
include_once('./_common.php');
|
||||
|
||||
$g5['title'] = '상담 예약 팝업 샘플';
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
?>
|
||||
|
||||
<div class="local_desc01 local_desc">
|
||||
<p>
|
||||
이 페이지는 사이트의 어떤 페이지에서든 상담 예약 기능을 쉽게 추가하는 방법을 보여주는 예제입니다.<br>
|
||||
아래 코드 한 줄만 포함하면, 버튼과 팝업 기능이 모두 활성화됩니다.
|
||||
</p>
|
||||
<pre><code><?php include_once(G5_ADMIN_PATH . '/consultant_manage/components/_consultant_popups.php'); ?></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- 💡 [시작] 상담 예약 기능 추가 예제 -->
|
||||
<!-- ================================================================== -->
|
||||
|
||||
<div style="text-align:center; padding: 50px 20px; background-color:#f5f5f5; border-radius:10px; margin: 20px 0;">
|
||||
<h3 style="margin-bottom:15px;">상담이 필요하신가요?</h3>
|
||||
<p style="margin-bottom:25px; color:#666;">버튼을 눌러 간편하게 상담을 신청하거나, 기존 예약을 확인/취소할 수 있습니다.</p>
|
||||
|
||||
<!-- 1. 팝업을 여는 버튼들 -->
|
||||
<div class="consultant-buttons">
|
||||
<button type="button" class="reservation-btn-main" onclick="openReservationPopup()">
|
||||
<i class="fa fa-calendar"></i> 상담 예약 신청
|
||||
</button>
|
||||
<button type="button" class="reservation-check-btn" onclick="openReservationCheckPopup()">
|
||||
예약 확인/취소
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// 2. 팝업 파일들 포함 (이 코드 한 줄이면 모든 팝업 기능이 로드됩니다)
|
||||
include_once(G5_ADMIN_PATH . '/consultant_manage/components/_consultant_popups.php');
|
||||
?>
|
||||
|
||||
<!-- 버튼 디자인을 위한 CSS (사이트의 공통 CSS 파일에 추가하는 것을 권장합니다) -->
|
||||
<style>
|
||||
.consultant-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.reservation-btn-main, .reservation-check-btn {
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 30px;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.reservation-btn-main {
|
||||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
||||
box-shadow: 0 4px 15px rgba(40, 167, 69, 0.3);
|
||||
}
|
||||
.reservation-btn-main:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(40, 167, 69, 0.4);
|
||||
}
|
||||
.reservation-btn-main i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.reservation-check-btn {
|
||||
background: #6c757d;
|
||||
}
|
||||
.reservation-check-btn:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- 💡 [끝] 상담 예약 기능 추가 예제 -->
|
||||
<!-- ================================================================== -->
|
||||
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 일정 설정
|
||||
*/
|
||||
$sub_menu = '850300';
|
||||
include_once('./_common.php');
|
||||
|
||||
// 관리자 권한 확인
|
||||
if (!$is_admin) {
|
||||
alert('관리자만 접근할 수 있습니다.');
|
||||
}
|
||||
|
||||
// 설치 확인
|
||||
if (!is_consultant_installed()) {
|
||||
alert('상담 예약 시스템이 설치되지 않았습니다.', 'install.php');
|
||||
}
|
||||
|
||||
$g5['title'] = '상담 일정 설정';
|
||||
|
||||
// 월별 스케줄 생성 처리
|
||||
if ($_POST['action'] == 'generate_monthly_schedule') {
|
||||
try {
|
||||
require_once('classes/ScheduleGenerator.class.php');
|
||||
$generator = new ScheduleGenerator();
|
||||
|
||||
$year = (int)($_POST['year'] ?? date('Y'));
|
||||
$month = (int)($_POST['month'] ?? date('n'));
|
||||
|
||||
// 충돌 검사
|
||||
$conflicts = $generator->checkScheduleConflicts($year, $month);
|
||||
if (!empty($conflicts)) {
|
||||
$conflictMsg = "다음 예약과 충돌이 발생합니다:\\n";
|
||||
foreach ($conflicts as $conflict) {
|
||||
$conflictMsg .= "- {$conflict['date']} {$conflict['time']} {$conflict['customer']} ({$conflict['phone']})\\n";
|
||||
}
|
||||
$conflictMsg .= "\\n계속 진행하시겠습니까?";
|
||||
|
||||
if (!isset($_POST['force_generate'])) {
|
||||
echo "<script>
|
||||
if (confirm('{$conflictMsg}')) {
|
||||
var form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.innerHTML = '<input type=\"hidden\" name=\"action\" value=\"generate_monthly_schedule\">' +
|
||||
'<input type=\"hidden\" name=\"year\" value=\"{$year}\">' +
|
||||
'<input type=\"hidden\" name=\"month\" value=\"{$month}\">' +
|
||||
'<input type=\"hidden\" name=\"force_generate\" value=\"1\">';
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
</script>";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// 스케줄 생성
|
||||
if ($generator->generateMonth($year, $month)) {
|
||||
alert("{$year}년 {$month}월 스케줄이 생성되었습니다.", $_SERVER['PHP_SELF']);
|
||||
} else {
|
||||
alert("스케줄 생성에 실패했습니다.");
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
alert("오류가 발생했습니다: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 폼 처리 (기존 코드 유지)
|
||||
if ($_POST['action'] == 'save_schedule') {
|
||||
// 기존 스케줄 삭제
|
||||
sql_query("DELETE FROM consultant_schedule WHERE day_of_week IS NOT NULL");
|
||||
|
||||
// 새 스케줄 저장
|
||||
for ($day = 1; $day <= 7; $day++) {
|
||||
$enabled = $_POST["day_{$day}_enabled"] ?? 0;
|
||||
$start_time = $_POST["day_{$day}_start"] ?? '09:00';
|
||||
$end_time = $_POST["day_{$day}_end"] ?? '18:00';
|
||||
|
||||
if ($enabled) {
|
||||
$sql = "INSERT INTO consultant_schedule
|
||||
(day_of_week, start_time, end_time, time_slot, max_persons, is_available)
|
||||
VALUES ({$day}, '{$start_time}', '{$end_time}', 60, 2, 1)";
|
||||
sql_query($sql);
|
||||
}
|
||||
}
|
||||
|
||||
alert('일정이 저장되었습니다.', $_SERVER['PHP_SELF']);
|
||||
}
|
||||
|
||||
// 현재 스케줄 조회
|
||||
$schedules = [];
|
||||
$sql = "SELECT * FROM consultant_schedule WHERE day_of_week IS NOT NULL ORDER BY day_of_week";
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$schedules[$row['day_of_week']] = $row;
|
||||
}
|
||||
|
||||
$days = ['', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일', '일요일'];
|
||||
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
?>
|
||||
|
||||
<style>
|
||||
.schedule-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.schedule-tabs {
|
||||
display: flex;
|
||||
border-bottom: 2px solid #ddd;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
padding: 16px 28px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
border-bottom: 3px solid transparent;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
color: #007bff;
|
||||
border-bottom-color: #007bff;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.monthly-generator {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.generator-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
color: #0c5460;
|
||||
background-color: #d1ecf1;
|
||||
border-color: #bee5eb;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
color: #856404;
|
||||
background-color: #fff3cd;
|
||||
border-color: #ffeaa7;
|
||||
}
|
||||
.schedule-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.schedule-form {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
}
|
||||
.day-schedule {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.day-schedule:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.day-name {
|
||||
width: 100px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.day-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
flex: 1;
|
||||
}
|
||||
.time-input {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width: 100px;
|
||||
}
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
.btn-primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
.alert {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.alert-info {
|
||||
color: #0c5460;
|
||||
background-color: #d1ecf1;
|
||||
border-color: #bee5eb;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="schedule-container">
|
||||
<h2><?php echo $g5['title']; ?></h2>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>안내:</strong> 상담 가능한 요일과 시간을 설정하세요. 체크된 요일만 예약이 가능합니다.
|
||||
</div>
|
||||
|
||||
<form method="post" class="schedule-form">
|
||||
<input type="hidden" name="action" value="save_schedule">
|
||||
|
||||
<?php for ($day = 1; $day <= 7; $day++): ?>
|
||||
<?php
|
||||
$schedule = $schedules[$day] ?? null;
|
||||
$enabled = $schedule ? 1 : 0;
|
||||
$start_time = $schedule['start_time'] ?? '09:00';
|
||||
$end_time = $schedule['end_time'] ?? '18:00';
|
||||
?>
|
||||
|
||||
<div class="day-schedule">
|
||||
<div class="day-name"><?php echo $days[$day]; ?></div>
|
||||
<div class="day-controls">
|
||||
<label>
|
||||
<input type="checkbox" name="day_<?php echo $day; ?>_enabled" value="1"
|
||||
<?php echo $enabled ? 'checked' : ''; ?>>
|
||||
운영
|
||||
</label>
|
||||
|
||||
<label>
|
||||
시작:
|
||||
<input type="time" name="day_<?php echo $day; ?>_start"
|
||||
value="<?php echo $start_time; ?>" class="time-input">
|
||||
</label>
|
||||
|
||||
<label>
|
||||
종료:
|
||||
<input type="time" name="day_<?php echo $day; ?>_end"
|
||||
value="<?php echo $end_time; ?>" class="time-input">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<?php endfor; ?>
|
||||
|
||||
<div style="text-align: center; margin-top: 30px;">
|
||||
<button type="submit" class="btn btn-primary">저장</button>
|
||||
<a href="dashboard.php" class="btn btn-secondary">대시보드로</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,454 @@
|
||||
<?php
|
||||
/**
|
||||
* 스케줄 생성 및 빠른 관리 페이지
|
||||
*/
|
||||
|
||||
$sub_menu = '850300';
|
||||
include_once('./_common.php');
|
||||
|
||||
// 권한 확인
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
// ScheduleGenerator 클래스 로드
|
||||
require_once('classes/ScheduleGenerator.class.php');
|
||||
|
||||
// 설치 확인
|
||||
if (!is_consultant_installed()) {
|
||||
alert('상담 예약 시스템이 설치되지 않았습니다.', 'install.php');
|
||||
}
|
||||
|
||||
$g5['title'] = '스케줄 빠른 관리';
|
||||
|
||||
// AJAX 요청 처리
|
||||
$action = $_REQUEST['action'] ?? '';
|
||||
|
||||
if ($action) {
|
||||
header('Content-Type: application/json');
|
||||
$response = ['success' => false, 'message' => '알 수 없는 요청입니다.'];
|
||||
|
||||
// 월별 스케줄 데이터 조회 (달력용)
|
||||
if ($action == 'get_monthly_schedule') {
|
||||
$year = (int) ($_GET['year'] ?? 0);
|
||||
$month = (int) ($_GET['month'] ?? 0);
|
||||
|
||||
if ($year && $month) {
|
||||
$sql = "SELECT id, specific_date, start_time, is_available, temp_1, temp_2
|
||||
FROM consultant_schedule
|
||||
WHERE YEAR(specific_date) = {$year} AND MONTH(specific_date) = {$month}
|
||||
ORDER BY specific_date, start_time";
|
||||
$result = sql_query($sql);
|
||||
$schedule_data = [];
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$schedule_data[] = $row;
|
||||
}
|
||||
$response = ['success' => true, 'data' => $schedule_data];
|
||||
} else {
|
||||
$response['message'] = '년도와 월 정보가 올바르지 않습니다.';
|
||||
}
|
||||
}
|
||||
|
||||
// 스케줄 슬롯 상태 변경 (블락/해제)
|
||||
if ($action == 'toggle_slot_status') {
|
||||
$id = (int) ($_POST['id'] ?? 0);
|
||||
if ($id) {
|
||||
$slot = sql_fetch("SELECT is_available, temp_1, temp_2 FROM consultant_schedule WHERE id = {$id}");
|
||||
if ($slot) {
|
||||
$is_lunch_override = $_POST['is_lunch_override'] ?? '0';
|
||||
$new_status = $slot['is_available'] ? 0 : 1; // 기본 토글 동작
|
||||
$new_temp1 = $slot['temp_1'];
|
||||
$new_temp2 = $slot['temp_2'];
|
||||
$log_msg_action = '';
|
||||
|
||||
// 💡 [로직 개선] 휴게시간을 상담시간으로 변경하는 경우
|
||||
if ($is_lunch_override === '1' && $slot['temp_1'] === 'lunch_time' && $new_status == 1) {
|
||||
$new_temp1 = 'manual_override'; // 휴게시간을 수동으로 변경했음을 명시
|
||||
$new_temp2 = '관리자 긴급 설정';
|
||||
$log_msg_action = "휴게시간을 상담 슬롯으로 변경";
|
||||
}
|
||||
// 💡 [로직 개선] 긴급 설정된 상담시간을 다시 휴게시간으로 되돌리는 경우
|
||||
else if ($slot['temp_1'] === 'manual_override' && $new_status == 0) {
|
||||
$new_temp1 = 'lunch_time';
|
||||
$new_temp2 = '점심시간';
|
||||
$log_msg_action = "긴급 슬롯을 다시 휴게시간으로 복원";
|
||||
}
|
||||
// 일반 슬롯을 블락/해제하는 경우
|
||||
else {
|
||||
$new_temp1 = $new_status ? 'auto_generated' : 'manual_block';
|
||||
$new_temp2 = $new_status ? '' : '관리자 설정';
|
||||
$log_msg_action = $new_status ? "슬롯 활성화" : "슬롯 비활성화";
|
||||
}
|
||||
|
||||
$sql = "UPDATE consultant_schedule
|
||||
SET is_available = '{$new_status}',
|
||||
temp_1 = '{$new_temp1}',
|
||||
temp_2 = '{$new_temp2}',
|
||||
updated_at = NOW()
|
||||
WHERE id = {$id}";
|
||||
|
||||
if (sql_query($sql)) {
|
||||
consultant_log("스케줄 수동 변경 (ID:{$id}): {$log_msg_action} (관리자: " . ($member['mb_id'] ?? 'unknown') . ")");
|
||||
$response = ['success' => true, 'new_status' => $new_status];
|
||||
} else {
|
||||
$response['message'] = '데이터베이스 업데이트에 실패했습니다.';
|
||||
}
|
||||
} else {
|
||||
$response['message'] = '해당 스케줄을 찾을 수 없습니다.';
|
||||
}
|
||||
} else {
|
||||
$response['message'] = 'ID가 제공되지 않았습니다.';
|
||||
}
|
||||
}
|
||||
|
||||
// 월별 스케줄 생성
|
||||
if ($action == 'generate_schedule') {
|
||||
$year = (int) ($_POST['year'] ?? 0);
|
||||
$month = (int) ($_POST['month'] ?? 0);
|
||||
if ($year && $month) {
|
||||
try {
|
||||
$generator = new ScheduleGenerator();
|
||||
if ($generator->generateMonth($year, $month)) {
|
||||
consultant_log("스케줄 생성/재생성 완료: {$year}년 {$month}월");
|
||||
$response = ['success' => true, 'message' => "{$year}년 {$month}월 스케줄이 성공적으로 생성되었습니다."];
|
||||
} else {
|
||||
$response['message'] = "{$year}년 {$month}월 스케줄 생성에 실패했습니다.";
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$response['message'] = '스케줄 생성 중 오류 발생: ' . $e->getMessage();
|
||||
}
|
||||
} else {
|
||||
$response['message'] = '년도와 월 정보가 올바르지 않습니다.';
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- 페이지 로드 시 실행 ---
|
||||
|
||||
// 월별 스케줄 상태 조회 함수
|
||||
function get_schedule_generation_status($year, $month) {
|
||||
$year = (int)$year;
|
||||
$month = (int)$month;
|
||||
$sql = "SELECT
|
||||
COUNT(*) as total_slots,
|
||||
SUM(CASE WHEN temp_1 = 'auto_generated' AND is_available = 1 THEN 1 ELSE 0 END) as available_slots,
|
||||
SUM(CASE WHEN temp_1 = 'lunch_time' THEN 1 ELSE 0 END) as lunch_slots,
|
||||
SUM(CASE WHEN temp_1 = 'holiday' THEN 1 ELSE 0 END) as holiday_slots,
|
||||
SUM(CASE WHEN is_available = 0 AND temp_1 NOT IN ('lunch_time', 'holiday') THEN 1 ELSE 0 END) as blocked_slots
|
||||
FROM consultant_schedule
|
||||
WHERE YEAR(specific_date) = {$year} AND MONTH(specific_date) = {$month}";
|
||||
return sql_fetch($sql);
|
||||
}
|
||||
|
||||
// 다음 3개월 상태 조회
|
||||
$next_months = [];
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$target_date = mktime(0, 0, 0, date('n') + $i, 1, date('Y'));
|
||||
$year = date('Y', $target_date);
|
||||
$month = date('m', $target_date);
|
||||
|
||||
$next_months[] = [
|
||||
'year' => $year,
|
||||
'month' => $month,
|
||||
'name' => date('Y년 n월', $target_date),
|
||||
'status' => get_schedule_generation_status($year, $month)
|
||||
];
|
||||
}
|
||||
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
?>
|
||||
|
||||
<style>
|
||||
.schedule-container { max-width: 1000px; margin: 0 auto; padding: 20px; }
|
||||
.schedule-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
|
||||
.status-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 30px; }
|
||||
.status-card { background: white; border: 1px solid #ddd; border-radius: 8px; padding: 20px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); }
|
||||
.status-card h3 { margin: 0 0 15px 0; color: #333; display: flex; align-items: center; gap: 8px; }
|
||||
.status-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #f0f0f0; font-size: 14px; }
|
||||
.status-item:last-child { border-bottom: none; }
|
||||
.status-label { color: #666; }
|
||||
.status-value { font-weight: bold; color: #333; }
|
||||
.btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-weight: 600; text-decoration: none; display: inline-block; transition: all 0.2s; font-size: 14px; }
|
||||
.btn-primary { background: #007bff; color: white; }
|
||||
.btn-success { background: #28a745; color: white; }
|
||||
.btn-warning { background: #ffc107; color: #212529; }
|
||||
.btn-secondary { background: #6c757d; color: white; }
|
||||
.btn-info { background: #17a2b8; color: white; }
|
||||
.alert-info { color: #0c5460; background-color: #d1ecf1; border-color: #bee5eb; padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; }
|
||||
.card-actions { display: flex; gap: 10px; margin-top: 20px; }
|
||||
.card-actions .btn { flex-grow: 1; }
|
||||
|
||||
/* Modal Styles */
|
||||
.modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.6); }
|
||||
.modal-content { background-color: #fefefe; margin: 5% auto; padding: 20px; border: 1px solid #888; width: 90%; max-width: 1200px; border-radius: 8px; }
|
||||
.modal-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 10px; border-bottom: 1px solid #ddd; }
|
||||
.modal-header h2 { margin: 0; font-size: 20px; }
|
||||
.close-button { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
|
||||
.close-button:hover, .close-button:focus { color: black; }
|
||||
.calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; margin-top: 20px; }
|
||||
.calendar-day { border: 1px solid #eee; min-height: 150px; }
|
||||
.calendar-day-header { background: #f9f9f9; padding: 5px; font-weight: bold; text-align: center; font-size: 14px; }
|
||||
.calendar-day-body { padding: 5px; max-height: 300px; overflow-y: auto; }
|
||||
.day-name-header { background: #f1f1f1; text-align: center; padding: 8px; font-weight: bold; }
|
||||
.time-slot { padding: 4px 6px; margin: 3px 0; border-radius: 4px; cursor: pointer; font-size: 12px; display: flex; justify-content: space-between; align-items: center; border: 1px solid transparent; }
|
||||
.time-slot.available { background-color: #e7f3ff; border-color: #b3d7ff; color: #004085; }
|
||||
.time-slot.available:hover { background-color: #cce5ff; }
|
||||
.time-slot.blocked { background-color: #f8d7da; border-color: #f5c6cb; color: #721c24; text-decoration: line-through; }
|
||||
.time-slot.blocked:hover { background-color: #f5c6cb; }
|
||||
.time-slot.lunch { background-color: #fff3cd; border-color: #ffeeba; color: #856404; cursor: pointer; }
|
||||
.time-slot.lunch:hover { background-color: #ffeeba; }
|
||||
.time-slot.holiday { background-color: #e2e3e5; color: #383d41; text-align: center; justify-content: center; cursor: not-allowed; }
|
||||
.other-month { background-color: #fafafa; }
|
||||
.spinner { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 30px; height: 30px; animation: spin 1s linear infinite; margin: 20px auto; }
|
||||
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||||
</style>
|
||||
|
||||
<div class="schedule-container">
|
||||
<div class="schedule-header">
|
||||
<h2><?php echo $g5['title']; ?></h2>
|
||||
<div>
|
||||
<a href="settings.php" class="btn btn-secondary">⚙️ 설정 관리</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>스케줄 관리 안내:</strong> 월별 스케줄 생성 상태를 확인하고, '달력 보기'를 통해 각 시간 슬롯을 빠르게 예약 마감 처리할 수 있습니다.
|
||||
</div>
|
||||
|
||||
<!-- 월별 상태 표시 -->
|
||||
<div class="status-grid">
|
||||
<?php foreach ($next_months as $month_info):
|
||||
$is_generated = ($month_info['status']['total_slots'] ?? 0) > 0;
|
||||
?>
|
||||
<div class="status-card">
|
||||
<h3>
|
||||
📅 <?php echo $month_info['name']; ?>
|
||||
<?php if ($is_generated): ?>
|
||||
<span style="color: #28a745; font-weight: bold;">✓ 생성됨</span>
|
||||
<?php else: ?>
|
||||
<span style="color: #dc3545; font-weight: bold;">✗ 미생성</span>
|
||||
<?php endif; ?>
|
||||
</h3>
|
||||
|
||||
<div class="status-item">
|
||||
<span class="status-label">상담 가능 슬롯</span>
|
||||
<span class="status-value"><?php echo number_format($month_info['status']['available_slots'] ?? 0); ?>개</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">예약 마감 슬롯</span>
|
||||
<span class="status-value"><?php echo number_format($month_info['status']['blocked_slots'] ?? 0); ?>개</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">전체 슬롯 (휴무/점심 제외)</span>
|
||||
<span class="status-value"><?php echo number_format(($month_info['status']['available_slots'] ?? 0) + ($month_info['status']['blocked_slots'] ?? 0)); ?>개</span>
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<button onclick="generateSchedule(<?php echo $month_info['year']; ?>, <?php echo $month_info['month']; ?>,this)" class="btn btn-success">
|
||||
<?php echo $is_generated ? '🔄 스케줄 재생성' : '✨ 스케줄 생성'; ?>
|
||||
</button>
|
||||
<?php if ($is_generated): ?>
|
||||
<button onclick="openCalendarModal(<?php echo $month_info['year']; ?>, <?php echo $month_info['month']; ?>)" class="btn btn-info">
|
||||
달력 보기
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- The Modal -->
|
||||
<div id="calendarModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 id="modalTitle"></h2>
|
||||
<span class="close-button">×</span>
|
||||
</div>
|
||||
<div id="modalBody">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// --- Modal Control ---
|
||||
const modal = document.getElementById("calendarModal");
|
||||
const closeButton = document.querySelector(".close-button");
|
||||
if(closeButton) closeButton.onclick = () => modal.style.display = "none";
|
||||
window.onclick = (event) => {
|
||||
if (event.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
};
|
||||
|
||||
async function openCalendarModal(year, month) {
|
||||
document.getElementById("modalTitle").innerText = `${year}년 ${month}월 스케줄`;
|
||||
const modalBody = document.getElementById("modalBody");
|
||||
modalBody.innerHTML = '<div class="spinner"></div>';
|
||||
modal.style.display = "block";
|
||||
|
||||
try {
|
||||
const response = await fetch(`?action=get_monthly_schedule&year=${year}&month=${month}`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
renderCalendar(year, month, result.data);
|
||||
} else {
|
||||
modalBody.innerHTML = `<p style="color: red;">스케줄을 불러오는 데 실패했습니다: ${result.message}</p>`;
|
||||
}
|
||||
} catch (error) {
|
||||
modalBody.innerHTML = `<p style="color: red;">오류가 발생했습니다: ${error}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderCalendar(year, month, scheduleData) {
|
||||
const modalBody = document.getElementById("modalBody");
|
||||
const firstDay = new Date(year, month - 1, 1).getDay(); // 0=일, 1=월, ...
|
||||
const daysInMonth = new Date(year, month, 0).getDate();
|
||||
const dayNames = ['일', '월', '화', '수', '목', '금', '토'];
|
||||
|
||||
let html = '<div class="calendar-grid">';
|
||||
dayNames.forEach(name => html += `<div class="day-name-header">${name}</div>`);
|
||||
|
||||
// Group data by date
|
||||
const scheduleByDate = scheduleData.reduce((acc, slot) => {
|
||||
const day = new Date(slot.specific_date).getDate();
|
||||
if (!acc[day]) acc[day] = [];
|
||||
acc[day].push(slot);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Pad start of month
|
||||
for (let i = 0; i < firstDay; i++) {
|
||||
html += '<div class="calendar-day other-month"></div>';
|
||||
}
|
||||
|
||||
// Render days
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
html += `<div class="calendar-day">
|
||||
<div class="calendar-day-header">${day}</div>
|
||||
<div class="calendar-day-body">`;
|
||||
|
||||
if (scheduleByDate[day]) {
|
||||
scheduleByDate[day].forEach(slot => {
|
||||
let slotClass = '';
|
||||
let slotText = `<span>${slot.start_time.substring(0, 5)}</span>`;
|
||||
if (slot.temp_1 === 'holiday') {
|
||||
slotClass = 'holiday';
|
||||
slotText = `<strong>${slot.temp_2 || '휴무일'}</strong>`;
|
||||
} else if (slot.temp_1 === 'lunch_time' || slot.temp_1 === 'manual_override') {
|
||||
slotText = `<span>${slot.start_time.substring(0, 5)} (휴게)</span>`;
|
||||
slotClass = (slot.is_available == '1') ? 'available' : 'lunch';
|
||||
} else if (slot.is_available == '1') {
|
||||
slotClass = 'available';
|
||||
} else {
|
||||
slotClass = 'blocked';
|
||||
}
|
||||
html += `<div class="time-slot ${slotClass}" data-id="${slot.id}" onclick="toggleSlot(this, '${slot.temp_1}')">
|
||||
${slotText}
|
||||
</div>`;
|
||||
});
|
||||
}
|
||||
html += `</div></div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
modalBody.innerHTML = html;
|
||||
}
|
||||
|
||||
async function toggleSlot(element, type) {
|
||||
if (type === 'holiday') return;
|
||||
|
||||
let isLunchOverride = false;
|
||||
if (type === 'lunch_time' || type === 'manual_override') {
|
||||
const slotIsAvailable = element.classList.contains('available');
|
||||
if (!slotIsAvailable) {
|
||||
if (!confirm('휴게시간입니다. 정말 이 시간에 상담을 등록하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
isLunchOverride = true;
|
||||
}
|
||||
}
|
||||
|
||||
const id = element.dataset.id;
|
||||
const originalClasses = element.className;
|
||||
element.innerHTML = '<span>처리중...</span>';
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'toggle_slot_status');
|
||||
formData.append('id', id);
|
||||
if (isLunchOverride) formData.append('is_lunch_override', '1');
|
||||
|
||||
const response = await fetch('', { method: 'POST', body: formData });
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Re-render the slot based on the new state
|
||||
const timeText = element.innerText.split(' ')[0];
|
||||
if (type === 'lunch_time' || type === 'manual_override') {
|
||||
if (element.classList.contains('lunch')) {
|
||||
element.className = 'time-slot available';
|
||||
} else {
|
||||
element.className = 'time-slot lunch';
|
||||
}
|
||||
element.innerHTML = `<span>${timeText} (휴게)</span>`;
|
||||
} else {
|
||||
element.className = result.new_status == 1 ? 'time-slot available' : 'time-slot blocked';
|
||||
element.innerHTML = `<span>${timeText}</span>`;
|
||||
}
|
||||
} else {
|
||||
alert('상태 변경 실패: ' + result.message);
|
||||
element.className = originalClasses;
|
||||
element.innerHTML = `<span>${element.innerText.split(' ')[0]}</span>`;
|
||||
}
|
||||
} catch (error) {
|
||||
alert('오류 발생: ' + error);
|
||||
element.className = originalClasses;
|
||||
element.innerHTML = `<span>${element.innerText.split(' ')[0]}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 💡 [추가] 스케줄 생성/재생성 함수
|
||||
async function generateSchedule(year, month,btn) {
|
||||
// const btn = document.querySelector('.status-card button[onclick="generateSchedule("+year+","+month")"]');
|
||||
const actionText = btn.textContent.trim().includes('재생성') ? '재생성' : '생성';
|
||||
|
||||
if (!confirm(`${year}년 ${month}월 스케줄을 ${actionText}하시겠습니까?\n\n기존에 수동으로 변경한 슬롯이 있다면 초기화될 수 있습니다.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> 생성 중...';
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'generate_schedule');
|
||||
formData.append('year', year);
|
||||
formData.append('month', month);
|
||||
|
||||
// 현재 페이지 URL('')로 AJAX 요청을 보냅니다.
|
||||
const response = await fetch('', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
alert(result.message);
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
alert('스케줄 생성 중 오류가 발생했습니다: ' + error);
|
||||
} finally {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
/**
|
||||
* 월별 스케줄 자동 생성 엔진
|
||||
*/
|
||||
|
||||
if (!defined('_GNUBOARD_'))
|
||||
exit;
|
||||
|
||||
class ScheduleGenerator
|
||||
{
|
||||
|
||||
/**
|
||||
* 특정 월의 전체 스케줄 생성
|
||||
*/
|
||||
public function generateMonth($year, $month)
|
||||
{
|
||||
try {
|
||||
// 기본 설정 조회
|
||||
$basic_settings = $this->getBasicSettings();
|
||||
|
||||
// 요일별 설정 조회
|
||||
$weekly_settings = $this->getWeeklySettings();
|
||||
|
||||
// 해당 월의 모든 날짜 생성
|
||||
$dates = $this->getMonthDates($year, $month);
|
||||
|
||||
// 기존 스케줄 삭제 (자동 생성된 것만)
|
||||
$this->clearAutoGeneratedSchedule($year, $month);
|
||||
|
||||
$generated_count = 0;
|
||||
|
||||
foreach ($dates as $date) {
|
||||
$day_of_week = date('w', strtotime($date)); // 0=일요일, 1=월요일, ...
|
||||
$day_name = $this->getDayName($day_of_week);
|
||||
|
||||
// 해당 요일의 설정 확인
|
||||
if (isset($weekly_settings[$day_name]) && $weekly_settings[$day_name]['enabled'] == '1') {
|
||||
// 운영일인 경우 스케줄 생성
|
||||
$slots_created = $this->generateDay($date, $weekly_settings[$day_name], $basic_settings);
|
||||
$generated_count += $slots_created;
|
||||
} else {
|
||||
// 휴무일인 경우 휴무 표시
|
||||
$this->createHolidaySlot($date, '휴무일');
|
||||
}
|
||||
}
|
||||
|
||||
consultant_log("월별 스케줄 생성 완료: {$year}-{$month}, 생성된 슬롯: {$generated_count}개");
|
||||
return $generated_count;
|
||||
|
||||
} catch (Exception $e) {
|
||||
consultant_log("월별 스케줄 생성 실패: " . $e->getMessage(), 'error');
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 날짜의 스케줄 생성
|
||||
*/
|
||||
public function generateDay($date, $day_settings, $basic_settings)
|
||||
{
|
||||
$slots_created = 0;
|
||||
|
||||
try {
|
||||
$start_time = $day_settings['start'];
|
||||
$end_time = $day_settings['end'];
|
||||
$lunch_start = $day_settings['lunch_start'];
|
||||
$lunch_end = $day_settings['lunch_end'];
|
||||
|
||||
$slot_duration = (int) $basic_settings['consultation_duration'];
|
||||
$max_persons = (int) $basic_settings['max_persons_per_slot'];
|
||||
|
||||
// 시간 슬롯 생성
|
||||
$current_time = strtotime($start_time);
|
||||
$end_timestamp = strtotime($end_time);
|
||||
|
||||
while ($current_time < $end_timestamp) {
|
||||
$slot_start = date('H:i', $current_time);
|
||||
$slot_end = date('H:i', $current_time + ($slot_duration * 60));
|
||||
|
||||
// 종료시간이 운영시간을 넘지 않도록 체크
|
||||
if (strtotime($slot_end) > $end_timestamp) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 점심시간 체크
|
||||
$is_lunch_time = $this->isLunchTime($slot_start, $slot_end, $lunch_start, $lunch_end);
|
||||
|
||||
if ($is_lunch_time) {
|
||||
// 점심시간 슬롯 생성
|
||||
$this->createTimeSlot($date, $slot_start, $slot_end, $slot_duration, 0, 0, 'lunch_time');
|
||||
} else {
|
||||
// 일반 상담 슬롯 생성
|
||||
$this->createTimeSlot($date, $slot_start, $slot_end, $slot_duration, $max_persons, 1, 'auto_generated');
|
||||
}
|
||||
|
||||
$slots_created++;
|
||||
$current_time += ($slot_duration * 60);
|
||||
}
|
||||
|
||||
return $slots_created;
|
||||
|
||||
} catch (Exception $e) {
|
||||
consultant_log("일별 스케줄 생성 실패 ({$date}): " . $e->getMessage(), 'error');
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 시간 슬롯 생성
|
||||
*/
|
||||
private function createTimeSlot($date, $start_time, $end_time, $duration, $max_persons, $is_available, $type)
|
||||
{
|
||||
$sql = "INSERT INTO consultant_schedule
|
||||
(specific_date, start_time, end_time, time_slot, max_persons, is_available, temp_1, created_at)
|
||||
VALUES
|
||||
('{$date}', '{$start_time}', '{$end_time}', {$duration}, {$max_persons}, {$is_available}, '{$type}', NOW())";
|
||||
|
||||
return sql_query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴무일 슬롯 생성
|
||||
*/
|
||||
private function createHolidaySlot($date, $reason = '휴무일')
|
||||
{
|
||||
$sql = "INSERT INTO consultant_schedule
|
||||
(specific_date, start_time, end_time, time_slot, max_persons, is_available, temp_1, temp_2, created_at)
|
||||
VALUES
|
||||
('{$date}', '00:00', '23:59', 0, 0, 0, 'holiday', '{$reason}', NOW())";
|
||||
|
||||
return sql_query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 점심시간 여부 확인
|
||||
*/
|
||||
private function isLunchTime($slot_start, $slot_end, $lunch_start, $lunch_end)
|
||||
{
|
||||
if (empty($lunch_start) || empty($lunch_end)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$slot_start_time = strtotime($slot_start);
|
||||
$slot_end_time = strtotime($slot_end);
|
||||
$lunch_start_time = strtotime($lunch_start);
|
||||
$lunch_end_time = strtotime($lunch_end);
|
||||
|
||||
// 슬롯이 점심시간과 겹치는지 확인
|
||||
return ($slot_start_time >= $lunch_start_time && $slot_start_time < $lunch_end_time) ||
|
||||
($slot_end_time > $lunch_start_time && $slot_end_time <= $lunch_end_time) ||
|
||||
($slot_start_time <= $lunch_start_time && $slot_end_time >= $lunch_end_time);
|
||||
}
|
||||
|
||||
/**
|
||||
* 해당 월의 모든 날짜 배열 생성
|
||||
*/
|
||||
private function getMonthDates($year, $month)
|
||||
{
|
||||
$dates = [];
|
||||
$days_in_month = cal_days_in_month(CAL_GREGORIAN, $month, $year);
|
||||
|
||||
for ($day = 1; $day <= $days_in_month; $day++) {
|
||||
$dates[] = sprintf('%04d-%02d-%02d', $year, $month, $day);
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 요일 숫자를 요일명으로 변환
|
||||
*/
|
||||
private function getDayName($day_of_week)
|
||||
{
|
||||
$day_names = [
|
||||
0 => 'sunday',
|
||||
1 => 'monday',
|
||||
2 => 'tuesday',
|
||||
3 => 'wednesday',
|
||||
4 => 'thursday',
|
||||
5 => 'friday',
|
||||
6 => 'saturday'
|
||||
];
|
||||
|
||||
return $day_names[$day_of_week] ?? 'sunday';
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본 설정 조회
|
||||
*/
|
||||
private function getBasicSettings()
|
||||
{
|
||||
return [
|
||||
'consultation_duration' => consultant_get_config('consultation_duration', '60'),
|
||||
'max_persons_per_slot' => consultant_get_config('max_persons_per_slot', '2'),
|
||||
'consultation_fee' => consultant_get_config('consultation_fee', '50000')
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 요일별 설정 조회
|
||||
*/
|
||||
private function getWeeklySettings()
|
||||
{
|
||||
$days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
||||
$settings = [];
|
||||
|
||||
foreach ($days as $day) {
|
||||
$settings[$day] = [
|
||||
'enabled' => consultant_get_config($day . '_enabled', $day == 'saturday' || $day == 'sunday' ? '0' : '1'),
|
||||
'start' => consultant_get_config($day . '_start', '09:00'),
|
||||
'end' => consultant_get_config($day . '_end', '18:00'),
|
||||
'lunch_start' => consultant_get_config($day . '_lunch_start', '12:00'),
|
||||
'lunch_end' => consultant_get_config($day . '_lunch_end', '13:00')
|
||||
];
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 자동 생성된 스케줄 삭제
|
||||
*/
|
||||
private function clearAutoGeneratedSchedule($year, $month)
|
||||
{
|
||||
$start_date = sprintf('%04d-%02d-01', $year, $month);
|
||||
$end_date = sprintf('%04d-%02d-%02d', $year, $month, cal_days_in_month(CAL_GREGORIAN, $month, $year));
|
||||
|
||||
// 기존 예약이 없는 자동 생성 스케줄만 삭제
|
||||
$sql = "DELETE cs FROM consultant_schedule cs
|
||||
LEFT JOIN consultant_reservations cr ON (
|
||||
cs.specific_date = cr.reservation_date
|
||||
AND cs.start_time = cr.reservation_time
|
||||
AND cr.is_deleted = 0
|
||||
)
|
||||
WHERE cs.specific_date >= '{$start_date}'
|
||||
AND cs.specific_date <= '{$end_date}'
|
||||
AND cs.temp_1 IN ('auto_generated', 'lunch_time', 'holiday')
|
||||
AND cr.id IS NULL";
|
||||
|
||||
return sql_query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 예약 보호 - 예약이 있는 시간대 확인
|
||||
*/
|
||||
public function getExistingReservations($year, $month)
|
||||
{
|
||||
$start_date = sprintf('%04d-%02d-01', $year, $month);
|
||||
$end_date = sprintf('%04d-%02d-%02d', $year, $month, cal_days_in_month(CAL_GREGORIAN, $month, $year));
|
||||
|
||||
$sql = "SELECT reservation_date, reservation_time, COUNT(*) as count
|
||||
FROM consultant_reservations
|
||||
WHERE reservation_date >= '{$start_date}'
|
||||
AND reservation_date <= '{$end_date}'
|
||||
AND is_deleted = 0
|
||||
GROUP BY reservation_date, reservation_time";
|
||||
|
||||
$result = sql_query($sql);
|
||||
$reservations = [];
|
||||
|
||||
if ($result) {
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$key = $row['reservation_date'] . '_' . $row['reservation_time'];
|
||||
$reservations[$key] = $row['count'];
|
||||
}
|
||||
}
|
||||
|
||||
return $reservations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 다음 달 스케줄 자동 생성 (크론잡용)
|
||||
*/
|
||||
public function generateNextMonth()
|
||||
{
|
||||
$next_month = date('Y-m', strtotime('+1 month'));
|
||||
list($year, $month) = explode('-', $next_month);
|
||||
|
||||
return $this->generateMonth((int) $year, (int) $month);
|
||||
}
|
||||
|
||||
/**
|
||||
* 스케줄 생성 상태 확인
|
||||
*/
|
||||
public function checkScheduleStatus($year, $month)
|
||||
{
|
||||
$start_date = sprintf('%04d-%02d-01', $year, $month);
|
||||
$end_date = sprintf('%04d-%02d-%02d', $year, $month, cal_days_in_month(CAL_GREGORIAN, $month, $year));
|
||||
|
||||
$sql = "SELECT
|
||||
COUNT(*) as total_slots,
|
||||
COUNT(CASE WHEN temp_1 = 'auto_generated' THEN 1 END) as auto_slots,
|
||||
COUNT(CASE WHEN temp_1 = 'lunch_time' THEN 1 END) as lunch_slots,
|
||||
COUNT(CASE WHEN temp_1 = 'holiday' THEN 1 END) as holiday_slots,
|
||||
COUNT(CASE WHEN temp_1 = 'admin_blocked' THEN 1 END) as blocked_slots
|
||||
FROM consultant_schedule
|
||||
WHERE specific_date >= '{$start_date}'
|
||||
AND specific_date <= '{$end_date}'";
|
||||
|
||||
return sql_fetch($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 변경 시 영향받는 예약 확인
|
||||
*/
|
||||
public function checkSettingConflicts($year, $month)
|
||||
{
|
||||
$existing_reservations = $this->getExistingReservations($year, $month);
|
||||
$conflicts = [];
|
||||
|
||||
// 새로운 설정으로 생성될 스케줄과 기존 예약 비교
|
||||
$weekly_settings = $this->getWeeklySettings();
|
||||
$dates = $this->getMonthDates($year, $month);
|
||||
|
||||
foreach ($dates as $date) {
|
||||
$day_of_week = date('w', strtotime($date));
|
||||
$day_name = $this->getDayName($day_of_week);
|
||||
|
||||
// 휴무일로 변경되었는데 예약이 있는 경우
|
||||
if (!isset($weekly_settings[$day_name]) || $weekly_settings[$day_name]['enabled'] != '1') {
|
||||
foreach ($existing_reservations as $key => $count) {
|
||||
if (strpos($key, $date) === 0) {
|
||||
$conflicts[] = [
|
||||
'date' => $date,
|
||||
'type' => 'holiday_conflict',
|
||||
'message' => "{$date}는 휴무일로 설정되었지만 {$count}건의 예약이 있습니다."
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $conflicts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 스케줄 생성 헬퍼 함수들
|
||||
*/
|
||||
|
||||
/**
|
||||
* 월별 스케줄 생성 실행
|
||||
*/
|
||||
function generate_monthly_schedule($year, $month)
|
||||
{
|
||||
$generator = new ScheduleGenerator();
|
||||
return $generator->generateMonth($year, $month);
|
||||
}
|
||||
|
||||
/**
|
||||
* 다음 달 스케줄 자동 생성
|
||||
*/
|
||||
function auto_generate_next_month_schedule()
|
||||
{
|
||||
$generator = new ScheduleGenerator();
|
||||
return $generator->generateNextMonth();
|
||||
}
|
||||
|
||||
/**
|
||||
* 스케줄 생성 상태 확인
|
||||
*/
|
||||
function get_schedule_generation_status($year, $month)
|
||||
{
|
||||
$generator = new ScheduleGenerator();
|
||||
return $generator->checkScheduleStatus($year, $month);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 변경 영향 확인
|
||||
*/
|
||||
function check_schedule_setting_conflicts($year, $month)
|
||||
{
|
||||
$generator = new ScheduleGenerator();
|
||||
return $generator->checkSettingConflicts($year, $month);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,381 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 시스템 통합 설정 관리
|
||||
*/
|
||||
|
||||
$sub_menu = '850610'; // 메뉴 코드 (기본/운영 설정)
|
||||
include_once('./_common.php');
|
||||
|
||||
// 권한 확인
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
// 설치 확인
|
||||
if (!is_consultant_installed()) {
|
||||
alert('상담 예약 시스템이 설치되지 않았습니다.', 'install.php');
|
||||
}
|
||||
|
||||
$g5['title'] = '상담 예약 설정';
|
||||
|
||||
// 현재 탭 확인
|
||||
$current_tab = $_GET['tab'] ?? 'basic';
|
||||
|
||||
// 폼 처리
|
||||
if (isset($_POST['action']) && $_POST['action']) {
|
||||
try {
|
||||
// 기본 설정 저장
|
||||
if ($_POST['action'] == 'save_basic_settings') {
|
||||
$basic_settings = [
|
||||
'consultation_duration' => (int) ($_POST['consultation_duration'] ?? 60),
|
||||
'max_persons_per_slot' => (int) ($_POST['max_persons_per_slot'] ?? 2),
|
||||
'consultation_fee' => (int) ($_POST['consultation_fee'] ?? 50000),
|
||||
'account_info' => trim($_POST['account_info'] ?? ''),
|
||||
'max_advance_days' => (int) ($_POST['max_advance_days'] ?? 30),
|
||||
'min_advance_hours' => (int) ($_POST['min_advance_hours'] ?? 24),
|
||||
'cancel_deadline_hours' => (int) ($_POST['cancel_deadline_hours'] ?? 24)
|
||||
];
|
||||
|
||||
// 유효성 검증 (생략)
|
||||
|
||||
foreach ($basic_settings as $key => $value) {
|
||||
consultant_set_config($key, $value);
|
||||
}
|
||||
alert('기본 설정이 저장되었습니다.', $_SERVER['PHP_SELF'] . '?tab=basic');
|
||||
}
|
||||
|
||||
// 요일별 설정 저장
|
||||
if ($_POST['action'] == 'save_weekly_settings') {
|
||||
$days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
||||
foreach ($days as $day) {
|
||||
consultant_set_config($day . '_enabled', $_POST[$day . '_enabled'] ?? '0');
|
||||
consultant_set_config($day . '_start', $_POST[$day . '_start'] ?? '09:00');
|
||||
consultant_set_config($day . '_end', $_POST[$day . '_end'] ?? '18:00');
|
||||
consultant_set_config($day . '_lunch_start', $_POST[$day . '_lunch_start'] ?? '12:00');
|
||||
consultant_set_config($day . '_lunch_end', $_POST[$day . '_lunch_end'] ?? '13:00');
|
||||
}
|
||||
alert('요일별 설정이 저장되었습니다.', $_SERVER['PHP_SELF'] . '?tab=weekly');
|
||||
}
|
||||
|
||||
// 알림 설정 저장
|
||||
if ($_POST['action'] == 'save_notification_settings') {
|
||||
consultant_set_config('notification_enabled', $_POST['notification_enabled'] ?? '0');
|
||||
alert('알림 설정이 저장되었습니다.', $_SERVER['PHP_SELF'] . '?tab=notification');
|
||||
}
|
||||
|
||||
// 💡 [추가] 고급 설정 저장 처리
|
||||
if ($_POST['action'] == 'save_advanced_settings') {
|
||||
$config_values = $_POST['config_value'] ?? [];
|
||||
$config_descs = $_POST['config_desc'] ?? [];
|
||||
|
||||
foreach ($config_values as $key => $value) {
|
||||
$sql = "UPDATE consultant_config
|
||||
SET config_value = '" . sql_real_escape_string($value) . "',
|
||||
config_desc = '" . sql_real_escape_string($config_descs[$key] ?? '') . "'
|
||||
WHERE config_key = '" . sql_real_escape_string($key) . "'";
|
||||
sql_query($sql);
|
||||
}
|
||||
alert('고급 설정이 저장되었습니다.', $_SERVER['PHP_SELF'] . '?tab=advanced');
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
alert('설정 저장 중 오류가 발생했습니다: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- 데이터 조회 ---
|
||||
// 기본 설정
|
||||
$consultation_duration = consultant_get_config('consultation_duration', '60');
|
||||
$max_persons_per_slot = consultant_get_config('max_persons_per_slot', '2');
|
||||
$consultation_fee = consultant_get_config('consultation_fee', '50000');
|
||||
$account_info = consultant_get_config('account_info', '국민은행 123-456-789 (주)상담센터');
|
||||
$notification_enabled = consultant_get_config('notification_enabled', '1');
|
||||
$max_advance_days = consultant_get_config('max_advance_days', '30');
|
||||
$min_advance_hours = consultant_get_config('min_advance_hours', '24');
|
||||
$cancel_deadline_hours = consultant_get_config('cancel_deadline_hours', '24');
|
||||
|
||||
// 요일별 설정
|
||||
$days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
||||
$day_names = ['월요일', '화요일', '수요일', '목요일', '금요일', '토요일', '일요일'];
|
||||
$weekly_settings = [];
|
||||
foreach ($days as $i => $day) {
|
||||
$weekly_settings[$day] = [
|
||||
'name' => $day_names[$i],
|
||||
'enabled' => consultant_get_config($day . '_enabled', $day == 'saturday' || $day == 'sunday' ? '0' : '1'),
|
||||
'start' => consultant_get_config($day . '_start', '09:00'),
|
||||
'end' => consultant_get_config($day . '_end', '18:00'),
|
||||
'lunch_start' => consultant_get_config($day . '_lunch_start', '12:00'),
|
||||
'lunch_end' => consultant_get_config($day . '_lunch_end', '13:00')
|
||||
];
|
||||
}
|
||||
|
||||
// 💡 [추가] 고급 설정 데이터 조회
|
||||
$advanced_configs = [];
|
||||
$result = sql_query("SELECT * FROM consultant_config ORDER BY id");
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$advanced_configs[] = $row;
|
||||
}
|
||||
|
||||
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
?>
|
||||
|
||||
<div class="settings-container">
|
||||
<div class="settings-header">
|
||||
<h2><?php echo $g5['title']; ?></h2>
|
||||
<div>
|
||||
<a href="dashboard.php" class="header-btn">📊 대시보드</a>
|
||||
<a href="schedule_generate.php" class="header-btn primary">📅 빠른 스케줄 관리</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 탭 네비게이션 -->
|
||||
<div class="settings-tabs">
|
||||
<a href="?tab=basic" class="tab-button <?php echo $current_tab == 'basic' ? 'active' : ''; ?>">⚙️ 기본 설정</a>
|
||||
<a href="?tab=weekly" class="tab-button <?php echo $current_tab == 'weekly' ? 'active' : ''; ?>">📅 요일별 운영시간</a>
|
||||
<a href="?tab=advanced" class="tab-button <?php echo $current_tab == 'advanced' ? 'active' : ''; ?>">🛠️ 고급 설정</a>
|
||||
<a href="?tab=notification" class="tab-button <?php echo $current_tab == 'notification' ? 'active' : ''; ?>">🔔 알림 설정</a>
|
||||
</div>
|
||||
|
||||
<!-- 기본 설정 탭 -->
|
||||
<div class="tab-content <?php echo $current_tab == 'basic' ? 'active' : ''; ?>">
|
||||
<div class="alert alert-info">
|
||||
<strong>기본 설정:</strong> 1회 상담시간, 최대인원, 상담비 등 기본적인 상담 조건을 설정합니다.
|
||||
</div>
|
||||
<form method="post" class="settings-form">
|
||||
<input type="hidden" name="action" value="save_basic_settings">
|
||||
|
||||
<div class="section-title">⏰ 상담 기본 조건</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="consultation_duration">1회 상담 시간 (분)</label>
|
||||
<select id="consultation_duration" name="consultation_duration">
|
||||
<?php
|
||||
for ($i = 15; $i <= 480; $i += 15) {
|
||||
$selected = ($consultation_duration == $i) ? 'selected' : '';
|
||||
echo "<option value=\"{$i}\" {$selected}>{$i}분</option>";
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<small>15분~480분 사이로 설정 가능합니다. (15분 단위)</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="max_persons_per_slot">1회 상담 최대 인원 (명)</label>
|
||||
<input type="number" id="max_persons_per_slot" name="max_persons_per_slot" value="<?php echo htmlspecialchars($max_persons_per_slot); ?>" min="1" max="50" placeholder="2">
|
||||
<small>1명~50명 사이로 설정 가능합니다.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-title">💰 결제 정보</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="consultation_fee">상담 비용 (원)</label>
|
||||
<input type="number" id="consultation_fee" name="consultation_fee" value="<?php echo htmlspecialchars($consultation_fee); ?>" min="0" step="1000" placeholder="50000">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account_info">입금 계좌 정보</label>
|
||||
<textarea id="account_info" name="account_info" placeholder="예: 국민은행 123-456-789 (주)상담센터"><?php echo htmlspecialchars($account_info); ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="section-title">📅 예약 제한 설정</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="max_advance_days">최대 예약 가능 일수</label>
|
||||
<input type="number" id="max_advance_days" name="max_advance_days" value="<?php echo htmlspecialchars($max_advance_days); ?>" min="1" max="365">
|
||||
<small>오늘부터 몇 일 후까지 예약 가능한지 설정</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="min_advance_hours">최소 예약 시간 (시간)</label>
|
||||
<input type="number" id="min_advance_hours" name="min_advance_hours" value="<?php echo htmlspecialchars($min_advance_hours); ?>" min="1" max="168">
|
||||
<small>최소 몇 시간 전에 예약해야 하는지 설정</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cancel_deadline_hours">예약 취소 마감 (시간)</label>
|
||||
<input type="number" id="cancel_deadline_hours" name="cancel_deadline_hours" value="<?php echo htmlspecialchars($cancel_deadline_hours); ?>" min="1" max="168">
|
||||
<small>상담 시작 몇 시간 전까지 고객이 직접 취소할 수 있는지 설정</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-top: 40px;">
|
||||
<button type="submit" class="btn btn-primary">기본 설정 저장</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 요일별 운영시간 탭 -->
|
||||
<div class="tab-content <?php echo $current_tab == 'weekly' ? 'active' : ''; ?>">
|
||||
<div class="alert alert-info">
|
||||
<strong>요일별 운영시간:</strong> 각 요일의 상담 운영 여부와 시간을 설정합니다. '운영'을 선택해야 해당 요일의 스케줄이 생성됩니다.
|
||||
</div>
|
||||
<form method="post" class="settings-form">
|
||||
<input type="hidden" name="action" value="save_weekly_settings">
|
||||
|
||||
<?php foreach ($weekly_settings as $day => $setting): ?>
|
||||
<div class="day-setting">
|
||||
<div class="day-header">
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="hidden" name="<?php echo $day; ?>_enabled" value="0">
|
||||
<input type="checkbox" id="<?php echo $day; ?>_enabled" name="<?php echo $day; ?>_enabled" value="1" <?php echo $setting['enabled'] == '1' ? 'checked' : ''; ?> onchange="toggleDayTimes('<?php echo $day; ?>')">
|
||||
<label for="<?php echo $day; ?>_enabled" class="day-name"><?php echo $setting['name']; ?></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="day-times" id="<?php echo $day; ?>_times">
|
||||
<div class="form-group">
|
||||
<label for="<?php echo $day; ?>_start">업무 시작</label>
|
||||
<input type="time" id="<?php echo $day; ?>_start" name="<?php echo $day; ?>_start" value="<?php echo htmlspecialchars($setting['start']); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="<?php echo $day; ?>_end">업무 종료</label>
|
||||
<input type="time" id="<?php echo $day; ?>_end" name="<?php echo $day; ?>_end" value="<?php echo htmlspecialchars($setting['end']); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="<?php echo $day; ?>_lunch_start">점심 시작</label>
|
||||
<input type="time" id="<?php echo $day; ?>_lunch_start" name="<?php echo $day; ?>_lunch_start" value="<?php echo htmlspecialchars($setting['lunch_start']); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="<?php echo $day; ?>_lunch_end">점심 종료</label>
|
||||
<input type="time" id="<?php echo $day; ?>_lunch_end" name="<?php echo $day; ?>_lunch_end" value="<?php echo htmlspecialchars($setting['lunch_end']); ?>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div style="text-align: center; margin-top: 40px;">
|
||||
<button type="submit" class="btn btn-primary">요일별 설정 저장</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 💡 [추가] 고급 설정 탭 -->
|
||||
<div class="tab-content <?php echo $current_tab == 'advanced' ? 'active' : ''; ?>">
|
||||
<div class="alert alert-info">
|
||||
<strong>고급 설정:</strong> 시스템의 모든 설정값을 직접 관리합니다. <strong>'Key'는 시스템에서 사용하는 고유값이므로 변경할 수 없습니다.</strong><br>
|
||||
'Value'는 실제 적용되는 값이며, 'Description'은 관리자가 참고하기 위한 설명입니다.
|
||||
</div>
|
||||
<form method="post" class="settings-form">
|
||||
<input type="hidden" name="action" value="save_advanced_settings">
|
||||
<div class="tbl_head01 tbl_wrap">
|
||||
<table>
|
||||
<caption>고급 설정 목록</caption>
|
||||
<colgroup>
|
||||
<col style="width: 25%;">
|
||||
<col style="width: 40%;">
|
||||
<col style="width: 35%;">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Key (변경불가)</th>
|
||||
<th scope="col">Value (설정값)</th>
|
||||
<th scope="col">Description (설명)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($advanced_configs as $config_item): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="config_value_<?php echo $config_item['config_key']; ?>">
|
||||
<code style="font-size: 13px; font-weight: bold;"><?php echo htmlspecialchars($config_item['config_key']); ?></code>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (strlen($config_item['config_value']) > 50 || strpos($config_item['config_value'], "\n") !== false): ?>
|
||||
<textarea class="form-control" id="config_value_<?php echo $config_item['config_key']; ?>" name="config_value[<?php echo $config_item['config_key']; ?>]" rows="2"><?php echo htmlspecialchars($config_item['config_value']); ?></textarea>
|
||||
<?php else: ?>
|
||||
<input type="text" class="form-control" id="config_value_<?php echo $config_item['config_key']; ?>" name="config_value[<?php echo $config_item['config_key']; ?>]" value="<?php echo htmlspecialchars($config_item['config_value']); ?>">
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="form-control" name="config_desc[<?php echo $config_item['config_key']; ?>]" value="<?php echo htmlspecialchars($config_item['config_desc']); ?>">
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="text-align: center; margin-top: 40px;">
|
||||
<button type="submit" class="btn btn-primary">고급 설정 저장</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 알림 설정 탭 -->
|
||||
<div class="tab-content <?php echo $current_tab == 'notification' ? 'active' : ''; ?>">
|
||||
<div class="alert alert-info">
|
||||
<strong>알림 설정:</strong> 예약 관련 알림 기능을 설정합니다.
|
||||
</div>
|
||||
<form method="post" class="settings-form">
|
||||
<input type="hidden" name="action" value="save_notification_settings">
|
||||
<div class="section-title">🔔 알림 기능</div>
|
||||
<div class="form-group">
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="checkbox" id="notification_enabled" name="notification_enabled" value="1" <?php echo $notification_enabled == '1' ? 'checked' : ''; ?>>
|
||||
<label for="notification_enabled">알림 기능 사용</label>
|
||||
</div>
|
||||
<small>예약 확정, 취소 등의 상황에서 고객에게 알림을 발송합니다.</small>
|
||||
</div>
|
||||
<div style="text-align: center; margin-top: 40px;">
|
||||
<button type="submit" class="btn btn-primary">알림 설정 저장</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 💡 [복구] 깨진 화면을 복구하기 위해 CSS와 JS를 파일 내에 다시 포함합니다. -->
|
||||
<style>
|
||||
.settings-container { max-width: 1000px; margin: 0 auto; padding: 20px; }
|
||||
.settings-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.settings-header div { display: flex; gap: 8px; }
|
||||
.settings-tabs { display: flex; border-bottom: 2px solid #ddd; margin-bottom: 30px; }
|
||||
.tab-button { padding: 16px 28px; border: none; background: none; cursor: pointer; font-size: 16px; font-weight: 600; color: #666; text-decoration: none; border-bottom: 3px solid transparent; transition: all 0.3s; }
|
||||
.tab-button.active { color: #007bff; border-bottom-color: #007bff; }
|
||||
.tab-button:hover { color: #007bff; background: #fff; }
|
||||
.tab-content { display: none; }
|
||||
.tab-content.active { display: block; }
|
||||
.settings-form { background: white; border: 1px solid #ddd; border-radius: 8px; padding: 30px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); }
|
||||
.form-group { margin-bottom: 20px; }
|
||||
.form-group label { display: block; margin-bottom: 8px; font-weight: bold; color: #333; }
|
||||
.form-group input, .form-group textarea, .form-group select { width: 100%; padding: 12px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; box-sizing: border-box; min-height: 44px; line-height: 1.4; }
|
||||
.form-group textarea { height: auto; resize: vertical; }
|
||||
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||
.btn { padding: 16px 32px; border: none; border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 16px; text-decoration: none; display: inline-block; transition: all 0.2s; text-align: center; min-height: 50px; line-height: 1.4; box-sizing: border-box; vertical-align: middle; }
|
||||
.btn-primary { background: #007bff; color: white; }
|
||||
.btn-primary:hover { background: #0056b3; }
|
||||
.alert { padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; }
|
||||
.alert-info { color: #0c5460; background-color: #d1ecf1; border-color: #bee5eb; }
|
||||
.section-title { font-size: 18px; font-weight: bold; margin: 30px 0 15px 0; padding-bottom: 8px; border-bottom: 2px solid #007bff; color: #333; }
|
||||
.section-title:first-child { margin-top: 0; }
|
||||
.day-setting { background: #fff; border: 1px solid #ddd; border-radius: 8px; padding: 20px; margin-bottom: 15px; }
|
||||
.day-header { display: flex; align-items: center; margin-bottom: 15px; gap: 15px; }
|
||||
.day-name { font-weight: bold; font-size: 16px; color: #333; min-width: 80px; }
|
||||
.day-times { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 15px; }
|
||||
.day-times.disabled { opacity: 0.5; pointer-events: none; }
|
||||
.checkbox-wrapper { display: flex; align-items: center; gap: 8px; }
|
||||
.checkbox-wrapper input[type="checkbox"] { width: 18px; height: 18px; margin: 0; }
|
||||
.form-group small { display: block; margin-top: 5px; font-size: 12px; color: #666; line-height: 1.3; }
|
||||
.header-btn { padding: 8px 16px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; font-weight: 500; font-size: 14px; text-decoration: none; display: inline-block; transition: all 0.2s; text-align: center; min-height: auto; line-height: 1.2; box-sizing: border-box; background: #fff; color: #333; }
|
||||
.header-btn:hover { background: #fff; border-color: #adb5bd; color: #333; }
|
||||
.header-btn.primary { background: #e3f2fd; border-color: #90caf9; color: #1976d2; }
|
||||
.header-btn.primary:hover { background: #bbdefb; border-color: #64b5f6; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function toggleDayTimes(day) {
|
||||
const checkbox = document.getElementById(day + '_enabled');
|
||||
const timesDiv = document.getElementById(day + '_times');
|
||||
if (checkbox?.checked) {
|
||||
timesDiv.classList.remove('disabled');
|
||||
} else {
|
||||
timesDiv?.classList.add('disabled');
|
||||
}
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
||||
days.forEach(function (day) {
|
||||
toggleDayTimes(day);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,454 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 통계 분석
|
||||
*/
|
||||
$sub_menu = '850400';
|
||||
include_once('./_common.php');
|
||||
|
||||
// 관리자 권한 확인
|
||||
if (!$is_admin) {
|
||||
alert('관리자만 접근할 수 있습니다.');
|
||||
}
|
||||
|
||||
// 설치 확인
|
||||
if (!is_consultant_installed()) {
|
||||
alert('상담 예약 시스템이 설치되지 않았습니다.', 'install.php');
|
||||
}
|
||||
|
||||
$g5['title'] = '통계 분석';
|
||||
|
||||
// 기간 설정
|
||||
$start_date = $_GET['start_date'] ?? date('Y-m-01'); // 이번 달 첫날
|
||||
$end_date = $_GET['end_date'] ?? date('Y-m-d'); // 오늘
|
||||
|
||||
// 전체 통계
|
||||
$sql = "SELECT
|
||||
COUNT(*) as total,
|
||||
COUNT(CASE WHEN status = 'payment_pending' THEN 1 END) as pending,
|
||||
COUNT(CASE WHEN status = 'reserved' THEN 1 END) as confirmed,
|
||||
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed,
|
||||
COUNT(CASE WHEN status = 'cancelled' THEN 1 END) as cancelled,
|
||||
SUM(CASE WHEN status = 'completed' THEN payment_amount ELSE 0 END) as total_revenue
|
||||
FROM consultant_reservations
|
||||
WHERE reservation_date BETWEEN '{$start_date}' AND '{$end_date}'
|
||||
AND is_deleted = 0";
|
||||
$total_stats = sql_fetch($sql);
|
||||
|
||||
// 일별 통계
|
||||
$sql = "SELECT
|
||||
reservation_date,
|
||||
COUNT(*) as total,
|
||||
COUNT(CASE WHEN status = 'payment_pending' THEN 1 END) as pending,
|
||||
COUNT(CASE WHEN status = 'reserved' THEN 1 END) as confirmed,
|
||||
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed,
|
||||
COUNT(CASE WHEN status = 'cancelled' THEN 1 END) as cancelled,
|
||||
SUM(CASE WHEN status = 'completed' THEN payment_amount ELSE 0 END) as revenue
|
||||
FROM consultant_reservations
|
||||
WHERE reservation_date BETWEEN '{$start_date}' AND '{$end_date}'
|
||||
AND is_deleted = 0
|
||||
GROUP BY reservation_date
|
||||
ORDER BY reservation_date DESC";
|
||||
$daily_stats = [];
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$daily_stats[] = $row;
|
||||
}
|
||||
|
||||
// 시간대별 통계
|
||||
$sql = "SELECT
|
||||
reservation_time,
|
||||
COUNT(*) as count
|
||||
FROM consultant_reservations
|
||||
WHERE reservation_date BETWEEN '{$start_date}' AND '{$end_date}'
|
||||
AND is_deleted = 0
|
||||
GROUP BY reservation_time
|
||||
ORDER BY reservation_time";
|
||||
$time_stats = [];
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$time_stats[] = $row;
|
||||
}
|
||||
|
||||
// 상태별 통계
|
||||
$status_labels = [
|
||||
'payment_pending' => '입금대기',
|
||||
'reserved' => '예약확정',
|
||||
'completed' => '상담완료',
|
||||
'cancelled' => '예약취소'
|
||||
];
|
||||
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
?>
|
||||
|
||||
<style>
|
||||
.statistics-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.filter-form {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.stat-card.total .stat-number {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.stat-card.pending .stat-number {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.stat-card.confirmed .stat-number {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.stat-card.completed .stat-number {
|
||||
color: #17a2b8;
|
||||
}
|
||||
|
||||
.stat-card.cancelled .stat-number {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.stat-card.revenue .stat-number {
|
||||
color: #6f42c1;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background: #fff;
|
||||
padding: 15px 20px;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.table-content {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #fff;
|
||||
font-weight: bold;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.bar-chart {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
height: 200px;
|
||||
gap: 10px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.bar {
|
||||
background: #007bff;
|
||||
border-radius: 4px 4px 0 0;
|
||||
min-width: 40px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bar-value {
|
||||
position: absolute;
|
||||
top: -25px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
margin-top: 10px;
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
transform: rotate(-45deg);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.filter-form {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="statistics-container">
|
||||
<h2><?php echo $g5['title']; ?></h2>
|
||||
|
||||
<!-- 기간 필터 -->
|
||||
<form method="get" class="filter-form">
|
||||
<label>
|
||||
시작일:
|
||||
<input type="date" name="start_date" value="<?php echo $start_date; ?>" class="form-control">
|
||||
</label>
|
||||
|
||||
<label>
|
||||
종료일:
|
||||
<input type="date" name="end_date" value="<?php echo $end_date; ?>" class="form-control">
|
||||
</label>
|
||||
|
||||
<button type="submit" class="btn btn-primary">조회</button>
|
||||
<a href="dashboard.php" class="btn btn-secondary">대시보드로</a>
|
||||
</form>
|
||||
|
||||
<!-- 전체 통계 -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card total">
|
||||
<div class="stat-number"><?php echo number_format($total_stats['total']); ?></div>
|
||||
<div class="stat-label">전체 예약</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card pending">
|
||||
<div class="stat-number"><?php echo number_format($total_stats['pending']); ?></div>
|
||||
<div class="stat-label">입금대기</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card confirmed">
|
||||
<div class="stat-number"><?php echo number_format($total_stats['confirmed']); ?></div>
|
||||
<div class="stat-label">예약확정</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card completed">
|
||||
<div class="stat-number"><?php echo number_format($total_stats['completed']); ?></div>
|
||||
<div class="stat-label">상담완료</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card cancelled">
|
||||
<div class="stat-number"><?php echo number_format($total_stats['cancelled']); ?></div>
|
||||
<div class="stat-label">예약취소</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card revenue">
|
||||
<div class="stat-number"><?php echo number_format($total_stats['total_revenue']); ?>원</div>
|
||||
<div class="stat-label">총 매출</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 시간대별 예약 현황 -->
|
||||
<?php if (!empty($time_stats)): ?>
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">시간대별 예약 현황</div>
|
||||
<div class="bar-chart">
|
||||
<?php
|
||||
$max_count = max(array_column($time_stats, 'count'));
|
||||
foreach ($time_stats as $stat):
|
||||
$height = $max_count > 0 ? ($stat['count'] / $max_count) * 150 : 0;
|
||||
?>
|
||||
<div class="bar" style="height: <?php echo $height; ?>px;">
|
||||
<div class="bar-value"><?php echo $stat['count']; ?></div>
|
||||
<div class="bar-label"><?php echo substr($stat['reservation_time'], 0, 5); ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- 일별 상세 통계 -->
|
||||
<div class="table-container">
|
||||
<div class="table-header">일별 예약 현황</div>
|
||||
<div class="table-content">
|
||||
<?php if (!empty($daily_stats)): ?>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>날짜</th>
|
||||
<th>전체</th>
|
||||
<th>입금대기</th>
|
||||
<th>예약확정</th>
|
||||
<th>상담완료</th>
|
||||
<th>예약취소</th>
|
||||
<th>매출</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($daily_stats as $stat): ?>
|
||||
<tr>
|
||||
<td><?php echo $stat['reservation_date']; ?></td>
|
||||
<td><?php echo number_format($stat['total']); ?></td>
|
||||
<td><?php echo number_format($stat['pending']); ?></td>
|
||||
<td><?php echo number_format($stat['confirmed']); ?></td>
|
||||
<td><?php echo number_format($stat['completed']); ?></td>
|
||||
<td><?php echo number_format($stat['cancelled']); ?></td>
|
||||
<td><?php echo number_format($stat['revenue']); ?>원</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="no-data">
|
||||
선택한 기간에 예약 데이터가 없습니다.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 차트 애니메이션
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const bars = document.querySelectorAll('.bar');
|
||||
bars.forEach((bar, index) => {
|
||||
setTimeout(() => {
|
||||
bar.style.opacity = '0';
|
||||
bar.style.transform = 'scaleY(0)';
|
||||
bar.style.transformOrigin = 'bottom';
|
||||
|
||||
setTimeout(() => {
|
||||
bar.style.transition = 'all 0.5s ease';
|
||||
bar.style.opacity = '1';
|
||||
bar.style.transform = 'scaleY(1)';
|
||||
}, 100);
|
||||
}, index * 100);
|
||||
});
|
||||
});
|
||||
|
||||
// 기간 설정 단축키
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const startDateInput = document.querySelector('input[name="start_date"]');
|
||||
const endDateInput = document.querySelector('input[name="end_date"]');
|
||||
|
||||
// 오늘 날짜
|
||||
const today = new Date();
|
||||
const todayStr = today.toISOString().split('T')[0];
|
||||
|
||||
// 이번 주 시작일 (월요일)
|
||||
const thisWeekStart = new Date(today);
|
||||
thisWeekStart.setDate(today.getDate() - today.getDay() + 1);
|
||||
const thisWeekStartStr = thisWeekStart.toISOString().split('T')[0];
|
||||
|
||||
// 이번 달 시작일
|
||||
const thisMonthStart = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
const thisMonthStartStr = thisMonthStart.toISOString().split('T')[0];
|
||||
|
||||
// 단축키 버튼들 추가
|
||||
const filterForm = document.querySelector('.filter-form');
|
||||
const shortcutButtons = document.createElement('div');
|
||||
shortcutButtons.innerHTML = `
|
||||
<button type="button" onclick="setDateRange('${todayStr}', '${todayStr}')" class="btn btn-secondary">오늘</button>
|
||||
<button type="button" onclick="setDateRange('${thisWeekStartStr}', '${todayStr}')" class="btn btn-secondary">이번주</button>
|
||||
<button type="button" onclick="setDateRange('${thisMonthStartStr}', '${todayStr}')" class="btn btn-secondary">이번달</button>
|
||||
`;
|
||||
filterForm.appendChild(shortcutButtons);
|
||||
|
||||
window.setDateRange = function (start, end) {
|
||||
startDateInput.value = start;
|
||||
endDateInput.value = end;
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,427 @@
|
||||
<?php
|
||||
/**
|
||||
* 알림 템플릿 관리
|
||||
*/
|
||||
$sub_menu = '850620';
|
||||
include_once('./_common.php');
|
||||
|
||||
// 관리자 권한 확인
|
||||
if (!$is_admin) {
|
||||
alert('관리자만 접근할 수 있습니다.');
|
||||
}
|
||||
|
||||
// 설치 확인
|
||||
if (!is_consultant_installed()) {
|
||||
alert('상담 예약 시스템이 설치되지 않았습니다.', 'install.php');
|
||||
}
|
||||
|
||||
$g5['title'] = '알림 템플릿 관리';
|
||||
|
||||
// 템플릿 저장 처리
|
||||
if ($_POST['action'] == 'save_template') {
|
||||
$template_key = $_POST['template_key'];
|
||||
$template_subject = $_POST['template_subject'];
|
||||
$template_type = $_POST['template_type'] ?? 'email'; // 💡 [추가] 템플릿 타입
|
||||
$template_content = $_POST['template_content'];
|
||||
$template_name = $_POST['template_name']; // 💡 [추가] 템플릿 이름을 폼에서 받아옵니다.
|
||||
|
||||
if ($template_key && $template_subject && $template_content) {
|
||||
// 템플릿 저장/업데이트
|
||||
// 💡 [수정] 타입에 따라 테이블 분기
|
||||
$table_name = ($template_type === 'sms') ? 'consultant_sms_templates' : 'consultant_mail_templates';
|
||||
|
||||
$sql = "INSERT INTO {$table_name}
|
||||
(template_key, template_type, template_name, template_subject, template_content, updated_at)
|
||||
VALUES (
|
||||
'" . sql_real_escape_string($template_key) . "',
|
||||
'" . sql_real_escape_string($template_type) . "',
|
||||
'" . sql_real_escape_string($template_name) . "',
|
||||
'" . sql_real_escape_string($template_subject) . "',
|
||||
'" . sql_real_escape_string($template_content) . "',
|
||||
NOW()
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
template_subject = '" . sql_real_escape_string($template_subject) . "',
|
||||
template_type = '" . sql_real_escape_string($template_type) . "',
|
||||
template_content = '" . sql_real_escape_string($template_content) . "',
|
||||
updated_at = NOW()";
|
||||
|
||||
if (sql_query($sql)) {
|
||||
alert('템플릿이 저장되었습니다.', $_SERVER['PHP_SELF'] . '?type=' . $template_type . '&template=' . $template_key);
|
||||
} else {
|
||||
alert('템플릿 저장에 실패했습니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 💡 [추가] 현재 탭 확인
|
||||
$current_type = $_GET['type'] ?? 'email';
|
||||
|
||||
// 기본 템플릿 정의
|
||||
$default_templates = [
|
||||
'consultant_reservation_customer' => [
|
||||
'name' => '고객 예약 신청 확인',
|
||||
'subject' => '[상담예약] 예약 신청이 접수되었습니다',
|
||||
'content' => "안녕하세요 {customer_name}님,\n\n상담 예약 신청이 정상적으로 접수되었습니다.\n\n예약 정보:\n- 날짜: {reservation_date}\n- 시간: {reservation_time}\n- 상담비: {payment_amount}원\n\n입금 계좌: {account_info}\n\n입금 확인 후 예약이 확정됩니다.\n\n감사합니다."
|
||||
],
|
||||
'consultant_confirmed_customer' => [
|
||||
'name' => '고객 예약 확정 알림',
|
||||
'subject' => '[상담예약] 예약이 확정되었습니다',
|
||||
'content' => "안녕하세요 {customer_name}님,\n\n입금이 확인되어 예약이 확정되었습니다.\n\n예약 정보:\n- 날짜: {reservation_date}\n- 시간: {reservation_time}\n\n상담 당일 시간에 맞춰 방문해주시기 바랍니다.\n\n감사합니다."
|
||||
],
|
||||
'consultant_cancelled_customer' => [
|
||||
'name' => '고객 예약 취소 알림',
|
||||
'subject' => '[상담예약] 예약이 취소되었습니다',
|
||||
'content' => "안녕하세요 {customer_name}님,\n\n예약이 취소되었습니다.\n\n취소된 예약 정보:\n- 날짜: {reservation_date}\n- 시간: {reservation_time}\n\n취소 사유: {cancel_reason}\n\n문의사항이 있으시면 연락주시기 바랍니다.\n\n감사합니다."
|
||||
]
|
||||
];
|
||||
|
||||
// 현재 템플릿 조회
|
||||
$templates = [];
|
||||
// 💡 [수정] 현재 탭의 타입에 맞는 템플릿만 조회합니다.
|
||||
if (is_consultant_installed()) {
|
||||
// 💡 [수정] 타입에 따라 테이블 분기
|
||||
$table_name = ($current_type === 'sms') ? 'consultant_sms_templates' : 'consultant_mail_templates';
|
||||
|
||||
// 테이블 존재 여부 확인 (설치 초기 단계 고려)
|
||||
$table_check = sql_query("SHOW TABLES LIKE '{$table_name}'", false);
|
||||
if (sql_num_rows($table_check) > 0) {
|
||||
$sql = "SELECT * FROM {$table_name} WHERE template_type = '".sql_real_escape_string($current_type)."' ORDER BY template_key";
|
||||
$result = sql_query($sql, false);
|
||||
if ($result) {
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$templates[$row['template_key']] = $row;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 기본 템플릿과 병합
|
||||
foreach ($default_templates as $key => $default) {
|
||||
if (!isset($templates[$key])) {
|
||||
$templates[$key] = [
|
||||
'template_key' => $key,
|
||||
'template_name' => $default['name'],
|
||||
'template_subject' => $default['subject'],
|
||||
'template_content' => $default['content']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$current_template_key = $_GET['template'] ?? array_key_first($templates);
|
||||
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
?>
|
||||
|
||||
<style>
|
||||
.templates-container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.template-nav {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.template-tabs {
|
||||
display: flex; border-bottom: 2px solid #ddd; margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.template-tab {
|
||||
padding: 16px 28px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
border-bottom: 3px solid transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.template-tab.active {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.template-form {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
height: 200px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.variables-info {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.variables-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.variables-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.variable-item {
|
||||
background: white;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
color: #0c5460;
|
||||
background-color: #d1ecf1;
|
||||
border-color: #bee5eb;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.template-tabs {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.variables-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="templates-container">
|
||||
<h2><?php echo $g5['title']; ?></h2>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>안내:</strong> 예약 관련 자동 발송 <?php echo ($current_type == 'sms') ? '문자' : '이메일'; ?>의 템플릿을 관리합니다.
|
||||
중괄호 {} 안의 변수들은 실제 데이터로 자동 치환됩니다.
|
||||
</div>
|
||||
|
||||
<!-- 템플릿 탭 -->
|
||||
<div class="template-nav">
|
||||
<div class="template-tabs">
|
||||
<a href="?type=email" class="template-tab <?php echo $current_type == 'email' ? 'active' : ''; ?>">📧 메일 템플릿</a>
|
||||
<a href="?type=sms" class="template-tab <?php echo $current_type == 'sms' ? 'active' : ''; ?>">📱 문자 템플릿</a>
|
||||
</div>
|
||||
|
||||
<?php if(!empty($templates)): ?>
|
||||
<div class="template-tabs" style="border-bottom:none; margin-bottom: 10px;">
|
||||
<?php foreach ($templates as $key => $template): ?>
|
||||
<a href="?type=<?php echo $current_type; ?>&template=<?php echo $key; ?>"
|
||||
class="template-tab <?php echo $current_template_key == $key ? 'active' : ''; ?>" style="font-size:14px; padding: 8px 16px;">
|
||||
<?php echo htmlspecialchars($template['template_name']); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- 사용 가능한 변수 안내 -->
|
||||
<div class="variables-info">
|
||||
<div class="variables-title">📝 사용 가능한 변수</div>
|
||||
<div class="variables-list">
|
||||
<div class="variable-item">{customer_name} - 고객명</div>
|
||||
<div class="variable-item">{customer_phone} - 고객 연락처</div>
|
||||
<div class="variable-item">{customer_email} - 고객 이메일</div>
|
||||
<div class="variable-item">{reservation_date} - 예약 날짜</div>
|
||||
<div class="variable-item">{reservation_time} - 예약 시간</div>
|
||||
<div class="variable-item">{payment_amount} - 상담 비용</div>
|
||||
<div class="variable-item">{account_info} - 입금 계좌</div>
|
||||
<div class="variable-item">{cancel_reason} - 취소 사유</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 템플릿 편집 폼 -->
|
||||
<?php if (isset($templates[$current_template_key])): ?>
|
||||
<?php $template = $templates[$current_template_key]; ?>
|
||||
<form method="post" class="template-form">
|
||||
<input type="hidden" name="action" value="save_template">
|
||||
<input type="hidden" name="template_key" value="<?php echo $current_template_key; ?>">
|
||||
<input type="hidden" name="template_name" value="<?php echo htmlspecialchars($template['template_name']); ?>">
|
||||
<input type="hidden" name="template_type" value="<?php echo $current_type; ?>">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="template_subject"><?php echo ($current_type == 'sms') ? '문자 제목 (LMS용)' : '이메일 제목'; ?></label>
|
||||
<input type="text" id="template_subject" name="template_subject"
|
||||
value="<?php echo htmlspecialchars($template['template_subject']); ?>"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="template_content"><?php echo ($current_type == 'sms') ? '문자 내용' : '이메일 내용'; ?></label>
|
||||
<textarea id="template_content" name="template_content" required><?php echo htmlspecialchars($template['template_content']); ?></textarea>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-top: 30px;">
|
||||
<!-- 💡 [개선] 미리보기 버튼을 JS로 생성하는 대신 HTML에 직접 추가하여 안정성을 높이고, 저장 버튼과 나란히 배치합니다. -->
|
||||
<button type="button" onclick="previewTemplate()" class="btn btn-secondary" style="margin-right: 10px;">미리보기</button>
|
||||
<button type="submit" class="btn btn-primary">템플릿 저장</button>
|
||||
<a href="dashboard.php" class="btn btn-secondary">대시보드로</a>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 변수 삽입 도우미
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const textarea = document.getElementById('template_content');
|
||||
const variableItems = document.querySelectorAll('.variable-item');
|
||||
|
||||
variableItems.forEach(item => {
|
||||
item.style.cursor = 'pointer';
|
||||
item.title = '클릭하여 템플릿에 삽입';
|
||||
|
||||
item.addEventListener('click', function() {
|
||||
const variable = this.textContent.split(' - ')[0];
|
||||
const cursorPos = textarea.selectionStart;
|
||||
const textBefore = textarea.value.substring(0, cursorPos);
|
||||
const textAfter = textarea.value.substring(cursorPos);
|
||||
|
||||
textarea.value = textBefore + variable + textAfter;
|
||||
textarea.focus();
|
||||
textarea.setSelectionRange(cursorPos + variable.length, cursorPos + variable.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 템플릿 미리보기
|
||||
function previewTemplate() {
|
||||
const subject = document.getElementById('template_subject').value;
|
||||
const content = document.getElementById('template_content').value;
|
||||
|
||||
// 샘플 데이터로 치환
|
||||
const sampleData = {
|
||||
'{customer_name}': '홍길동',
|
||||
'{customer_phone}': '010-1234-5678',
|
||||
'{customer_email}': 'hong@example.com',
|
||||
'{reservation_date}': '2024-12-15',
|
||||
'{reservation_time}': '14:00',
|
||||
'{payment_amount}': '50,000',
|
||||
'{account_info}': '국민은행 123-456-789 (주)상담센터',
|
||||
'{cancel_reason}': '개인 사정'
|
||||
};
|
||||
|
||||
let previewSubject = subject;
|
||||
let previewContent = content;
|
||||
|
||||
for (const [variable, value] of Object.entries(sampleData)) {
|
||||
previewSubject = previewSubject.replace(new RegExp(variable.replace(/[{}]/g, '\\\\$&'), 'g'), value);
|
||||
previewContent = previewContent.replace(new RegExp(variable.replace(/[{}]/g, '\\\\$&'), 'g'), value);
|
||||
}
|
||||
|
||||
// 💡 [개선] 실제 이메일처럼 보이도록 nl2br 처리 및 UI 개선
|
||||
const previewHtmlContent = previewContent.replace(/\n/g, '<br>');
|
||||
|
||||
// 💡 [개선] document.write() 대신 DOM 조작을 사용하여 안정성을 높입니다.
|
||||
const previewWindow = window.open('', '_blank', 'width=800,height=600');
|
||||
const previewDoc = previewWindow.document;
|
||||
|
||||
previewDoc.open();
|
||||
previewDoc.write(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>미리보기</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 0; padding: 0; background-color: #f6f8fa; }
|
||||
.preview-container { max-width: 800px; margin: 20px auto; background-color: #fff; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 1px 5px rgba(0,0,0,0.1); }
|
||||
.preview-header { padding: 20px; border-bottom: 1px solid #eee; }
|
||||
.preview-header h2 { margin: 0; font-size: 20px; color: #333; }
|
||||
.preview-meta { padding: 15px 20px; background-color: #fdfdfd; border-bottom: 1px solid #eee; font-size: 14px; }
|
||||
.meta-item { display: flex; margin-bottom: 8px; }
|
||||
.meta-label { font-weight: bold; color: #555; width: 80px; }
|
||||
.meta-value { color: #333; }
|
||||
.preview-body { padding: 30px 20px; line-height: 1.7; color: #333; font-size: 15px; }
|
||||
</style>
|
||||
</head>
|
||||
<bo`+`dy>
|
||||
<div class='preview-container'>
|
||||
<div class="preview-header"><h2>미리보기</h2></div>
|
||||
<div class="preview-meta">
|
||||
<div class="meta-item"><span class="meta-label">보내는사람:</span><span class="meta-value">관리자 <admin@example.com></span></div>
|
||||
<div class="meta-item"><span class="meta-label">받는사람:</span><span class="meta-value">홍길동 <hong@example.com></span></div>
|
||||
<div class="meta-item"><span class="meta-label">제 목:</span><span class="meta-value">${previewSubject}</span></div>
|
||||
</div>
|
||||
<div class="preview-body">${previewHtmlContent}</div>
|
||||
</div>
|
||||
</bo`+`dy>
|
||||
</html>
|
||||
`);
|
||||
previewWindow.document.close();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 시스템 테스트 페이지
|
||||
*/
|
||||
|
||||
// 오류 표시 활성화
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
echo "<h1>상담 예약 시스템 테스트</h1>";
|
||||
|
||||
// 1. 그누보드 기본 파일 로드 테스트
|
||||
echo "<h3>1. 그누보드 기본 파일 로드 테스트</h3>";
|
||||
try {
|
||||
include_once('./_common_con.php');
|
||||
echo "✅ common.php 로드 성공<br>";
|
||||
echo "✅ 데이터베이스 연결: " . (isset($connect_db) ? "성공" : "실패") . "<br>";
|
||||
echo "✅ 관리자 권한: " . ($is_admin ? "있음" : "없음") . "<br>";
|
||||
echo "✅ G5_PATH: " . (defined('G5_PATH') ? G5_PATH : "정의되지 않음") . "<br>";
|
||||
} catch (Exception $e) {
|
||||
echo "❌ common.php 로드 실패: " . $e->getMessage() . "<br>";
|
||||
}
|
||||
|
||||
// 2. 함수 존재 확인
|
||||
echo "<h3>2. 필수 함수 존재 확인</h3>";
|
||||
$functions = ['sql_query', 'sql_fetch', 'sql_real_escape_string', 'alert'];
|
||||
foreach ($functions as $func) {
|
||||
echo (function_exists($func) ? "✅" : "❌") . " {$func}<br>";
|
||||
}
|
||||
|
||||
// 3. 상수 확인
|
||||
echo "<h3>3. 필수 상수 확인</h3>";
|
||||
$constants = ['G5_PATH', 'G5_ADMIN_PATH', 'G5_DATA_PATH'];
|
||||
foreach ($constants as $const) {
|
||||
echo (defined($const) ? "✅" : "❌") . " {$const}: " . (defined($const) ? constant($const) : "정의되지 않음") . "<br>";
|
||||
}
|
||||
|
||||
// 4. 데이터베이스 연결 테스트
|
||||
echo "<h3>4. 데이터베이스 연결 테스트</h3>";
|
||||
try {
|
||||
$sql = "SELECT 1 as test";
|
||||
$result = sql_query($sql);
|
||||
if ($result) {
|
||||
echo "✅ 데이터베이스 쿼리 성공<br>";
|
||||
} else {
|
||||
echo "❌ 데이터베이스 쿼리 실패<br>";
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "❌ 데이터베이스 오류: " . $e->getMessage() . "<br>";
|
||||
}
|
||||
|
||||
// 5. 테이블 존재 확인
|
||||
echo "<h3>5. 상담 시스템 테이블 확인</h3>";
|
||||
$tables = ['consultant_config', 'consultant_schedule', 'consultant_reservations'];
|
||||
foreach ($tables as $table) {
|
||||
$sql = "SHOW TABLES LIKE '{$table}'";
|
||||
$result = sql_query($sql, false);
|
||||
$exists = $result && sql_num_rows($result) > 0;
|
||||
echo ($exists ? "✅" : "❌") . " {$table}<br>";
|
||||
}
|
||||
|
||||
echo "<hr>";
|
||||
echo "<p><a href='install_simple.php'>간단 설치 페이지로 이동</a></p>";
|
||||
echo "<p><a href='../install.php'>원본 설치 페이지로 이동</a></p>";
|
||||
?>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 시스템 설치 테스트
|
||||
*/
|
||||
|
||||
// 오류 표시 활성화
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
echo "<h1>상담 예약 시스템 설치 테스트</h1>";
|
||||
|
||||
// 1. 그누보드 기본 파일 로드 테스트
|
||||
echo "<h3>1. 그누보드 기본 파일 로드 테스트</h3>";
|
||||
try {
|
||||
include_once('../../common.php');
|
||||
echo "✅ common.php 로드 성공<br>";
|
||||
echo "✅ 데이터베이스 연결: " . (isset($connect_db) ? "성공" : "실패") . "<br>";
|
||||
echo "✅ 관리자 권한: " . ($is_admin ? "있음" : "없음") . "<br>";
|
||||
} catch (Exception $e) {
|
||||
echo "❌ common.php 로드 실패: " . $e->getMessage() . "<br>";
|
||||
}
|
||||
|
||||
// 2. _common.php 로드 테스트
|
||||
echo "<h3>2. _common.php 로드 테스트</h3>";
|
||||
try {
|
||||
include_once('./_common.php');
|
||||
echo "✅ _common.php 로드 성공<br>";
|
||||
echo "✅ 상담 시스템 버전: " . (defined('G5_CONSULTANT_VERSION') ? G5_CONSULTANT_VERSION : "정의되지 않음") . "<br>";
|
||||
} catch (Exception $e) {
|
||||
echo "❌ _common.php 로드 실패: " . $e->getMessage() . "<br>";
|
||||
}
|
||||
|
||||
// 3. 함수 존재 확인
|
||||
echo "<h3>3. 필수 함수 존재 확인</h3>";
|
||||
$functions = ['sql_query', 'sql_fetch', 'sql_real_escape_string', 'alert'];
|
||||
foreach ($functions as $func) {
|
||||
echo (function_exists($func) ? "✅" : "❌") . " {$func}<br>";
|
||||
}
|
||||
|
||||
// 4. 상수 확인
|
||||
echo "<h3>4. 필수 상수 확인</h3>";
|
||||
$constants = ['G5_PATH', 'G5_ADMIN_PATH', 'G5_DATA_PATH'];
|
||||
foreach ($constants as $const) {
|
||||
echo (defined($const) ? "✅" : "❌") . " {$const}: " . (defined($const) ? constant($const) : "정의되지 않음") . "<br>";
|
||||
}
|
||||
|
||||
// 5. 데이터베이스 연결 테스트
|
||||
echo "<h3>5. 데이터베이스 연결 테스트</h3>";
|
||||
try {
|
||||
$sql = "SELECT 1 as test";
|
||||
$result = sql_query($sql);
|
||||
if ($result) {
|
||||
echo "✅ 데이터베이스 쿼리 성공<br>";
|
||||
} else {
|
||||
echo "❌ 데이터베이스 쿼리 실패<br>";
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "❌ 데이터베이스 오류: " . $e->getMessage() . "<br>";
|
||||
}
|
||||
|
||||
echo "<hr>";
|
||||
echo "<p><a href='../install.php'>설치 페이지로 이동</a></p>";
|
||||
?>
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
/**
|
||||
* 스케줄 생성 테스트 페이지
|
||||
*/
|
||||
|
||||
include_once('./_common.php');
|
||||
|
||||
// 관리자 권한 확인
|
||||
if (!$is_admin) {
|
||||
alert('관리자만 접근할 수 있습니다.');
|
||||
}
|
||||
|
||||
// 설치 확인
|
||||
if (!is_consultant_installed()) {
|
||||
alert('상담 예약 시스템이 설치되지 않았습니다.', 'install.php');
|
||||
}
|
||||
|
||||
$g5['title'] = '스케줄 생성 테스트';
|
||||
|
||||
// 테스트 실행
|
||||
if ($_POST['action'] == 'test_generation') {
|
||||
$year = (int) ($_POST['year'] ?? date('Y'));
|
||||
$month = (int) ($_POST['month'] ?? date('n'));
|
||||
|
||||
try {
|
||||
require_once('classes/ScheduleGenerator.class.php');
|
||||
$generator = new ScheduleGenerator();
|
||||
|
||||
echo "<h3>테스트 결과</h3>";
|
||||
echo "<p><strong>대상:</strong> {$year}년 {$month}월</p>";
|
||||
|
||||
// 기존 스케줄 확인
|
||||
$existing = sql_fetch("SELECT COUNT(*) as count FROM consultant_schedule WHERE YEAR(specific_date) = {$year} AND MONTH(specific_date) = {$month}");
|
||||
echo "<p><strong>기존 스케줄:</strong> {$existing['count']}개</p>";
|
||||
|
||||
// 충돌 검사
|
||||
// 💡 [수정] 최신 충돌 검사 로직으로 변경
|
||||
$conflicts = $generator->findConflictsWithNewSettings($year, $month);
|
||||
echo "<p><strong>충돌 검사:</strong> " . count($conflicts) . "건</p>";
|
||||
|
||||
if (!empty($conflicts)) {
|
||||
echo "<ul>";
|
||||
foreach ($conflicts as $conflict) {
|
||||
echo "<li>{$conflict['date']} {$conflict['time']} - {$conflict['customer']} ({$conflict['reason']})</li>";
|
||||
}
|
||||
echo "</ul>";
|
||||
}
|
||||
|
||||
// 스케줄 생성
|
||||
$result = $generator->generateMonth($year, $month);
|
||||
|
||||
if ($result) {
|
||||
$new_count = sql_fetch("SELECT COUNT(*) as count FROM consultant_schedule WHERE YEAR(specific_date) = {$year} AND MONTH(specific_date) = {$month}");
|
||||
echo "<p style='color: green;'><strong>✅ 성공:</strong> {$new_count['count']}개 스케줄 생성 완료</p>";
|
||||
|
||||
// 생성된 스케줄 샘플 표시
|
||||
$samples = sql_query("SELECT * FROM consultant_schedule WHERE YEAR(specific_date) = {$year} AND MONTH(specific_date) = {$month} ORDER BY specific_date, start_time LIMIT 10");
|
||||
echo "<h4>생성된 스케줄 샘플 (최대 10개)</h4>";
|
||||
echo "<table border='1' style='border-collapse: collapse; width: 100%;'>";
|
||||
echo "<tr><th>날짜</th><th>시작시간</th><th>종료시간</th><th>최대인원</th><th>사용가능</th><th>타입</th></tr>";
|
||||
while ($row = sql_fetch_array($samples)) {
|
||||
$available = $row['is_available'] ? '가능' : '불가능';
|
||||
$type = $row['temp_1'] ?? '일반';
|
||||
echo "<tr>";
|
||||
echo "<td>{$row['specific_date']}</td>";
|
||||
echo "<td>{$row['start_time']}</td>";
|
||||
echo "<td>{$row['end_time']}</td>";
|
||||
echo "<td>{$row['max_persons']}</td>";
|
||||
echo "<td>{$available}</td>";
|
||||
echo "<td>{$type}</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
} else {
|
||||
echo "<p style='color: red;'><strong>❌ 실패:</strong> 스케줄 생성에 실패했습니다.</p>";
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "<p style='color: red;'><strong>오류:</strong> " . htmlspecialchars($e->getMessage()) . "</p>";
|
||||
}
|
||||
|
||||
echo "<hr>";
|
||||
}
|
||||
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
?>
|
||||
|
||||
<style>
|
||||
.test-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.test-form {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
table {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="test-container">
|
||||
<h2><?php echo $g5['title']; ?></h2>
|
||||
|
||||
<div class="test-form">
|
||||
<h3>스케줄 생성 테스트</h3>
|
||||
<p>선택한 년월의 스케줄을 생성하고 결과를 확인합니다.</p>
|
||||
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="test_generation">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="year">년도</label>
|
||||
<input type="number" id="year" name="year" value="<?php echo date('Y'); ?>" min="2024" max="2030">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="month">월</label>
|
||||
<select id="month" name="month">
|
||||
<?php for ($i = 1; $i <= 12; $i++): ?>
|
||||
<option value="<?php echo $i; ?>" <?php echo $i == date('n') ? 'selected' : ''; ?>>
|
||||
<?php echo $i; ?>월
|
||||
</option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<button type="submit" class="btn btn-primary">스케줄 생성 테스트</button>
|
||||
<a href="../settings.php" class="btn btn-secondary">설정으로 돌아가기</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* ScheduleGenerator 테스트 스크립트
|
||||
*/
|
||||
|
||||
// 기본 설정
|
||||
define('G5_PATH', realpath('../../'));
|
||||
include_once(G5_PATH . '/common.php');
|
||||
include_once('./_common.php');
|
||||
|
||||
// 관리자 권한 확인
|
||||
if (!$is_admin) {
|
||||
die('관리자만 접근할 수 있습니다.');
|
||||
}
|
||||
|
||||
// 설치 확인
|
||||
if (!is_consultant_installed()) {
|
||||
die('상담 예약 시스템이 설치되지 않았습니다.');
|
||||
}
|
||||
|
||||
echo "<h2>ScheduleGenerator 테스트</h2>";
|
||||
|
||||
try {
|
||||
require_once('classes/ScheduleGenerator.class.php');
|
||||
$generator = new ScheduleGenerator();
|
||||
|
||||
// 테스트할 년월
|
||||
$year = 2024;
|
||||
$month = 12;
|
||||
|
||||
echo "<h3>1. 기본 설정 확인</h3>";
|
||||
|
||||
// 기본 설정 확인
|
||||
$duration = consultant_get_config('consultation_duration', 60);
|
||||
$maxPersons = consultant_get_config('max_persons_per_slot', 2);
|
||||
|
||||
echo "- 상담 시간: {$duration}분<br>";
|
||||
echo "- 최대 인원: {$maxPersons}명<br>";
|
||||
|
||||
echo "<h3>2. 요일별 설정 확인</h3>";
|
||||
|
||||
$days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
||||
$dayNames = ['월요일', '화요일', '수요일', '목요일', '금요일', '토요일', '일요일'];
|
||||
|
||||
foreach ($days as $i => $day) {
|
||||
$enabled = consultant_get_config($day . '_enabled', '1');
|
||||
$start = consultant_get_config($day . '_start', '09:00');
|
||||
$end = consultant_get_config($day . '_end', '18:00');
|
||||
$lunchStart = consultant_get_config($day . '_lunch_start', '12:00');
|
||||
$lunchEnd = consultant_get_config($day . '_lunch_end', '13:00');
|
||||
|
||||
echo "- {$dayNames[$i]}: ";
|
||||
if ($enabled == '1') {
|
||||
echo "운영 ({$start}~{$end}, 점심: {$lunchStart}~{$lunchEnd})";
|
||||
} else {
|
||||
echo "휴무";
|
||||
}
|
||||
echo "<br>";
|
||||
}
|
||||
|
||||
echo "<h3>3. 기존 스케줄 확인</h3>";
|
||||
|
||||
$existingCount = sql_fetch("SELECT COUNT(*) as count FROM consultant_schedule WHERE YEAR(specific_date) = {$year} AND MONTH(specific_date) = {$month}");
|
||||
echo "- 기존 스케줄: {$existingCount['count']}개<br>";
|
||||
|
||||
echo "<h3>4. 충돌 검사</h3>";
|
||||
|
||||
$conflicts = $generator->checkScheduleConflicts($year, $month);
|
||||
echo "- 충돌 건수: " . count($conflicts) . "건<br>";
|
||||
|
||||
if (!empty($conflicts)) {
|
||||
echo "<ul>";
|
||||
foreach ($conflicts as $conflict) {
|
||||
echo "<li>{$conflict['date']} {$conflict['time']} - {$conflict['customer']} ({$conflict['reason']})</li>";
|
||||
}
|
||||
echo "</ul>";
|
||||
}
|
||||
|
||||
echo "<h3>5. 스케줄 생성 테스트</h3>";
|
||||
|
||||
$result = $generator->generateMonth($year, $month);
|
||||
|
||||
if ($result) {
|
||||
$newCount = sql_fetch("SELECT COUNT(*) as count FROM consultant_schedule WHERE YEAR(specific_date) = {$year} AND MONTH(specific_date) = {$month}");
|
||||
echo "<p style='color: green;'>✅ 성공: {$newCount['count']}개 스케줄 생성 완료</p>";
|
||||
|
||||
// 생성된 스케줄 샘플 표시
|
||||
echo "<h4>생성된 스케줄 샘플 (첫 5일)</h4>";
|
||||
$samples = sql_query("SELECT * FROM consultant_schedule WHERE YEAR(specific_date) = {$year} AND MONTH(specific_date) = {$month} ORDER BY specific_date, start_time LIMIT 20");
|
||||
|
||||
echo "<table border='1' style='border-collapse: collapse; width: 100%;'>";
|
||||
echo "<tr><th>날짜</th><th>요일</th><th>시작시간</th><th>종료시간</th><th>최대인원</th><th>사용가능</th><th>타입</th><th>메모</th></tr>";
|
||||
|
||||
while ($row = sql_fetch_array($samples)) {
|
||||
$dayOfWeek = date('N', strtotime($row['specific_date']));
|
||||
$dayName = $dayNames[$dayOfWeek - 1];
|
||||
$available = $row['is_available'] ? '가능' : '불가능';
|
||||
$type = $row['temp_1'] ?? '일반';
|
||||
$memo = $row['temp_2'] ?? '';
|
||||
|
||||
echo "<tr>";
|
||||
echo "<td>{$row['specific_date']}</td>";
|
||||
echo "<td>{$dayName}</td>";
|
||||
echo "<td>{$row['start_time']}</td>";
|
||||
echo "<td>{$row['end_time']}</td>";
|
||||
echo "<td>{$row['max_persons']}</td>";
|
||||
echo "<td>{$available}</td>";
|
||||
echo "<td>{$type}</td>";
|
||||
echo "<td>{$memo}</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
|
||||
// 타입별 통계
|
||||
echo "<h4>생성된 스케줄 통계</h4>";
|
||||
$stats = sql_query("SELECT temp_1, COUNT(*) as count FROM consultant_schedule WHERE YEAR(specific_date) = {$year} AND MONTH(specific_date) = {$month} GROUP BY temp_1");
|
||||
|
||||
echo "<ul>";
|
||||
while ($stat = sql_fetch_array($stats)) {
|
||||
$type = $stat['temp_1'] ?? '일반';
|
||||
echo "<li>{$type}: {$stat['count']}개</li>";
|
||||
}
|
||||
echo "</ul>";
|
||||
|
||||
} else {
|
||||
echo "<p style='color: red;'>❌ 실패: 스케줄 생성에 실패했습니다.</p>";
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "<p style='color: red;'>오류: " . htmlspecialchars($e->getMessage()) . "</p>";
|
||||
echo "<pre>" . htmlspecialchars($e->getTraceAsString()) . "</pre>";
|
||||
}
|
||||
|
||||
echo "<hr>";
|
||||
echo "<p><a href='../settings.php'>설정으로 돌아가기</a> | <a href='../schedule.php'>스케줄 관리</a></p>";
|
||||
?>
|
||||
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 시스템 테스트 페이지
|
||||
*/
|
||||
|
||||
$sub_menu = '850900';
|
||||
include_once('./_common.php');
|
||||
|
||||
// 권한 확인
|
||||
auth_check_menu($auth, $sub_menu, 'r');
|
||||
|
||||
$g5['title'] = '시스템 테스트';
|
||||
|
||||
// 테스트 결과
|
||||
$test_results = [];
|
||||
|
||||
// 1. 테이블 존재 확인
|
||||
$tables_to_check = [
|
||||
'consultant_config' => '설정 테이블',
|
||||
'consultant_schedule' => '스케줄 테이블',
|
||||
'consultant_reservations' => '예약 테이블',
|
||||
'consultant_mail_templates' => '메일 템플릿 테이블',
|
||||
'consultant_sms_templates' => 'SMS 템플릿 테이블'
|
||||
];
|
||||
|
||||
foreach ($tables_to_check as $table => $description) {
|
||||
$sql = "SHOW TABLES LIKE '{$table}'";
|
||||
$result = sql_query($sql, false);
|
||||
$exists = $result && sql_num_rows($result) > 0;
|
||||
|
||||
$test_results['tables'][$table] = [
|
||||
'name' => $description,
|
||||
'status' => $exists,
|
||||
'message' => $exists ? '존재함' : '존재하지 않음'
|
||||
];
|
||||
}
|
||||
|
||||
// 2. 기본 설정값 확인
|
||||
$config_keys = [
|
||||
'consultation_duration' => '1회 상담시간',
|
||||
'max_persons_per_slot' => '최대 인원',
|
||||
'consultation_fee' => '상담비',
|
||||
'account_info' => '계좌정보',
|
||||
'monday_enabled' => '월요일 운영여부',
|
||||
'monday_start' => '월요일 시작시간'
|
||||
];
|
||||
|
||||
foreach ($config_keys as $key => $description) {
|
||||
$value = consultant_get_config($key);
|
||||
$test_results['config'][$key] = [
|
||||
'name' => $description,
|
||||
'value' => $value,
|
||||
'status' => $value !== null
|
||||
];
|
||||
}
|
||||
|
||||
// 3. 함수 테스트
|
||||
$function_tests = [
|
||||
'is_consultant_installed' => is_consultant_installed(),
|
||||
'consultant_get_config' => consultant_get_config('consultation_duration', '60'),
|
||||
'consultant_format_time' => consultant_format_time('14:30'),
|
||||
'consultant_format_date' => consultant_format_date('2024-12-02')
|
||||
];
|
||||
|
||||
// 4. 스케줄 생성 테스트 (현재 월)
|
||||
$current_year = date('Y');
|
||||
$current_month = date('m');
|
||||
|
||||
try {
|
||||
include_once('./schedule_generator.php');
|
||||
$generator = new ScheduleGenerator();
|
||||
$schedule_status = $generator->checkScheduleStatus($current_year, $current_month);
|
||||
$test_results['schedule_status'] = $schedule_status;
|
||||
} catch (Exception $e) {
|
||||
$test_results['schedule_error'] = $e->getMessage();
|
||||
}
|
||||
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
?>
|
||||
|
||||
<style>
|
||||
.test-container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.test-section h3 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #333;
|
||||
border-bottom: 2px solid #007bff;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.test-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.test-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.test-name {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.test-value {
|
||||
color: #666;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.test-status {
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-ok {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.status-warning {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.summary-number {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="test-container">
|
||||
<h2><?php echo $g5['title']; ?></h2>
|
||||
|
||||
<!-- 전체 요약 -->
|
||||
<div class="summary-grid">
|
||||
<div class="summary-card">
|
||||
<div class="summary-number" style="color: <?php echo is_consultant_installed() ? '#28a745' : '#dc3545'; ?>">
|
||||
<?php echo is_consultant_installed() ? '✓' : '✗'; ?>
|
||||
</div>
|
||||
<div class="summary-label">시스템 설치</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-card">
|
||||
<div class="summary-number" style="color: #007bff;">
|
||||
<?php echo count(array_filter($test_results['tables'], function ($t) {
|
||||
return $t['status']; })); ?>/<?php echo count($test_results['tables']); ?>
|
||||
</div>
|
||||
<div class="summary-label">테이블 상태</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-card">
|
||||
<div class="summary-number" style="color: #17a2b8;">
|
||||
<?php echo count(array_filter($test_results['config'], function ($c) {
|
||||
return $c['status']; })); ?>/<?php echo count($test_results['config']); ?>
|
||||
</div>
|
||||
<div class="summary-label">설정 상태</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-card">
|
||||
<div class="summary-number" style="color: #6f42c1;">
|
||||
<?php echo isset($test_results['schedule_status']) ? number_format($test_results['schedule_status']['total_slots']) : '0'; ?>
|
||||
</div>
|
||||
<div class="summary-label">현재 월 스케줄</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 테이블 상태 -->
|
||||
<div class="test-section">
|
||||
<h3>📋 데이터베이스 테이블 상태</h3>
|
||||
<?php foreach ($test_results['tables'] as $table => $info): ?>
|
||||
<div class="test-item">
|
||||
<div class="test-name"><?php echo $info['name']; ?> (<?php echo $table; ?>)</div>
|
||||
<div class="test-status <?php echo $info['status'] ? 'status-ok' : 'status-error'; ?>">
|
||||
<?php echo $info['message']; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- 설정 상태 -->
|
||||
<div class="test-section">
|
||||
<h3>⚙️ 기본 설정 상태</h3>
|
||||
<?php foreach ($test_results['config'] as $key => $info): ?>
|
||||
<div class="test-item">
|
||||
<div class="test-name"><?php echo $info['name']; ?> (<?php echo $key; ?>)</div>
|
||||
<div class="test-value"><?php echo htmlspecialchars($info['value'] ?? 'NULL'); ?></div>
|
||||
<div class="test-status <?php echo $info['status'] ? 'status-ok' : 'status-warning'; ?>">
|
||||
<?php echo $info['status'] ? '설정됨' : '미설정'; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- 함수 테스트 -->
|
||||
<div class="test-section">
|
||||
<h3>🔧 함수 테스트</h3>
|
||||
<?php foreach ($function_tests as $func_name => $result): ?>
|
||||
<div class="test-item">
|
||||
<div class="test-name"><?php echo $func_name; ?>()</div>
|
||||
<div class="test-value">
|
||||
<?php echo is_bool($result) ? ($result ? 'true' : 'false') : htmlspecialchars($result); ?></div>
|
||||
<div class="test-status status-ok">정상</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- 스케줄 상태 -->
|
||||
<div class="test-section">
|
||||
<h3>📅 현재 월 스케줄 상태 (<?php echo $current_year; ?>년 <?php echo $current_month; ?>월)</h3>
|
||||
|
||||
<?php if (isset($test_results['schedule_status'])): ?>
|
||||
<?php $status = $test_results['schedule_status']; ?>
|
||||
<div class="test-item">
|
||||
<div class="test-name">전체 슬롯</div>
|
||||
<div class="test-value"><?php echo number_format($status['total_slots']); ?>개</div>
|
||||
<div class="test-status <?php echo $status['total_slots'] > 0 ? 'status-ok' : 'status-warning'; ?>">
|
||||
<?php echo $status['total_slots'] > 0 ? '생성됨' : '미생성'; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-item">
|
||||
<div class="test-name">상담 가능 슬롯</div>
|
||||
<div class="test-value"><?php echo number_format($status['auto_slots']); ?>개</div>
|
||||
<div class="test-status status-ok">정상</div>
|
||||
</div>
|
||||
|
||||
<div class="test-item">
|
||||
<div class="test-name">점심시간 슬롯</div>
|
||||
<div class="test-value"><?php echo number_format($status['lunch_slots']); ?>개</div>
|
||||
<div class="test-status status-ok">정상</div>
|
||||
</div>
|
||||
|
||||
<div class="test-item">
|
||||
<div class="test-name">휴무일 슬롯</div>
|
||||
<div class="test-value"><?php echo number_format($status['holiday_slots']); ?>개</div>
|
||||
<div class="test-status status-ok">정상</div>
|
||||
</div>
|
||||
|
||||
<?php elseif (isset($test_results['schedule_error'])): ?>
|
||||
<div class="test-item">
|
||||
<div class="test-name">스케줄 조회 오류</div>
|
||||
<div class="test-value"><?php echo htmlspecialchars($test_results['schedule_error']); ?></div>
|
||||
<div class="test-status status-error">오류</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- 액션 버튼 -->
|
||||
<div class="test-section">
|
||||
<h3>🚀 빠른 액션</h3>
|
||||
<div style="text-align: center;">
|
||||
<?php if (!is_consultant_installed()): ?>
|
||||
<a href="../install.php" class="btn btn-warning">시스템 설치</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="../settings.php" class="btn btn-primary">설정 관리</a>
|
||||
<a href="../schedule_generate.php" class="btn btn-success">스케줄 생성</a>
|
||||
<a href="../dashboard.php" class="btn btn-primary">대시보드</a>
|
||||
|
||||
<button onclick="location.reload()" class="btn btn-secondary">새로고침</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 자동 새로고침 (30초마다)
|
||||
setTimeout(function () {
|
||||
location.reload();
|
||||
}, 30000);
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
include_once('./_common.php');
|
||||
|
||||
if (!$is_admin) {
|
||||
alert('관리자만 접근할 수 있습니다.');
|
||||
}
|
||||
|
||||
echo '<div style="padding:20px; border:1px solid #ddd; background:#fff; max-width:600px; margin:50px auto; text-align:center;">';
|
||||
echo '<h3>알림</h3>';
|
||||
echo '<p>이 파일은 더 이상 사용되지 않습니다.</p>';
|
||||
echo '<p>관리자 페이지 > 상담관리 > <strong>설치/업데이트</strong> 메뉴에서 [재설치 (업데이트)] 버튼을 클릭하여 DB를 업데이트해 주세요.</p>';
|
||||
echo '<a href="install.php" style="display:inline-block; padding:10px 20px; background:#007bff; color:#fff; text-decoration:none; border-radius:5px;">설치/업데이트 페이지로 이동</a>';
|
||||
echo '</div>';
|
||||
?>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
define('G5_IS_ADMIN', true);
|
||||
include_once('../../common.php');
|
||||
include_once(G5_ADMIN_PATH . '/admin.lib.php');
|
||||
|
||||
$g5['contact_inquiry_table'] = G5_TABLE_PREFIX . 'contact_inquiry';
|
||||
?>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
// 810번대 메뉴 (상담문의관리)
|
||||
if (!isset($menu['menu810'])) $menu['menu810'] = array();
|
||||
|
||||
$menu['menu810'] = array_merge($menu['menu810'], array(
|
||||
array('810000', '상담문의관리', G5_ADMIN_URL.'/contact_inquiry/list.php', 'contact_inquiry_main', 'fa-comments'),
|
||||
array('810100', '상담문의내역', G5_ADMIN_URL.'/contact_inquiry/list.php', 'contact_inquiry_list'),
|
||||
array('810900', '솔루션 설치', G5_ADMIN_URL.'/contact_inquiry/install.php', 'contact_inquiry_install'),
|
||||
));
|
||||
?>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user