https://shooter.dothome.co.kr/bbs/board.php?bo_table=ARCHIVE&wr_id=7

이렇게 나와야하는데 수정해도 틀이 틀어져요...
처음엔 잘나왔다가 소스를 조금 수정했더니 충돌나는건지 자꾸 창이 길게 나옵니다.
view.skin.php 소스

이렇게 나와야하는데 수정해도 틀이 틀어져요...처음엔 잘나왔다가 소스를 조금 수정했더니 충돌나는건지 자꾸 창이 길게 나옵니다.
view.skin.php 소스
Copy
<?php
if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 차단
include_once(G5_LIB_PATH.'/thumbnail.lib.php');
// 0. 강력한 브라우저 캐시 방지
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
// 1. 세션 키 설정
$ss_down_key = 'ss_downloaded_' . $bo_table . '_' . $wr_id;
$ss_name = 'ss_view_password_' . $bo_table . '_' . $wr_id;
// 2. [개선] 실시간 DB 상태를 확인하여 차단 해제 (관리자 제외)
if (!$is_admin && get_session($ss_down_key)) {
// 현재 게시물에서 'private' 처리된 파일이 아직 남아있는지 실시간 확인
$sql = " select count(*) as cnt from {$g5['board_file_table']}
where bo_table = '$bo_table'
and wr_id = '$wr_id'
and bf_content = 'private' ";
$row = sql_fetch($sql);
// 만약 관리자가 모든 사진을 다시 공개로 바꿨다면 (private인 파일이 0개라면)
if ((int)$row['cnt'] === 0) {
// 차단 세션을 제거하여 재접속 허용
set_session($ss_down_key, '');
} else {
// 아직 비공개(다운로드 완료) 상태인 사진이 있다면 계속 차단
echo "<script>alert('보안 정책상 이미 다운로드한 앨범에는 다시 접근할 수 없습니다.'); location.replace('".G5_URL."');</script>";
exit;
}
}
// --- [공통] 사진 상태 변경 및 다운로드 완료 세션 처리 ---
if (isset($_POST['action']) && $_POST['action'] == 'toggle_privacy') {
$bf_no = (int)$_POST['bf_no'];
$target_status = $_POST['status'];
$val = ($target_status == 'private') ? 'private' : '';
if (!$is_admin && $target_status == 'private') {
set_session($ss_down_key, true);
}
sql_query(" update {$g5['board_file_table']} set bf_content = '$val' where bo_table = '$bo_table' and wr_id = '$wr_id' and bf_no = '$bf_no' ");
exit;
}
// [관리자] 선택 삭제 처리
if ($is_admin && isset($_POST['btn_delete_selected'])) {
$del_files = $_POST['chk_file_idx'];
if (is_array($del_files)) {
foreach ($del_files as $f_idx) {
$f_idx = (int)$f_idx;
sql_query(" delete from {$g5['board_file_table']} where bo_table = '$bo_table' and wr_id = '$wr_id' and bf_no = '$f_idx' ");
}
}
goto_url("./board.php?bo_table=$bo_table&wr_id=$wr_id");
}
// 나가기(잠금해제)
if (isset($_GET['unlock']) && $_GET['unlock'] == 'y') {
set_session($ss_name, '');
header("Location: " . G5_URL);
exit;
}
$is_unlocked = ($is_admin || empty($view['wr_1']) || get_session($ss_name)) ? true : false;
if (isset($_POST['view_pwd'])) {
if (!empty($view['wr_1']) && $_POST['view_pwd'] === $view['wr_1']) {
set_session($ss_name, true);
$is_unlocked = true;
} else {
echo "<script>alert('비밀번호가 일치하지 않습니다.');</script>";
}
}
if (!$is_unlocked) {
?>
<style>
.pwd-lock-screen { position: fixed; inset: 0; background: #f8f9fa; display: flex; align-items: center; justify-content: center; z-index: 10000; font-family: 'Pretendard', sans-serif; }
.pwd-box { background: #fff; padding: 50px 30px; border-radius: 20px; box-shadow: 0 15px 35px rgba(0,0,0,0.08); text-align: center; width: 90%; max-width: 400px; }
.pwd-box h2 { margin-bottom: 10px; font-size: 18px; font-weight: 700; }
.pwd-input { width: 100%; padding: 18px; border: 1px solid #e2e8f0; border-radius: 12px; margin-bottom: 15px; text-align: center; font-size: 16px; outline: none; box-sizing: border-box; }
.pwd-submit { width: 100%; padding: 18px; background: #000; color: #fff; border: none; border-radius: 12px; cursor: pointer; font-size: 17px; font-weight: 600; }
</style>
<div class="pwd-lock-screen"><div class="pwd-box"><h2><?php echo get_text($view['wr_subject']); ?></h2><p>행사에서 안내받은 비밀번호를 입력해주세요.</p><form method="post" autocomplete="off"><input type="password" name="view_pwd" class="pwd-input" placeholder="Password" required autofocus><button type="submit" class="pwd-submit">확인</button></form></div></div>
<?php return; }
// --- 파일 데이터 처리 ---
$all_valid_files = array();
if (isset($view['file']) && is_array($view['file'])) {
foreach ($view['file'] as $i => $file) {
if (isset($file['view']) && $file['view']) {
$is_file_private = ($file['bf_content'] == 'private');
if (!$is_admin && $is_file_private) continue;
$file['orig_idx'] = $i;
$file['is_private'] = $is_file_private;
$all_valid_files[] = $file;
}
}
}
?>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/default-skin/default-skin.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<style>
/* 펌 방지 및 기본 폰트 */
body { -webkit-user-select: none !important; user-select: none !important; -webkit-touch-callout: none !important; margin: 0; font-family: 'Pretendard', sans-serif; background: #fff; }
img { -webkit-user-drag: none !important; pointer-events: none !important; }
/* 레이아웃 */
.view-top-bar { display: flex; justify-content: space-between; align-items: center; padding: 12px 20px; border-bottom: 1px solid #eee; position: sticky; top: 0; background: #fff; z-index: 1001; }
.view-top-bar h1 { font-size: 18px; margin: 0; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 50%; }
.top-right-btns { display: flex; gap: 5px; align-items: center; }
.top-btn { font-size: 20px; color: #444; background: none; border: 0; cursor: pointer; padding: 8px; text-decoration: none; border-radius: 8px; display: flex; align-items: center; justify-content: center; transition: 0.2s; }
.top-btn:hover { background: #f5f5f5; }
.top-btn.active-mode { color: #2563eb; background: #f0f4ff; }
.gallery-container { padding: 15px; max-width: 1400px; margin: 0 auto; }
.masonry-grid { column-count: 4; column-gap: 12px; }
.grid-item { display: inline-block; width: 100%; margin-bottom: 12px; break-inside: avoid; position: relative; border-radius: 8px; overflow: hidden; background: #f0f0f0; }
@media (max-width: 768px) { .masonry-grid { column-count: 2 !important; } .view-top-bar h1 { font-size: 16px; } }
.img-wrapper { position: relative; border: 4px solid transparent; transition: 0.2s; }
.img-wrapper.selected { border-color: #2563eb; }
.img-wrapper img { width: 100%; display: block; height: auto; }
.click-layer { position: absolute; inset: 0; z-index: 10; cursor: pointer; }
/* 관리자 전용 스타일 */
.private-item { opacity: 0.5; filter: grayscale(1); }
.admin-status-badge { position: absolute; top: 8px; right: 8px; z-index: 20; background: rgba(255,255,255,0.95); width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; border: none; box-shadow: 0 2px 5px rgba(0,0,0,0.15); pointer-events: auto !important; }
/* 보안 모달 */
.security-modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 20000; align-items: center; justify-content: center; padding: 20px; }
.security-modal.active { display: flex; }
.modal-content { background: #fff; width: 100%; max-width: 450px; border-radius: 20px; overflow: hidden; animation: modalShow 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28); }
@keyframes modalShow { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
.modal-header { padding: 30px 20px 10px; text-align: center; }
.modal-header i { color: #2563eb; font-size: 32px; margin-bottom: 15px; display: block; }
.modal-header h3 { margin: 0; font-size: 20px; font-weight: 800; }
.modal-body { padding: 10px 25px; }
.check-list { list-style: none; padding: 15px 0; margin: 0; }
.check-item { display: flex; justify-content: space-between; gap: 12px; margin-bottom: 15px; cursor: pointer; padding: 12px; border-radius: 10px; background: #f8fafc; border: 1px solid transparent; transition: 0.2s; }
.check-item.checked { border-color: #cbd5e1; background: #f1f5f9; }
.check-text { font-size: 14px; color: #475569; line-height: 1.4; word-break: keep-all; }
.check-box-ui { width: 20px; height: 20px; border: 2px solid #cbd5e1; border-radius: 5px; flex-shrink: 0; position: relative; background: #fff; transition: 0.2s; }
.check-item.checked .check-box-ui { background: #2563eb; border-color: #2563eb; }
.check-item.checked .check-box-ui::after { content: '✓'; color: #fff; position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 12px; }
.modal-footer { padding: 10px 25px 30px; }
.btn-confirm-dw { width: 100%; padding: 18px; background: #e2e8f0; color: #94a3b8; border: none; border-radius: 12px; font-size: 16px; font-weight: 700; cursor: not-allowed; transition: 0.3s; }
.btn-confirm-dw.ready { background: #2563eb; color: #fff; cursor: pointer; box-shadow: 0 4px 12px rgba(37, 99, 235, 0.2); }
.pswp__button--custom { background: none !important; color: #fff; font-size: 20px !important; line-height: 44px; text-align: center; width: 44px; float: right; opacity: 0.8; }
</style>
<script>
window.addEventListener('contextmenu', e => e.preventDefault());
window.addEventListener('keydown', e => {
if (e.ctrlKey && (e.keyCode === 67 || e.keyCode === 86 || e.keyCode === 85 || e.keyCode === 83 || e.keyCode === 73)) e.preventDefault();
if (e.keyCode === 123) e.preventDefault();
});
</script>
<div id="res_view_skin">
<form name="fview" id="fview" method="post">
<input type="hidden" name="btn_delete_selected" value="1">
<div class="view-top-bar">
<h1><?php echo get_text($view['wr_subject']); ?></h1>
<div class="top-right-btns">
<?php if ($is_admin) { ?>
<a href="<?php echo G5_BBS_URL; ?>/write.php?w=u&bo_table=<?php echo $bo_table; ?>&wr_id=<?php echo $wr_id; ?>" class="top-btn" title="사진 추가/수정"><i class="fa-solid fa-plus"></i></a>
<button type="button" id="toggle-select-btn" onclick="toggleSelectMode();" class="top-btn" title="선택모드"><i class="fa-solid fa-square-check"></i></button>
<button type="button" id="delete-selected-btn" onclick="deleteSelected();" class="top-btn" style="display:none; color:#ef4444;" title="선택삭제"><i class="fa-solid fa-trash-can"></i></button>
<button type="button" onclick="toggleSelectAll();" class="top-btn" title="전체선택"><i class="fa-solid fa-list-check"></i></button>
<button type="button" onclick="downloadZipBrowser();" class="top-btn" title="전체 압축다운로드"><i class="fa-solid fa-file-zipper" style="color:#2563eb;"></i></button>
<?php } ?>
<a href="?bo_table=<?php echo $bo_table; ?>&wr_id=<?php echo $wr_id; ?>&unlock=y" class="top-btn" title="나가기"><i class="fa-solid fa-right-from-bracket"></i></a>
</div>
</div>
<div class="gallery-container">
<div class="masonry-grid">
<?php foreach ($all_valid_files as $idx => $file) {
preg_match('/src="([^"]+)"/', $file['view'], $match);
$img_url = $match[1];
?>
<div class="grid-item <?php echo $file['is_private'] ? 'private-item' : ''; ?>">
<?php if ($is_admin) { ?>
<button type="button" class="admin-status-badge" onclick="adminTogglePrivacy(<?php echo $file['orig_idx']; ?>, '<?php echo $file['is_private'] ? 'public' : 'private'; ?>')" style="color:<?php echo $file['is_private'] ? '#ef4444' : '#2563eb'; ?>"><i class="fa-solid <?php echo $file['is_private'] ? 'fa-eye-slash' : 'fa-eye'; ?>"></i></button>
<?php } ?>
<div class="click-layer" onclick="handleAction(<?php echo $idx; ?>, this, event);"></div>
<div class="img-wrapper">
<img src="<?php echo $img_url; ?>" class="gallery-img" data-bfno="<?php echo $file['orig_idx']; ?>">
<input type="checkbox" name="chk_file_idx[]" value="<?php echo $file['orig_idx']; ?>" class="file-chk" style="display:none;">
<input type="checkbox" name="chk_file_url[]" value="<?php echo $img_url; ?>" class="url-chk" style="display:none;">
</div>
</div>
<?php } ?>
</div>
</div>
</form>
</div>
<div id="dw_modal" class="security-modal">
<div class="modal-content">
<div class="modal-header"><i class="fa-solid fa-shield-halved"></i><h3>사진 다운로드 보안 동의</h3></div>
<div class="modal-body">
<div class="check-list">
<div class="check-item" onclick="toggleCheck(this)"><div class="check-text">본 사진의 다운로드는 1회만 제공되며, 본 사진은 비공개 및 완료 즉시 앨범 재접속이 영구 차단됩니다.</div><div class="check-box-ui"></div></div>
<div class="check-item" onclick="toggleCheck(this)"><div class="check-text">무단 배포 및 상업적 이용 금지에 동의하며, 위반 시 법적 책임을 질 수 있음을 확인합니다.</div><div class="check-box-ui"></div></div>
</div>
</div>
<div class="modal-footer"><button type="button" id="btn_dw_start" class="btn-confirm-dw" onclick="executeDownload()">동의 및 다운로드</button></div>
</div>
</div>
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true" style="z-index:9999;">
<div class="pswp__bg"></div>
<div class="pswp__scroll-wrap">
<div class="pswp__container"><div class="pswp__item"></div><div class="pswp__item"></div><div class="pswp__item"></div></div>
<div class="pswp__ui pswp__ui--hidden">
<div class="pswp__top-bar">
<div class="pswp__counter"></div>
<button class="pswp__button pswp__button--close" title="닫기 (Esc)"></button>
<button class="pswp__button pswp__button--fs" title="전체화면"></button>
<button class="pswp__button pswp__button--zoom" title="확대/축소"></button>
<button class="pswp__button pswp__button--custom" id="btn-pswp-dw" title="다운로드"><i class="fa-solid fa-download"></i></button>
<button class="pswp__button pswp__button--custom" id="btn-pswp-play" title="슬라이드쇼"><i class="fa-solid fa-play"></i></button>
<div class="pswp__preloader"><div class="pswp__preloader__icn"><div class="pswp__preloader__cut"><div class="pswp__preloader__donut"></div></div></div></div>
</div>
<button class="pswp__button pswp__button--arrow--left" title="이전"></button>
<button class="pswp__button pswp__button--arrow--right" title="다음"></button>
<div class="pswp__caption"><div class="pswp__caption__center"></div></div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script>
let gallery = null;
let playTimer = null;
let isSelectMode = false;
const isAdmin = <?php echo $is_admin ? 'true' : 'false'; ?>;
function toggleSelectMode() {
isSelectMode = !isSelectMode;
document.getElementById('toggle-select-btn').classList.toggle('active-mode', isSelectMode);
document.getElementById('delete-selected-btn').style.display = isSelectMode ? 'flex' : 'none';
}
function deleteSelected() {
const checked = document.querySelectorAll('.file-chk:checked').length;
if (checked === 0) return alert("삭제할 사진을 선택하세요.");
if (confirm("선택한 " + checked + "장의 사진을 삭제하시겠습니까?")) document.getElementById('fview').submit();
}
function toggleSelectAll() {
if(!isSelectMode) toggleSelectMode();
const all = document.querySelectorAll('.img-wrapper');
const isNowAll = document.querySelectorAll('.img-wrapper.selected').length === all.length;
all.forEach(el => {
el.classList.toggle('selected', !isNowAll);
el.querySelectorAll('input[type="checkbox"]').forEach(chk => chk.checked = !isNowAll);
});
}
function handleAction(index, layer, e) {
if (isAdmin && isSelectMode) {
const wrapper = layer.nextElementSibling;
const chk = wrapper.querySelector('.file-chk');
chk.checked = !chk.checked;
wrapper.classList.toggle('selected', chk.checked);
} else {
openPhotoSwipe(index);
}
}
async function adminTogglePrivacy(bf_no, status) {
if(!confirm("사진의 공개 상태를 변경하시겠습니까?")) return;
const formData = new FormData();
formData.append('action', 'toggle_privacy'); formData.append('bf_no', bf_no); formData.append('status', status);
await fetch(window.location.href, { method: 'POST', body: formData });
location.reload();
}
function toggleCheck(el) {
el.classList.toggle('checked');
const allChecked = document.querySelectorAll('.check-item.checked').length === 2;
document.getElementById('btn_dw_start').classList.toggle('ready', allChecked);
}
function openPhotoSwipe(index) {
const pswpElement = document.querySelectorAll('.pswp')[0];
const items = [];
// 이미지 사이즈 동적 계산 로직 추가
document.querySelectorAll('.gallery-img').forEach(img => {
// 자연 크기(naturalWidth)가 없으면 화면 비율에 맞는 기본값 설정
const w = img.naturalWidth || 1200;
const h = img.naturalHeight || 1600;
items.push({
src: img.src,
w: w,
h: h,
bf_no: img.getAttribute('data-bfno'),
msrc: img.src
});
});
const options = {
index: index,
history: false,
bgOpacity: 0.95,
showHideOpacity: true,
shareEl: false,
fullscreenEl: true,
zoomEl: true,
// 사진이 화면에 꽉 차게 나오도록 스케일 모드 조정
getThumbBoundsFn: function(index) {
const thumbnail = document.querySelectorAll('.gallery-img')[index];
const pageYScroll = window.pageYOffset || document.documentElement.scrollTop;
const rect = thumbnail.getBoundingClientRect();
return {x:rect.left, y:rect.top + pageYScroll, w:rect.width};
}
};
gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);
// 다운로드 버튼 이벤트
document.getElementById('btn-pswp-dw').onclick = (e) => {
e.stopPropagation();
if (isAdmin) {
const a = document.createElement('a');
a.href = gallery.currItem.src;
a.download = 'admin_download.jpg';
a.click();
} else {
document.getElementById('dw_modal').classList.add('active');
}
};
// 슬라이드쇼 재생 이벤트
document.getElementById('btn-pswp-play').onclick = function(e) {
e.stopPropagation();
const icon = this.querySelector('i');
if (!playTimer) {
playTimer = setInterval(() => { gallery.next(); }, 3500);
icon.className = 'fa-solid fa-pause';
} else {
clearInterval(playTimer); playTimer = null;
icon.className = 'fa-solid fa-play';
}
};
gallery.listen('close', () => { if(playTimer) { clearInterval(playTimer); playTimer = null; } });
gallery.init();
}
async function executeDownload() {
if (!document.getElementById('btn_dw_start').classList.contains('ready')) return;
const bf_no = gallery.currItem.bf_no;
const url = gallery.currItem.src;
const formData = new FormData();
formData.append('action', 'toggle_privacy');
formData.append('bf_no', bf_no);
formData.append('status', 'private');
await fetch(window.location.href, { method: 'POST', body: formData });
const a = document.createElement('a');
a.href = url; a.download = 'shooter_photo.jpg'; a.click();
alert("다운로드가 완료되었습니다.\n보안 정책에 따라 앨범 접근이 차단됩니다.");
location.replace("<?php echo G5_URL; ?>");
}
async function downloadZipBrowser() {
const selected = document.querySelectorAll('.url-chk:checked');
if (selected.length === 0) return alert("압축할 사진을 선택해주세요.");
const zip = new JSZip();
const total = selected.length;
for (let i = 0; i < total; i++) {
try {
const resp = await fetch(selected[i].value);
const blob = await resp.blob();
zip.file(`photo_${i+1}.jpg`, blob);
} catch (e) {
console.error("파일 로드 실패:", selected[i].value);
}
}
zip.generateAsync({type:"blob"}).then(c => {
saveAs(c, "album_archive.zip");
});
}
</script>
답변 1개 / 댓글 4개
채택된 답변
+20 포인트
2026-04-27 (월) 15:49:12
height 을 주되지 안을까요?
답변에 대한 댓글 4개
nanase
2026-04-27 (월) 17:22:57
max-height 설정 및 .modal. header ,i , h3 , body 전체수정해서 처음부터 다시 수정했더니 잡혔네요...
.modal-header { padding: 30px 20px 10px; text-align: center; }
.modal-header i { color: #2563eb; font-size: 32px; margin-bottom: 15px; display: block; }
.modal-header h3 { margin: 0; font-size: 20px; font-weight: 800; }
.modal-body { padding: 10px 25px; }
수정후
.modal-header { display: flex !important; flex-direction: column !important; align-items: center !important; justify-content: center !important; padding: 40px 20px 20px !important; height: auto !important; position: relative !important; }
.modal-header i { position: static !important; display: block !important; font-size: 45px !important; color: #2563eb; margin-bottom: 15px !important; }
.modal-header h3 { position: static !important; display: block !important; margin: 0 !important; font-size: 22px !important; font-weight: 800; color: #000; line-height: 1.2 !important; }
.modal-body { position: static !important; padding: 10px 25px !important; }
.modal-header { padding: 30px 20px 10px; text-align: center; }
.modal-header i { color: #2563eb; font-size: 32px; margin-bottom: 15px; display: block; }
.modal-header h3 { margin: 0; font-size: 20px; font-weight: 800; }
.modal-body { padding: 10px 25px; }
수정후
.modal-header { display: flex !important; flex-direction: column !important; align-items: center !important; justify-content: center !important; padding: 40px 20px 20px !important; height: auto !important; position: relative !important; }
.modal-header i { position: static !important; display: block !important; font-size: 45px !important; color: #2563eb; margin-bottom: 15px !important; }
.modal-header h3 { position: static !important; display: block !important; margin: 0 !important; font-size: 22px !important; font-weight: 800; color: #000; line-height: 1.2 !important; }
.modal-body { position: static !important; padding: 10px 25px !important; }
2026-04-27 (월) 22:10:29
파이팅
답변을 작성하려면 로그인이 필요합니다.