테스트 사이트 - 개발 중인 베타 버전입니다

고수님 도와주세요. ㅠㅠ 채택완료

4455 3개월 전 조회 792

저는 제미나이한테 조직도를 만들어 달라고 부탁했는데 잘 만들어서 디비에 저장까지 되는 구현했는데 문제는 ㅠㅠ 헤더와 푸터가 제대로 못 불러옵니다. 

그림보는 바 같이 이렇게 됩니다. 자기 말론 인코딩이 못 불러와서 이렇게 된다고 하더군요 저도 코딩 지식이 기본 밖에 모르는 사람이라 저거 때문에 몇시간째 수정하고 있는데도 안되어서 결국엔 여기까지 와서 질문을 남깁니다. ㅠㅠㅠ

http://sir.kr/data/editor/2507/3067840266_1752458343.3524.png" width="100%" />

 

제 코드는 

<?php
// PHP 에러 표시 (문제 해결 후 반드시 제거하거나 Off로 설정하세요)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

// 그누보드 핵심 파일 로드 시도
// !!!!!!!!!! 이 부분을 강세경님의 실제 _common.php 파일의 절대 경로로 수정해주세요 !!!!!!!!!!!!!
// 예시: $common_path = '/var/services/web/ksk507/_common.php';
// 예시: $common_path = '/volume1/web/그누보드설치폴더/_common.php';
$common_path = '/var/services/web/ksk507/_common.php'; // <-- 이 부분을 강세경님의 실제 경로로 변경하세요!

// 이 아래는 수정할 필요 없습니다.
if (!file_exists($common_path)) {
    echo "<h1 style='color:red; text-align:center;'>오류: 그누보드 핵심 파일을 찾을 수 없습니다.</h1>";
    echo "<p style='color:red; text-align:center;'>확인된 경로: '".$common_path."'</p>";
    echo "<p style='color:red; text-align:center;'>_common.php 파일이 해당 경로에 존재하는지 확인해주세요.</p>";
    echo "<p style='color:red; text-align:center;'>'timeline.php' 파일의 코드를 열어 '$common_path = ...' 부분을 실제 _common.php 경로로 수정해야 합니다.</p>";
    exit;
}

include_once($common_path);

// MySQL 연결 문자셋을 UTF-8로 강제 설정
if (function_exists('sql_query')) {
    sql_query("SET NAMES utf8mb4");
}

// HTML 응답의 문자 인코딩을 UTF-8로 명시적으로 설정
header('Content-Type: text/html; charset=UTF-8');
mb_internal_encoding("UTF-8");
mb_regex_encoding("UTF-8");
ini_set('default_charset', 'UTF-8');

// 이 시점부터는 G5_PATH 상수가 정의되어 있으므로 안전하게 사용할 수 있습니다.
$g5['title'] = '강세경님의 연혁 관리'; // 페이지 제목 설정
include_once(G5_PATH.'/head.php'); // 그누보드 헤더 포함 (G5_PATH 사용)

// ----------------------------------------------------
// 데이터 처리 로직 (POST 요청 처리)
// ----------------------------------------------------

// 연혁 추가/수정 처리
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $mode = isset($_POST['mode']) ? $_POST['mode'] : '';
    $tl_id = isset($_POST['tl_id']) ? (int)$_POST['tl_id'] : 0;
    $tl_year = isset($_POST['timelineYear']) ? sql_real_escape_string(trim($_POST['timelineYear'])) : '';
    $tl_month = isset($_POST['timelineMonth']) ? sql_real_escape_string(trim($_POST['timelineMonth'])) : '';
    $tl_day = isset($_POST['timelineDay']) ? sql_real_escape_string(trim($_POST['timelineDay'])) : '';
    $tl_description = isset($_POST['timelineDescription']) ? sql_real_escape_string(trim($_POST['timelineDescription'])) : '';

    // 필수 필드 유효성 검사
    if (!$tl_year || !$tl_month || !$tl_day || !$tl_description) {
        alert("연도, 월, 일, 내용을 모두 입력해주세요.");
        exit;
    }

    // 모드에 따른 데이터베이스 작업
    if ($mode == 'insert') {
        $sql = " INSERT INTO g5_timeline SET
                    tl_year = '{$tl_year}',
                    tl_month = '{$tl_month}',
                    tl_day = '{$tl_day}',
                    tl_description = '{$tl_description}',
                    tl_datetime = NOW() ";
        sql_query($sql);
        alert("연혁이 추가되었습니다.", './timeline.php'); // 추가 후 현재 페이지로 리다이렉트
    } else if ($mode == 'update' && $tl_id) {
        $sql = " UPDATE g5_timeline SET
                    tl_year = '{$tl_year}',
                    tl_month = '{$tl_month}',
                    tl_day = '{$tl_day}',
                    tl_description = '{$tl_description}'
                 WHERE tl_id = '{$tl_id}' ";
        sql_query($sql);
        alert("연혁이 수정되었습니다.", './timeline.php'); // 수정 후 현재 페이지로 리다이렉트
    }
}

// 연혁 삭제 처리 (GET 요청)
if (isset($_GET['mode']) && $_GET['mode'] == 'delete') {
    $tl_id = isset($_GET['tl_id']) ? (int)$_GET['tl_id'] : 0;

    if ($tl_id) {
        sql_query(" DELETE FROM g5_timeline WHERE tl_id = '{$tl_id}' ");
        alert("연혁이 삭제되었습니다.", './timeline.php'); // 삭제 후 현재 페이지로 리다이렉트
    } else {
        alert("잘못된 접근입니다.", './timeline.php');
    }
}

// ----------------------------------------------------
// HTML 및 JavaScript 출력
// ----------------------------------------------------
?>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?php echo $g5['title']; ?></title>
    <style>
        /* CSS 코드는 변경 없이 그대로 붙여넣으세요 */
        body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; margin: 0; padding: 20px; background-color: #f4f7f6; }
        .container { max-width: 900px; margin: 0 auto; padding: 20px; background-color: #fff; box-shadow: 0 0 15px rgba(0,0,0,0.05); border-radius: 8px; }
        .section-heading { text-align: center; font-size: 2.2em; color: #444; margin-bottom: 50px; font-weight: 600; margin-top: 20px; }
        .timeline { position: relative; padding: 0; margin: 0; }
        .timeline::before { content: ''; position: absolute; width: 2px; background-color: #ddd; top: 0; bottom: 0; left: 50%; transform: translateX(-50%); z-index: 1; }
        .timeline-item::before { content: ''; position: absolute; width: 14px; height: 14px; border-radius: 50%; background-color: #5bc0de; border: 2px solid #fff; top: 5px; left: 50%; transform: translateX(-50%); z-index: 3; }
        .timeline-item { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 60px; position: relative; z-index: 2; width: 100%; }
        .timeline-year { flex-basis: 45%; padding-right: 40px; font-size: 1.8em; font-weight: 700; color: #34495e; padding-top: 0px; text-align: right; }
        .timeline-content { flex-basis: 45%; padding-left: 40px; text-align: left; }
        .timeline-content h3 { font-size: 1.2em; color: #444; margin-top: 0; margin-bottom: 10px; }
        .timeline-content ul { list-style: none; padding: 0; margin: 0; }
        .timeline-content ul li { margin-bottom: 8px; font-size: 0.95em; color: #555; display: flex; align-items: flex-start; position: relative; padding-right: 120px; }
        .timeline-content .event-date-detail { font-weight: bold; color: #5bc0de; flex-shrink: 0; order: 2; text-align: right; min-width: 50px; margin-left: auto; padding-left: 10px; }
        .timeline-content .event-description { flex-grow: 1; order: 1; }
        .timeline-item .action-buttons { position: absolute; top: 0px; right: 0px; display: flex; gap: 5px; z-index: 10; }
        .timeline-item .action-buttons button { padding: 5px 10px; border: none; border-radius: 4px; cursor: pointer; font-size: 0.8em; transition: background-color 0.2s ease; }
        .timeline-item .action-buttons .edit-btn { background-color: #f0ad4e; color: white; }
        .timeline-item .action-buttons .edit-btn:hover { background-color: #ec971f; }
        .timeline-item .action-buttons .delete-btn { background-color: #d9534f; color: white; }
        .timeline-item .action-buttons .delete-btn:hover { background-color: #c9302c; }
        .timeline-item:nth-child(even) .timeline-year { order: 2; text-align: left; padding-left: 40px; padding-right: 0; }
        .timeline-item:nth-child(even) .timeline-content { order: 1; text-align: right; padding-right: 40px; padding-left: 0; }
        .timeline-item:nth-child(even) .timeline-content .event-date-detail { order: 1; margin-left: 0; margin-right: auto; text-align: left; padding-right: 10px; padding-left: 0; }
        .timeline-content .event-description { order: 2; }
        .timeline-item:nth-child(even) .action-buttons { left: 0px; right: auto; }
        @media (max-width: 768px) {
            .timeline::before { left: 20px; transform: translateX(0); }
            .timeline-item { flex-direction: column; align-items: flex-start; margin-left: 20px; }
            .timeline-item::before { left: 20px; transform: translateX(-50%); }
            .timeline-year, .timeline-content { width: 100%; flex-basis: auto; padding: 0; text-align: left !important; }
            .timeline-year { font-size: 1.5em; margin-bottom: 10px; padding-left: 30px; order: 1 !important; }
            .timeline-content { padding-left: 30px; order: 2 !important; }
            .timeline-item:nth-child(even) .timeline-year, .timeline-item:nth-child(even) .timeline-content { padding-left: 30px; padding-right: 0; }
            .timeline-content .event-date-detail { order: 1 !important; margin-left: 0 !important; margin-right: 0 !important; text-align: left !important; padding-left: 0 !important; padding-right: 10px !important; }
            .timeline-content .event-description { order: 2 !important; }
            .timeline-item .action-buttons { position: static; margin-top: 10px; margin-left: 30px; justify-content: flex-start; }
            .timeline-item:nth-child(even) .action-buttons { left: auto; right: auto; }
        }
        .add-timeline-form { background-color: #f0f8ff; padding: 25px; border-radius: 8px; margin-bottom: 40px; border: 1px solid #e0f2f7; }
        .add-timeline-form h3 { color: #34495e; margin-top: 0; margin-bottom: 20px; }
        .add-timeline-form label { display: block; margin-bottom: 8px; font-weight: 600; color: #34495e; }
        .add-timeline-form input[type="text"], .add-timeline-form textarea, .add-timeline-form select { width: calc(100% - 20px); padding: 10px; margin-bottom: 15px; border: 1px solid #ccc; border-radius: 4px; font-size: 1em; box-sizing: border-box; background-color: #fff; appearance: none; -webkit-appearance: none; -moz-appearance: none; background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23000000%22%20d%3D%22M287%2C114.1L146.2%2C292.4L5.5%2C114.1H287z%22%2F%2F%3E%3C%2Fsvg%3E'); background-repeat: no-repeat; background-position: right 10px center; background-size: 12px; }
        .date-select-group { display: flex; gap: 10px; margin-bottom: 15px; }
        .date-select-group select { flex: 1; width: auto; margin-bottom: 0; }
        .add-timeline-form textarea { resize: vertical; min-height: 60px; }
        .add-timeline-form button#addTimelineBtn, .add-timeline-form button#updateTimelineBtn, .add-timeline-form button#cancelEditBtn { background-color: #5bc0de; color: white; padding: 12px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 1.1em; font-weight: 600; transition: background-color 0.2s ease; margin-right: 10px; }
        .add-timeline-form button#addTimelineBtn:hover, .add-timeline-form button#updateTimelineBtn:hover { background-color: #46b8da; }
        .add-timeline-form button#cancelEditBtn { background-color: #6c757d; }
        .add-timeline-form button#cancelEditBtn:hover { background-color: #5a6268; }
        .add-timeline-form button#updateTimelineBtn, .add-timeline-form button#cancelEditBtn { display: none; }
    </style>
</head>
<body>

    <div class="container">
        <h2 class="section-heading">연혁</h2>

        <div class="add-timeline-form">
            <h3 id="formTitle">새로운 연혁 추가</h3>
            <form id="timelineForm" method="post" action="./timeline.php">
                <input type="hidden" name="mode" id="formMode" value="insert">
                <input type="hidden" name="tl_id" id="formTlId" value="">

                <label for="timelineYear">연도:</label>
                <select id="timelineYear" name="timelineYear"></select>
                
                <label>날짜:</label>
                <div class="date-select-group">
                    <select id="timelineMonth" name="timelineMonth"></select>
                    <select id="timelineDay" name="timelineDay"></select>
                </div>

                <label for="timelineDescription">내용:</label>
                <textarea id="timelineDescription" name="timelineDescription" placeholder="연혁 내용을 입력하세요"></textarea>
                
                <button type="submit" id="addTimelineBtn">연혁 추가</button>
                <button type="submit" id="updateTimelineBtn">수정 완료</button>
                <button type="button" id="cancelEditBtn">수정 취소</button>
            </form>
        </div>

        <div class="timeline" id="timelineContainer">
            <?php
            // 데이터베이스에서 연혁 데이터 불러오기 (최신순으로 정렬)
            $sql = " SELECT * FROM g5_timeline ORDER BY tl_year DESC, tl_month DESC, tl_day DESC ";
            $result = sql_query($sql);

            $item_index = 0; // 지그재그 클래스를 위한 인덱스
            if ($result && function_exists('sql_num_rows') && sql_num_rows($result) > 0) {
                while ($row = sql_fetch_array($result)) {
                    $item_index++;
                    $class_even = ($item_index % 2 == 0) ? ' timeline-item-even' : '';
                    ?>
                    <div class="timeline-item<?php echo $class_even; ?>">
                        <div class="timeline-year"><?php echo $row['tl_year']; ?></div>
                        <div class="timeline-content">
                            <ul>
                                <li>
                                    <span class="event-date-detail"><?php echo $row['tl_month'].'.'.$row['tl_day']; ?></span>
                                    <span class="event-description"><?php echo $row['tl_description']; ?></span>
                                    <div class="action-buttons">
                                        <button class="edit-btn"
                                                data-tl_id="<?php echo $row['tl_id']; ?>"
                                                data-year="<?php echo $row['tl_year']; ?>"
                                                data-month="<?php echo $row['tl_month']; ?>"
                                                data-day="<?php echo $row['tl_day']; ?>"
                                                data-description="<?php echo $row['tl_description']; ?>">수정</button>
                                        <button class="delete-btn" data-tl_id="<?php echo $row['tl_id']; ?>">삭제</button>
                                    </div>
                                </li>
                            </ul>
                        </div>
                    </div>
                    <?php
                }
            } else {
                // 데이터가 없거나 쿼리 실패 시에는 아무것도 출력하지 않습니다.
            }
            ?>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const addTimelineBtn = document.getElementById('addTimelineBtn');
            const updateTimelineBtn = document.getElementById('updateTimelineBtn');
            const cancelEditBtn = document.getElementById('cancelEditBtn');
            const formTitle = document.getElementById('formTitle');

            const timelineYearSelect = document.getElementById('timelineYear');
            const timelineMonthSelect = document.getElementById('timelineMonth');
            const timelineDaySelect = document.getElementById('timelineDay');
            const timelineDescriptionInput = document.getElementById('timelineDescription');
            const timelineContainer = document.getElementById('timelineContainer');

            const timelineForm = document.getElementById('timelineForm');
            const formMode = document.getElementById('formMode');
            const formTlId = document.getElementById('formTlId');

            // 연도 셀렉트 박스 동적 생성 함수
            function populateYears() {
                const currentYear = new Date().getFullYear();
                const futureYearsToAdd = 5; // 현재 연도 이후 5년까지 표시
                const startYear = currentYear + futureYearsToAdd;
                const endYear = 1999; // 1999년까지 표시

                timelineYearSelect.innerHTML = ''; // 기존 옵션 제거

                const defaultYearOption = document.createElement('option');
                defaultYearOption.value = "";
                defaultYearOption.textContent = "연도 선택";
                defaultYearOption.selected = true;
                defaultYearOption.disabled = true;
                timelineYearSelect.appendChild(defaultYearOption);

                for (let year = startYear; year >= endYear; year--) {
                    const option = document.createElement('option');
                    option.value = year;
                    option.textContent = year;
                    timelineYearSelect.appendChild(option);
                }
            }

            // 월 셀렉트 박스 동적 생성 함수
            function populateMonths() {
                timelineMonthSelect.innerHTML = ''; // 기존 옵션 제거

                const defaultMonthOption = document.createElement('option');
                defaultMonthOption.value = "";
                defaultMonthOption.textContent = "월 선택";
                defaultMonthOption.selected = true;
                defaultMonthOption.disabled = true;
                timelineMonthSelect.appendChild(defaultMonthOption);

                for (let month = 1; month <= 12; month++) {
                    const option = document.createElement('option');
                    option.value = String(month).padStart(2, '0'); // 01, 02 형식으로
                    option.textContent = month + '월';
                    timelineMonthSelect.appendChild(option);
                }
            }

            // 일 셀렉트 박스 동적 생성 함수 (월에 따라 일수가 달라지는 로직 포함)
            function populateDays(year, month, selectedDay = null) {
                timelineDaySelect.innerHTML = ''; // 기존 옵션 제거

                const defaultDayOption = document.createElement('option');
                defaultDayOption.value = "";
                defaultDayOption.textContent = "일 선택";
                defaultDayOption.selected = true;
                defaultDayOption.disabled = true;
                timelineDaySelect.appendChild(defaultDayOption);

                let daysInMonth = 31; // 기본값

                if (year && month) {
                    // 선택된 연도와 월을 기반으로 해당 월의 마지막 날짜를 계산
                    daysInMonth = new Date(year, month, 0).getDate();
                } else {
                    // 월 또는 연도가 선택되지 않은 경우, 임시로 최대 31일까지 표시 (사용자 편의)
                    daysInMonth = 31;
                }

                for (let day = 1; day <= daysInMonth; day++) {
                    const option = document.createElement('option');
                    option.value = String(day).padStart(2, '0'); // 01, 02 형식으로
                    option.textContent = day + '일';
                    if (selectedDay && parseInt(selectedDay) === day) {
                        option.selected = true;
                    }
                    timelineDaySelect.appendChild(option);
                }
            }

            // 월/연도 변경 시 일자 업데이트 이벤트 리스너
            timelineMonthSelect.addEventListener('change', function() {
                const year = timelineYearSelect.value;
                const month = timelineMonthSelect.value;
                populateDays(year, month);
            });
            timelineYearSelect.addEventListener('change', function() {
                const year = timelineYearSelect.value;
                const month = timelineMonthSelect.value;
                populateDays(year, month);
            });

            // 페이지 로드 시 셀렉트 박스 채우기
            populateYears();
            populateMonths();
            populateDays(); // 초기 로드 시에는 기본값으로 채움

            // 폼 초기화 함수
            function resetForm() {
                timelineYearSelect.value = "";
                timelineMonthSelect.value = "";
                timelineDaySelect.value = "";
                timelineDescriptionInput.value = '';
                addTimelineBtn.style.display = 'inline-block'; // '추가' 버튼 보이게
                updateTimelineBtn.style.display = 'none';    // '수정 완료' 버튼 숨기게
                cancelEditBtn.style.display = 'none';      // '수정 취소' 버튼 숨기게
                formTitle.textContent = '새로운 연혁 추가'; // 폼 제목 초기화
                formMode.value = 'insert'; // 모드를 'insert'로 재설정
                formTlId.value = ''; // tl_id 초기화
                populateDays(); // 일자 셀렉트 박스 초기화 (월,일 선택 안된 상태)
            }

            // "수정" 및 "삭제" 버튼 클릭 이벤트 (이벤트 위임 사용)
            timelineContainer.addEventListener('click', function(event) {
                if (event.target.classList.contains('edit-btn')) {
                    const button = event.target;
                    const tl_id = button.dataset.tl_id;
                    const year = button.dataset.year;
                    const month = button.dataset.month;
                    const day = button.dataset.day;
                    const description = button.dataset.description; // 이제 htmlspecialchars() 제거된 순수 텍스트

                    // 폼 제목 변경
                    formTitle.textContent = '연혁 수정';

                    // '추가' 버튼 숨기고 '수정 완료', '수정 취소' 버튼 표시
                    addTimelineBtn.style.display = 'none';
                    updateTimelineBtn.style.display = 'inline-block';
                    cancelEditBtn.style.display = 'inline-block';

                    // 폼 필드에 값 채우기
                    timelineYearSelect.value = year;
                    populateDays(year, month, day); // 해당 연월에 맞는 일자 목록 생성 후 선택된 일 설정
                    timelineMonthSelect.value = month; // 선택된 월 설정
                    // timelineDaySelect.value는 populateDays 함수에서 처리됨
                    timelineDescriptionInput.value = description;

                    // 폼의 hidden input에 값 설정
                    formMode.value = 'update';
                    formTlId.value = tl_id;

                    // 폼으로 스크롤 이동
                    window.scrollTo({
                        top: 0,
                        behavior: 'smooth'
                    });
                } else if (event.target.classList.contains('delete-btn')) {
                    const button = event.target;
                    const tl_id = button.dataset.tl_id;
                    if (confirm('정말로 이 연혁 항목을 삭제하시겠습니까?')) {
                        // 삭제는 GET 요청으로 처리 (페이지 새로고침)
                        window.location.href = './timeline.php?mode=delete&tl_id=' + tl_id;
                    }
                }
            });

            // "연혁 추가" 버튼 클릭 시 유효성 검사 및 폼 제출
            addTimelineBtn.addEventListener('click', function(event) {
                if (!timelineYearSelect.value || !timelineMonthSelect.value || !timelineDaySelect.value || !timelineDescriptionInput.value.trim()) {
                    alert('연도, 월, 일, 내용을 모두 입력해주세요.');
                    event.preventDefault(); // 폼 제출 방지
                    return false;
                }
                formMode.value = 'insert'; // 명시적으로 insert 모드 설정
                // 폼은 type="submit"이므로 자동으로 제출됩니다.
            });

            // "수정 완료" 버튼 클릭 시 유효성 검사 및 폼 제출
            updateTimelineBtn.addEventListener('click', function(event) {
                if (!timelineYearSelect.value || !timelineMonthSelect.value || !timelineDaySelect.value || !timelineDescriptionInput.value.trim()) {
                    alert('연도, 월, 일, 내용을 모두 입력해주세요.');
                    alert("연도, 월, 일, 내용을 모두 입력해주세요.");
                    event.preventDefault(); // 폼 제출 방지
                    return false;
                }
                formMode.value = 'update'; // 명시적으로 update 모드 설정
                // 폼은 type="submit"이므로 자동으로 제출됩니다.
            });

            // "수정 취소" 버튼 클릭 이벤트
            cancelEditBtn.addEventListener('click', function() {
                resetForm(); // 폼 초기화
            });

            // PHP에서 출력된 항목의 CSS 클래스를 조정 (PHP가 데이터베이스 정렬을 하므로 JavaScript에서는 필요 없을 수 있음)
            function updateTimelineItemClasses() {
                const items = timelineContainer.querySelectorAll('.timeline-item');
                items.forEach((item, index) => {
                    item.classList.remove('timeline-item-even');
                    if ((index + 1) % 2 === 0) { // 짝수 번째 항목에 클래스 추가
                        item.classList.add('timeline-item-even');
                    }
                });
            }
            // 페이지 로드 시 한 번 실행 (PHP에서 이미 클래스를 할당하지만, 혹시 동적으로 추가/삭제될 경우 대비)
            updateTimelineItemClasses();
        });
    </script>
</body>
</html>

댓글을 작성하려면 로그인이 필요합니다.

답변 3개

채택된 답변
+20 포인트
박긍정
3개월 전

확실친 않지만, 왠지 느낌이 tl_description 필드값에 쌍따옴표(") 가 들어가서 html 구조가 무너졌을수 있을듯한 코드네요. 

만약 위 경우가 맞다면, 아래 해결책입니다.

- g5_timeline 테이블에서 tl_description 값에 쌍따옴표가 있다면 제거(추천) 또는 \" 로 치환

- addslashes 사용 : <?php echo $row['tl_description']; ?> 를 <?php echo addslashes($row['tl_description']); ?> 로 변경 (2군데 보입니다)

로그인 후 평가할 수 있습니다

댓글을 작성하려면 로그인이 필요합니다.

3개월 전

그누보드 커스텀 페이지에서, 헤더/푸터 불러오는 코드 형태는 다음과 같습니다. https://sir.kr/qa/495462

</p>

<p><?php

include_once('../common.php');

include_once(G5_PATH.'/head.php');

?></p>

<p> </p>

<p><?php

include_once(G5_PATH.'/tail.php');

?></p>

<p>

 

-------------------

 

</p>

<p><?php

include_once('./_common.php');</p>

<p>// 데이터 처리

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $mode = $_POST['mode'] ?? '';

    $tl_id = (int)($_POST['tl_id'] ?? 0);

    $tl_year = sql_real_escape_string(trim($_POST['timelineYear'] ?? ''));

    $tl_month = sql_real_escape_string(trim($_POST['timelineMonth'] ?? ''));

    $tl_day = sql_real_escape_string(trim($_POST['timelineDay'] ?? ''));

    $tl_description = sql_real_escape_string(trim($_POST['timelineDescription'] ?? ''));</p>

<p>    if (!$tl_year || !$tl_month || !$tl_day || !$tl_description) {

        alert("연도, 월, 일, 내용을 모두 입력해주세요.");

        exit;

    }</p>

<p>    if ($mode == 'insert') {

        sql_query("INSERT INTO g5_timeline SET tl_year='{$tl_year}', tl_month='{$tl_month}', tl_day='{$tl_day}', tl_description='{$tl_description}', tl_datetime=NOW()");

        alert("연혁이 추가되었습니다.", './timeline.php');

    } elseif ($mode == 'update' && $tl_id) {

        sql_query("UPDATE g5_timeline SET tl_year='{$tl_year}', tl_month='{$tl_month}', tl_day='{$tl_day}', tl_description='{$tl_description}' WHERE tl_id='{$tl_id}'");

        alert("연혁이 수정되었습니다.", './timeline.php');

    }

}</p>

<p>// 삭제 처리

if (($_GET['mode'] ?? '') == 'delete') {

    $tl_id = (int)($_GET['tl_id'] ?? 0);

    if ($tl_id) {

        sql_query("DELETE FROM g5_timeline WHERE tl_id='{$tl_id}'");

        alert("연혁이 삭제되었습니다.", './timeline.php');

    }

}</p>

<p>$g5['title'] = '연혁 관리';

include_once(G5_PATH.'/head.php');

?></p>

<p><style>

body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; background: #f4f7f6; }

.container { max-width: 900px; margin: 0 auto; padding: 20px; background: #fff; box-shadow: 0 0 15px rgba(0,0,0,0.05); border-radius: 8px; }

.section-heading { text-align: center; font-size: 2.2em; color: #444; margin: 20px 0 50px; font-weight: 600; }</p>

<p>/* Timeline */

.timeline { position: relative; padding: 0; margin: 0; }

.timeline::before { content: ''; position: absolute; width: 2px; background: #ddd; top: 0; bottom: 0; left: 50%; transform: translateX(-50%); z-index: 1; }

.timeline-item { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 60px; position: relative; z-index: 2; }

.timeline-item::before { content: ''; position: absolute; width: 14px; height: 14px; border-radius: 50%; background: #5bc0de; border: 2px solid #fff; top: 5px; left: 50%; transform: translateX(-50%); z-index: 3; }

.timeline-year { flex-basis: 45%; padding-right: 40px; font-size: 1.8em; font-weight: 700; color: #34495e; text-align: right; }

.timeline-content { flex-basis: 45%; padding-left: 40px; }

.timeline-content ul { list-style: none; padding: 0; margin: 0; }

.timeline-content li { margin-bottom: 8px; font-size: 0.95em; color: #555; display: flex; align-items: flex-start; position: relative; padding-right: 120px; }

.event-date-detail { font-weight: bold; color: #5bc0de; flex-shrink: 0; order: 2; text-align: right; min-width: 50px; margin-left: auto; padding-left: 10px; }

.event-description { flex-grow: 1; order: 1; }

.action-buttons { position: absolute; top: 0; right: 0; display: flex; gap: 5px; z-index: 10; }

.action-buttons button { padding: 5px 10px; border: none; border-radius: 4px; cursor: pointer; font-size: 0.8em; transition: background-color 0.2s; }

.edit-btn { background: #f0ad4e; color: white; }

.edit-btn:hover { background: #ec971f; }

.delete-btn { background: #d9534f; color: white; }

.delete-btn:hover { background: #c9302c; }</p>

<p>/* Alternating layout */

.timeline-item:nth-child(even) .timeline-year { order: 2; text-align: left; padding-left: 40px; padding-right: 0; }

.timeline-item:nth-child(even) .timeline-content { order: 1; text-align: right; padding-right: 40px; padding-left: 0; }

.timeline-item:nth-child(even) .event-date-detail { order: 1; margin-left: 0; margin-right: auto; text-align: left; padding-right: 10px; padding-left: 0; }

.timeline-item:nth-child(even) .action-buttons { left: 0; right: auto; }</p>

<p>/* Form */

.form { background: #f0f8ff; padding: 25px; border-radius: 8px; margin-bottom: 40px; border: 1px solid #e0f2f7; }

.form h3 { color: #34495e; margin: 0 0 20px; }

.form label { display: block; margin-bottom: 8px; font-weight: 600; color: #34495e; }

.form input, .form textarea, .form select { width: calc(100% - 20px); padding: 10px; margin-bottom: 15px; border: 1px solid #ccc; border-radius: 4px; font-size: 1em; box-sizing: border-box; }

.date-group { display: flex; gap: 10px; margin-bottom: 15px; }

.date-group select { flex: 1; width: auto; margin-bottom: 0; }

.form textarea { resize: vertical; min-height: 60px; }

.form button { background: #5bc0de; color: white; padding: 12px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 1.1em; font-weight: 600; transition: background-color 0.2s; margin-right: 10px; }

.form button:hover { background: #46b8da; }

.form .cancel { background: #6c757d; }

.form .cancel:hover { background: #5a6268; }

.form .update, .form .cancel { display: none; }</p>

<p>/* Mobile */

@media (max-width: 768px) {

    .timeline::before { left: 20px; transform: translateX(0); }

    .timeline-item { flex-direction: column; align-items: flex-start; margin-left: 20px; }

    .timeline-item::before { left: 20px; transform: translateX(-50%); }

    .timeline-year, .timeline-content { width: 100%; flex-basis: auto; padding: 0; text-align: left !important; }

    .timeline-year { font-size: 1.5em; margin-bottom: 10px; padding-left: 30px; order: 1 !important; }

    .timeline-content { padding-left: 30px; order: 2 !important; }

    .timeline-item:nth-child(even) .timeline-year, .timeline-item:nth-child(even) .timeline-content { padding-left: 30px; padding-right: 0; }

    .event-date-detail { order: 1 !important; margin: 0 !important; text-align: left !important; padding: 0 10px 0 0 !important; }

    .event-description { order: 2 !important; }

    .action-buttons { position: static; margin-top: 10px; margin-left: 30px; justify-content: flex-start; }

    .timeline-item:nth-child(even) .action-buttons { left: auto; right: auto; }

}

</style></p>

<p><div class="container">

    <h2 class="section-heading">연혁</h2></p>

<p>    <div class="form">

        <h3 id="formTitle">새로운 연혁 추가</h3>

        <form method="post">

            <input type="hidden" name="mode" id="mode" value="insert">

            <input type="hidden" name="tl_id" id="tl_id"></p>

<p>            <label>연도:</label>

            <select id="year" name="timelineYear"></select>

            

            <label>날짜:</label>

            <div class="date-group">

                <select id="month" name="timelineMonth"></select>

                <select id="day" name="timelineDay"></select>

            </div></p>

<p>            <label>내용:</label>

            <textarea id="description" name="timelineDescription" placeholder="연혁 내용을 입력하세요"></textarea>

            

            <button type="submit" id="add">연혁 추가</button>

            <button type="submit" id="update" class="update">수정 완료</button>

            <button type="button" id="cancel" class="cancel">수정 취소</button>

        </form>

    </div></p>

<p>    <div class="timeline">

        <?php

        $result = sql_query("SELECT * FROM g5_timeline ORDER BY tl_year DESC, tl_month DESC, tl_day DESC");

        $index = 0;

        while ($row = sql_fetch_array($result)) {

            $index++;

        ?>

        <div class="timeline-item">

            <div class="timeline-year"><?= $row['tl_year'] ?></div>

            <div class="timeline-content">

                <ul>

                    <li>

                        <span class="event-date-detail"><?= $row['tl_month'].'.'.$row['tl_day'] ?></span>

                        <span class="event-description"><?= $row['tl_description'] ?></span>

                        <div class="action-buttons">

                            <button class="edit-btn" data-id="<?= $row['tl_id'] ?>" data-year="<?= $row['tl_year'] ?>" data-month="<?= $row['tl_month'] ?>" data-day="<?= $row['tl_day'] ?>" data-desc="<?= $row['tl_description'] ?>">수정</button>

                            <button class="delete-btn" data-id="<?= $row['tl_id'] ?>">삭제</button>

                        </div>

                    </li>

                </ul>

            </div>

        </div>

        <?php } ?>

    </div>

</div></p>

<p><script>

$(function() {

    var $year = $('#year'), $month = $('#month'), $day = $('#day'), $desc = $('#description');

    var $add = $('#add'), $update = $('#update'), $cancel = $('#cancel'), $title = $('#formTitle');

    var $mode = $('#mode'), $id = $('#tl_id');</p>

<p>    // 옵션 생성

    function populateSelect($el, start, end, prefix, selected) {

        $el.empty().append('<option value="" disabled selected>' + prefix + ' 선택</option>');

        for (var i = start; i <= end; i++) {

            var val = String(i).padStart(2, '0');

            var text = i + (prefix === '연도' ? '' : prefix);

            var $opt = $('<option>').val(prefix === '연도' ? i : val).text(text);

            if (selected == i) $opt.prop('selected', true);

            $el.append($opt);

        }

    }</p>

<p>    function populateDays(year, month, selected) {

        var days = month && year ? new Date(year, month, 0).getDate() : 31;

        populateSelect($day, 1, days, '일', selected);

    }</p>

<p>    function resetForm() {

        $year.val(''); $month.val(''); $day.val(''); $desc.val('');

        $add.show(); $update.hide(); $cancel.hide();

        $title.text('새로운 연혁 추가');

        $mode.val('insert'); $id.val('');

        populateDays();

    }</p>

<p>    // 초기화

    populateSelect($year, 1999, new Date().getFullYear() + 5, '연도');

    populateSelect($month, 1, 12, '월');

    populateDays();</p>

<p>    $year.add($month).on('change', function() {

        populateDays($year.val(), $month.val());

    });</p>

<p>    // 이벤트

    $('.timeline').on('click', '.edit-btn', function() {

        var $btn = $(this);

        $year.val($btn.data('year'));

        populateDays($btn.data('year'), $btn.data('month'), $btn.data('day'));

        $month.val($btn.data('month'));

        $desc.val($btn.data('desc'));

        

        $add.hide(); $update.show(); $cancel.show();

        $title.text('연혁 수정');

        $mode.val('update'); $id.val($btn.data('id'));

        

        $('html, body').animate({scrollTop: 0}, 500);

    });</p>

<p>    $('.timeline').on('click', '.delete-btn', function() {

        if (confirm('정말로 삭제하시겠습니까?')) {

            location.href = '?mode=delete&tl_id=' + $(this).data('id');

        }

    });</p>

<p>    $cancel.on('click', resetForm);</p>

<p>    $add.add($update).on('click', function(e) {

        if (!$year.val() || !$month.val() || !$day.val() || !$.trim($desc.val())) {

            alert('모든 항목을 입력해주세요.');

            e.preventDefault();

        }

    });

});

</script></p>

<p><?php include_once(G5_PATH.'/tail.php'); ?></p>

<p>

로그인 후 평가할 수 있습니다

댓글을 작성하려면 로그인이 필요합니다.

웅푸
3개월 전

해결안되시면 내아이디 클릭해서 sns 주세요 도와드릴테닌깐요 

로그인 후 평가할 수 있습니다

댓글을 작성하려면 로그인이 필요합니다.

답변을 작성하려면 로그인이 필요합니다.

로그인