Files
egbim_homepage/egbim/skin/qa/basic/descope_qa_write.skin.php
T
2026-07-27 17:23:52 +09:00

659 lines
25 KiB
PHP

<?php
// skin/qa/basic/descope_qa_write.skin.php
// ✅ DB 연결
$pdo = new PDO("mysql:host=db;dbname=egbim;charset=utf8mb4", "egbim", "baron3840!!", [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
// ✅ EG-BIM (product_code=8) 기준 최신 external_version 가져오기
$stmt = $pdo->prepare("
SELECT external_version
FROM deploy_versions
WHERE product_code = 1
ORDER BY updated_at DESC
LIMIT 1
");
$stmt->execute();
$version = $stmt->fetchColumn() ?: '1.0.0'; // fallback
// ✅ 다운로드 URL 자동 생성
$download_url = "https://release-download.hmac.kr/eg-bim/{$version}/setup_eg-bim_{$version}.exe";
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>111</title>
<link rel="stylesheet" href="../js/font-awesome/css/font-awesome.min.css?ver=2303229">
<!-- 디자인팀 공통 -->
<script type="text/javascript" src="/egbim/js/jquery-3.6.1.min.js"></script>
<script type="text/javascript" src="/egbim/js/jquery.mousewheel.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollToPlugin.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/aos@2.3.1/dist/aos.css" type="text/css"/>
<link rel="stylesheet" href="https://unpkg.com/lenis@1.1.9/dist/lenis.css">
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script src="https://unpkg.com/lenis@1.1.9/dist/lenis.min.js"></script>
<script type="text/javascript" src="/egbim/js/popup.js?v=20260723"></script>
<script type="text/javascript" src="/egbim/js/common.js"></script>
<script type="text/javascript" src="/egbim/js/index.js"></script>
<!-- CKEditor 5 (Classic Build) CDN -->
<script src="https://cdn.ckeditor.com/ckeditor5/41.0.0/classic/ckeditor.js"></script>
<link rel="stylesheet" href="/egbim/css/reset.css"/>
<link rel="stylesheet" href="/egbim/css/font.css"/>
<link rel="stylesheet" href="/egbim/css/style.css"/>
</head>
<body>
<div class="wrapper">
<!-- Header -->
<?php include_once(__DIR__ . '/../../member/basic/main_header.skin.php'); ?>
<!-- //Header -->
<div class="container faq">
<section class="intro">
<div class="top js__dark">
<h2>Q&A</h2>
<span>EG-BIM 관련 문의하기</span>
</div>
<ul class="sub_tab">
<li><a href="/egbim/bbs/faq.php">자주하는 질문(FAQ)</a></li>
<li class="on"><a href="/egbim/bbs/descope_qa_list.php">문의하기(Q&A)</a></li>
<li><a href="https://939.co.kr/saman/" target="_blank">원격지원</a></li>
</ul>
</section>
<span class="sub_tit">문의하기(Q&A)</span>
<div class="contents">
<?php if (!empty($errors)): ?>
<div class="qa-error-box" style="margin-bottom:20px;padding:14px 16px;border:1px solid #e66;background:#fff5f5;color:#b42318;line-height:1.6;">
<?= nl2br(htmlspecialchars(implode("\n", $errors), ENT_QUOTES, 'UTF-8')) ?>
</div>
<?php endif; ?>
<?php
$admin = is_qna_admin();
?>
<!-- ★ action 에 ?id= 붙여서 수정/등록 구분 -->
<form id="qaForm"
action="/egbim/bbs/descope_qa_write.php<?= $isEdit ? '?id=' . $post['post_id'] : '' ?>"
method="POST"
enctype="multipart/form-data">
<section class="form-wrap">
<div class="form-area">
<div class="form-group">
<div class="form-item">
<label class="form-tit" for="category">구분</label>
<select class="select-sm" name="category" id="category" required>
<option value="">-- 선택 --</option>
<option value="오류문의" <?= $post['category']=='error' ? 'selected' : '' ?>>오류문의</option>
<option value="개선문의" <?= $post['category']=='improvement' ? 'selected' : '' ?>>개선문의</option>
<option value="일반문의" <?= $post['category']=='general' ? 'selected' : '' ?>>일반문의</option>
<?php if ($admin): ?>
<option value="공지사항" <?= $post['category']=='notice' ? 'selected' : '' ?>>공지사항</option>
<option value="관리글" <?= $post['category']=='admin' ? 'selected' : '' ?>>관리글</option>
<?php endif; ?>
</select>
</div>
<!-- <div class="form-item">
<strong class="form-tit">처리상태</strong>
<?php if (!$isEdit): ?>
<input type="hidden" name="status" value="await">
<?php endif; ?>
<select class="select-sm" name="status" <?= $isEdit ? '' : 'disabled' ?>>
<option value="await" <?= $post['status']==='await' ? 'selected':'' ?>>답변대기</option>
<option value="review" <?= $post['status']==='review' ? 'selected':'' ?>>검토중</option>
<option value="done" <?= $post['status']==='done' ? 'selected':'' ?>>답변완료</option>
</select>
</div> -->
</div>
<div class="form-item">
<strong class="form-tit">제목<span class="require">*</span></strong>
<input type="text" name="title" style="width:100%;" placeholder="제목을 입력하세요"
class="input-text" required
value="<?= htmlspecialchars($post['title'], ENT_QUOTES, 'UTF-8') ?>">
</div>
<div class="form-item">
<strong class="form-tit">비밀글</strong>
<label>
<input type="checkbox" name="secret" value="1" <?= $post['is_secret'] ? 'checked':'' ?>>
비밀글로 등록
</label>
</div>
<div class="form-item">
<label class="form-tit" for="content">내용</label>
<div class="form-col-group">
<textarea id="content" name="content" class="text-area" placeholder="문의 내용을 작성해주세요"><?= $post['content'] ?></textarea>
</div>
</div>
<!--CKeditor script-->
<script>
document.addEventListener("DOMContentLoaded", function() {
const qaForm = document.getElementById("qaForm");
const contentField = document.getElementById("content");
const saveButton = qaForm?.querySelector('.btn-save');
let isSubmitting = false;
const editorReady = ClassicEditor
.create(contentField, {
toolbar: [
'heading', '|',
'bold', 'italic', 'link',
'bulletedList', 'numberedList', '|',
'blockQuote', 'insertTable', 'imageUpload',
'undo', 'redo'
],
language: 'ko',
ckfinder: {
uploadUrl: '/egbim/bbs/descope_qa_img_upload.php'
}
})
.then((editor) => {
window.editor = editor;
return editor;
})
.catch((error) => {
console.error(error);
return null;
});
qaForm?.addEventListener("submit", async function(e) {
e.preventDefault();
if (isSubmitting) return;
const editor = await editorReady;
const editorData = editor ? editor.getData().trim() : contentField.value.trim();
if (!editorData) {
alert("내용을 입력해주세요.");
if (editor) {
editor.editing.view.focus();
} else {
contentField.focus();
}
return;
}
if (typeof syncFileInput === 'function') {
syncFileInput();
}
contentField.value = editorData;
isSubmitting = true;
if (saveButton) saveButton.disabled = true;
qaForm.submit();
});
});
</script>
<div class="form-item">
<label class="form-tit" for="attach">첨부파일</label>
<div class="form-col-group">
<div class="attach-box">
<input type="file" id="attach" name="attach[]" multiple>
<small class="info-msg">* 최대 30MB 이내, exe 같은 실행파일은 직접 업로드할 수 없고 zip 등 압축파일로 첨부해야 합니다.</small>
</div>
<!-- 드래그 앤 드롭 영역 -->
<div id="drop-zone" class="drop-zone">
<p>여기로 파일을 드래그하거나 클릭해서 선택하세요.</p>
</div>
<!-- 업로드 파일 미리보기 -->
<ul id="file-list"></ul>
<?php if ($isEdit): ?>
<?php
// DB에서 해당 글(post_id)의 기존 첨부파일 불러오기
$stmt = $pdo->prepare("SELECT id, ori_name, file_size FROM qa_attachments WHERE post_id = ?");
$stmt->execute([$post['post_id']]);
$files = $stmt->fetchAll();
?>
<?php if (!empty($files)): ?>
<div class="old-attachments">
<p><strong>현재 첨부파일</strong></p>
<ul>
<?php foreach ($files as $file): ?>
<li>
<label>
<input type="checkbox" name="delete_files[]" value="<?= $file['id'] ?>">
<?= htmlspecialchars($file['ori_name']) ?>
(<?= number_format($file['file_size']/1024,1) ?> KB)
<span style="color:red;">[삭제]</span>
</label>
</li>
<?php endforeach ?>
</ul>
</div>
<?php endif ?>
<?php endif ?>
</div>
</div>
</div>
<div class="btn-group">
<button type="button" class="btn btn-cancel" onclick="history.back()">취소</button>
<button type="submit" class="btn btn-save"><?= $isEdit ? '수정 저장' : '작성완료' ?></button>
</div>
</section>
</form>
</div><!-- contents END -->
</div><!-- container END -->
<!-- FOOTER -->
<?php include_once(__DIR__ . '/../../member/basic/main_footer.skin.php'); ?>
<!-- //FOOTER -->
</div><!-- wrapper END -->
<!-- 첨부파일 drag -->
<script>
const dropZone = document.getElementById("drop-zone");
const fileInput = document.getElementById("attach");
const fileList = document.getElementById("file-list");
const allowedExtensions = new Set(['jpg','jpeg','png','gif','pdf','hwp','doc','docx','xls','xlsx','zip','7z','dwg','dwt','dxf','grm','ctb','shx','shp','lin','lsp','reg']);
// 업로드 대기 파일 보관용
let uploadFiles = [];
// 드래그 영역 클릭 → 파일 선택창 열기
dropZone.addEventListener("click", () => fileInput.click());
// 드래그 시 시각 효과
dropZone.addEventListener("dragover", (e) => {
e.preventDefault();
dropZone.classList.add("dragover");
});
dropZone.addEventListener("dragleave", () => {
dropZone.classList.remove("dragover");
});
// 드롭 처리
dropZone.addEventListener("drop", (e) => {
e.preventDefault();
dropZone.classList.remove("dragover");
addFiles(e.dataTransfer.files);
});
// 버튼으로 파일 선택했을 때
fileInput.addEventListener("change", () => {
addFiles(fileInput.files);
});
// 파일 추가 처리
function addFiles(files) {
for (const file of Array.from(files)) {
const ext = (file.name.split('.').pop() || '').toLowerCase();
if (!allowedExtensions.has(ext)) {
alert(`허용되지 않은 확장자입니다: ${file.name}\nexe 파일은 zip 등으로 압축해서 업로드해주세요.`);
continue;
}
if (file.size > 30 * 1024 * 1024) {
alert(`파일 용량이 30MB를 초과합니다: ${file.name}`);
continue;
}
const exists = uploadFiles.some((savedFile) => (
savedFile.name === file.name &&
savedFile.size === file.size &&
savedFile.lastModified === file.lastModified
));
if (!exists) {
uploadFiles.push(file);
}
}
renderFileList();
syncFileInput();
}
// 파일 목록 출력
function renderFileList() {
fileList.innerHTML = "";
uploadFiles.forEach(file => {
const li = document.createElement("li");
li.textContent = `${file.name} (${(file.size/1024).toFixed(1)} KB)`;
fileList.appendChild(li);
});
}
// fileInput.files 에 다시 세팅 (서버로 전송 가능하게)
function syncFileInput() {
const dataTransfer = new DataTransfer();
uploadFiles.forEach(file => dataTransfer.items.add(file));
fileInput.files = dataTransfer.files;
}
</script>
<!-- ===== AOS / Lenis 초기화 (가드) ===== -->
<script>
try{
AOS.init();
if (typeof Lenis !== 'undefined') {
const lenis = new Lenis();
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add((time)=>{ lenis.raf(time * 1000) });
gsap.ticker.lagSmoothing(0);
window.lenis = lenis;
}
} catch(e){ console.error(e); }
</script>
<!-- ===== 공용 유틸 & 헤더/팝업 동작 (list 스킨과 동일) ===== -->
<script>
//공용 유틸
function getQueryString(name){
const intro = document.querySelector('.intro_wrap');
if (intro) intro.style.display = "none";
const mainMask = document.querySelector('.main_mask');
if (mainMask) mainMask.classList.add('skip');
return new URLSearchParams(window.location.search).get(name);
}
// 필터 변경 시 자동 제출
document.querySelectorAll('.qa-controls input[name="writer"], .qa-controls input[name="status[]"]')
.forEach(el=>{
el.addEventListener('change', function(){
this.closest('.qa-controls').submit();
});
});
// Family site 토글
$(function(){
$('.family_btn').on('click', function(e){
e.stopPropagation();
$('.family_list, .family_btn').toggleClass('family_on');
});
$(document).on('click', function(e){
if(!$(e.target).closest('.family_wrap').length){
$('.family_list, .family_btn').removeClass('family_on');
}
});
});
function deleteCookie(name){
document.cookie = name+"=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/egbim;";
document.cookie = name+"=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/egbim/bbs;";
document.cookie = name+"=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/egbim/faq;";
document.cookie = name+"=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/egbim/qa;";
}
function getCookie(cname){
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(';');
for(let i=0;i<ca.length;i++){
let c = ca[i];
while(c.charAt(0)==' '){ c = c.substring(1); }
if(c.indexOf(name) == 0){ return c.substring(name.length, c.length); }
}
return "";
}
// Header 메뉴 표시
function updateHeaderMenu(){
try{
const jwt = sessionStorage.getItem('sessionJwt') || '';
const loginId = sessionStorage.getItem('loginId') || '';
const isLoggedIn = jwt.split('.').length === 3;
const adminList = [
'kjy0426@hanmaceng.co.kr',
'b24014@hanmaceng.co.kr',
'b23065@hanmaceng.co.kr',
'b23008@baroncs.co.kr',
'shyeom1@samaneng.com',
'cjy627@hanmaceng.co.kr',
'b23072@hanmaceng.co.kr',
'b24051@hanmaceng.co.kr',
'rmsgud1202@hanmaceng.co.kr',
'm21318@hanmaceng.co.kr',
'b21367@hanmaceng.co.kr',
'b25023@hanmaceng.co.kr',
'sdi9429@naver.com',
'junsuy@hanmail.net',
'ilphilo92@gmail.com'
];
const isAdmin = adminList.includes(loginId.toLowerCase());
$('.my_join2, .my_login2').toggle(isLoggedIn);
$('.my_join, .my_login').toggle(!isLoggedIn);
$('#my_join2').html(
isLoggedIn && isAdmin
? "<a href='/egbim/adm/index.php' target='_blank'>관리페이지</a>"
: "<a href='javascript:mypage03();'>마이페이지</a>"
);
} catch(e){ console.error(e); }
}
function descopeLogout() {
fetch('/egbim/skin/member/basic/descope_logout.php', {
method: 'POST',
credentials: 'include' // PHPSESSID 쿠키 전송
})
.then(res => {
if (!res.ok) throw new Error('서버 로그아웃 실패');
// 1) 로컬 저장소 정리
// sessionStorage.removeItem("refreshJwt");
// sessionStorage.removeItem("sessionJwt");
// sessionStorage.removeItem("userRole");
// sessionStorage.removeItem("userName");
// sessionStorage.removeItem("loginId");
sessionStorage.clear();
localStorage.clear();
// 2) UI 갱신
updateHeaderMenu();
// 3) 로그인 페이지로 이동
window.location.href = '/egbim/index.php';
})
.catch(err => {
console.error(err);
alert('로그아웃 중 오류가 발생했습니다.');
});
}
$(document).ready(function() {
// 기존에 있던 updateHeaderMenu() 호출 뒤에
$('#my_login2 a').off('click').on('click', function(e){
e.preventDefault();
descopeLogout();
});
});
$(document).ready(function() {
updateHeaderMenu();
// (필요하다면, 로그인 성공 후에도 항상 updateHeaderMenu(); 호출)
});
//descope login
$('#flogin button[type=submit]').click(function(e){
e.preventDefault();
let mb_id = $('#login_id').val();
let mb_password = $('#login_pw').val();
$.ajax({
url : '/egbim/skin/member/basic/descope_login.php',
data : {
'mb_id' : mb_id,
'mb_password' : mb_password
},
type : "POST",
dataType : "json",
success : function(res){
if(res.status === "ok") {
// 토큰 저장
// sessionStorage.setItem("refreshJwt", res.refreshJwt);
sessionStorage.setItem("sessionJwt", res.sessionJwt);
// ✅ 사용자 정보 저장
if (res.user) {
sessionStorage.setItem("loginId", res.user.loginIds?.[0] || '');
sessionStorage.setItem("userName", res.user.name || '');
sessionStorage.setItem("company", res.user.customAttributes?.company || '');
sessionStorage.setItem("familyCompany", res.user.customAttributes?.familyCompany || '');
sessionStorage.setItem("position", res.user.customAttributes?.position || '');
sessionStorage.setItem("team", res.user.customAttributes?.team || '');
sessionStorage.setItem("familyUniqueKey", res.user.customAttributes?.familyUniqueKey || '');
sessionStorage.setItem("phone", res.user.phone || '');
}
// ✅ 추가: 관리자 등 정보 저장
if(res.user && res.user.roleNames && res.user.roleNames[0])
sessionStorage.setItem("userRole", res.user.roleNames[0]);
else
sessionStorage.removeItem("userRole");
// ✅ 로그인 후 메뉴 갱신
updateHeaderMenu();
// UI 처리
$('.popup_wrap').hide();
$('body').css('overflow', '');
lenis.start();
// alert('로그인 하셨습니다.');
} else {
alert(res.message || "로그인 실패");
// 🔴 실패 횟수에 따라 안내 배너 교체
if (res.failCount && res.failCount >= 8) {
updateLoginBanner("fail");
}
}
},
error : function(err){
alert(err.statusText || "통신 오류");
}
});
});
// ✅ 배너 교체 함수
function updateLoginBanner(type = "default") {
const $banner = $('#login_notice_banner');
if (!$banner.length) return;
if (type === "default") {
$banner.css({ background: "#063e01ff", color: "#fff" });
$banner.find("strong").text("[로그인 시스템 변경 안내]");
$banner.find("p.left").html(`
더 나은 보안을 위해 2025년 8월 8일부터 로그인 시스템이 변경되었습니다.<br>
기존 회원 또한 신규 시스템에 맞춰 재가입을 진행해주시기 바랍니다.
`);
$banner.find("p:last").html(`※ 로그인 및 가입 관련 문의: <strong>010-8721-7453</strong>`);
}
if (type === "fail") {
$banner.css({ background: "#6a0202ff", color: "#faf6f6ff" });
$banner.find("strong").text("[로그인 시도 제한 안내]");
$banner.find("p.left").html(`
비밀번호 입력을 <strong>7회 이상 연속 실패</strong>하셨습니다.<br>
<strong>8번째 시도부터 3분 대기</strong>가 적용됩니다.<br>
10회 이상 실패 시 계정이 잠금 처리됩니다.
`);
$banner.find("p:last").text("");
}
}
// // 회원가입 약관동의 (기존 유지)
$(document).on('click', '#fregister button[type=submit]', function(e){
e.preventDefault();
let agree11 = $('#agree11').val();
let agree21 = $('#agree21').val();
$.ajax({
url : '/egbim/bbs/register_form.php',
type: 'POST',
datatype: 'json',
contentType: 'application/json; charset=utf-8',
data: { agree11, agree21 },
success: function(){},
error: function(err){ alert(err); }
});
});
// URL 쿼리 파싱 → 회원가입/비번재설정 팝업 자동 오픈
$(function(){
try{
const popupParam = getQueryString('popup');
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('t');
if (popupParam === 'signup') {
$('#pop_agreement').show();
$('body').css('overflow','hidden');
if (window.lenis) window.lenis.stop();
}
if (popupParam === 'pwreset') {
$('#pop_search').show();
$('body').css('overflow','hidden');
$('#pop_search .popup_contents_wrap').hide();
if (token) {
if (!sessionStorage.getItem('pwreset_shown')) {
$('#password_reset').show();
sessionStorage.setItem('pwreset_shown', '1');
}
} else {
$('#pop_search .popup_contents_wrap').first().show();
}
$('#pop_search .btn_close').on('click', function(){
$('#pop_search').hide();
$('body').css('overflow','');
});
}
} catch(e){ console.error(e); }
});
// 초기 메뉴 상태 반영 + storage 동기화
document.addEventListener('DOMContentLoaded', function(){
updateHeaderMenu();
setTimeout(updateHeaderMenu, 0);
});
window.addEventListener('storage', function(e){
if (e.key === 'sessionJwt' || e.key === 'userRole') updateHeaderMenu();
});
// tmp 쿠키 시나리오 유지
(function(){
try{
let x = document.cookie;
if(x.includes("tmp_1=John")){
deleteCookie("tmp_1");
$('#pop_mypage02').show();
}
} catch(e){ console.error(e); }
})();
</script>
<?php
// Q&A 페이지에서는 Descope 로그인에 필요한 최소 팝업만 포함합니다.
$BASE = $_SERVER['DOCUMENT_ROOT'].'/egbim/skin/member/basic';
$partials = [
'login.skin.php',
'register.skin.php',
'privacy.skin.php',
'password_lost.skin.php',
];
foreach ($partials as $p) {
$full = $BASE.'/'.$p;
if (file_exists($full)) {
include $full;
echo "<!-- INCLUDED: {$full} -->\n"; // 디버그 표시
} else {
echo "<!-- MISSING: {$full} -->\n"; // 경로 문제 확인용
}
}
?>
</body>
</html>
<script>
</script>