'리더십', ]; $SUBCATE_MAP = [ 'CA200L01' => '리더십 시작하기', 'CA200L02' => '셀프 리더십', 'CA200L03' => '팀 리더십', 'CA200L04' => '실전조직 리더십', 'CA200L05' => '리더케이스탐구', ]; // --------------------------------------------------------- // 공통 함수 // --------------------------------------------------------- function h(?string $str): string { return htmlspecialchars((string)$str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } function mapContentRow(array $row, array $catMap = [], array $subcateMap = []): array { $kwStr = $row['keywords'] ?? ''; $keywords = $kwStr !== '' ? explode(',', $kwStr) : []; $watchTm = (float)($row['watch_tm'] ?? 0); $contentTm = (float)($row['content_tm'] ?? 0); $gauge = ($contentTm > 0) ? (int)round(($watchTm / $contentTm) * 100) : 0; $gauge = min(100, max(0, $gauge)); $raw = trim($row['content_url'] ?? ''); if (preg_match('/(?:v=|youtu\.be\/)([A-Za-z0-9_-]{11})/', $raw, $m)) { $videoId = $m[1]; } elseif (preg_match('/^[A-Za-z0-9_-]{11}$/', $raw)) { $videoId = $raw; } else { $videoId = ''; } $thumbFromDb = trim($row['thumbnail_url'] ?? ''); $url = $videoId !== '' ? "https://www.youtube.com/watch?v={$videoId}" : $raw; $thumbnail = $thumbFromDb !== '' ? $thumbFromDb : ($videoId !== '' ? "https://img.youtube.com/vi/{$videoId}/sddefault.jpg" : ''); $categoryCode = $row['category_code'] ?? ''; $groupCode = $row['category_group'] ?? ''; return [ 'id' => $row['content_id'] ?? '', 'url' => $url, 'thumbnail' => $thumbnail, 'category' => $catMap[$categoryCode] ?? $categoryCode, 'category_code' => $categoryCode, 'subcate' => $subcateMap[$groupCode] ?? $groupCode, 'bookmark' => !empty($row['is_bookmarked']), 'title' => $row['title'] ?? '', 'keywords' => $keywords, 'gauge' => $gauge, 'watch_tm' => (int)($row['watch_tm'] ?? 0), 'content_tm' => (int)($row['content_tm'] ?? 0), 'view_cnt' => (int)($row['view_cnt'] ?? 0), ]; } /** * 디버그용: 바인딩값을 SQL에 치환한 문자열 생성 * 주의: 실제 실행 SQL을 PDO가 제공하는 것은 아니고, 보기 쉽게 만든 디버그용 문자열임 */ function buildDebugSql(string $sql, array $params): string { // :member_id 와 :member_id2 같은 이름 충돌 방지 uksort($params, function ($a, $b) { return strlen((string)$b) <=> strlen((string)$a); }); foreach ($params as $key => $value) { if ($value === null) { $replace = 'NULL'; } elseif (is_int($value) || is_float($value)) { $replace = (string)$value; } else { $replace = "'" . str_replace("'", "''", (string)$value) . "'"; } $sql = str_replace($key, $replace, $sql); } return $sql; } try { $pdo = db_conn(); $pdo->exec("SET NAMES 'utf8mb4'"); // --------------------------------------------------------- // edu_codes 에서 실제 코드맵 조회 // --------------------------------------------------------- $stmtCode = $pdo->query(" SELECT group_code, code, code_name FROM edu_codes WHERE is_active = 1 AND group_code IN ('CA100', 'CA200') "); if ($stmtCode) { foreach ($stmtCode->fetchAll(PDO::FETCH_ASSOC) as $cr) { $key = $cr['group_code'] . $cr['code']; if ($cr['group_code'] === 'CA100') { $CATEGORY_MAP[$key] = $cr['code_name']; } elseif ($cr['group_code'] === 'CA200') { $SUBCATE_MAP[$key] = $cr['code_name']; } } } // --------------------------------------------------------- // 이 페이지는 리더십 대분류 전용 // 탭은 category_group 으로 필터링 // category 파라미터는 CA200L01 형태를 권장 // --------------------------------------------------------- $categoryGroup = ''; if ($category !== '' && strtolower($category) !== 'all') { if (preg_match('/^L\d{2}$/', $category)) { $categoryGroup = 'CA200' . $category; // L01 -> CA200L01 } else { $categoryGroup = $category; // 이미 CA200L01이면 그대로 } } // --------------------------------------------------------- // 정렬 조건 // --------------------------------------------------------- switch ($sort) { case 'latest': $orderBy = " COALESCE(c.updated_at, c.created_at) DESC, c.sort_order ASC, c.content_id DESC "; break; case 'seen': $orderBy = " CASE WHEN lh.content_id IS NOT NULL THEN 0 ELSE 1 END ASC, lh.last_viewed_at DESC, c.content_id DESC "; break; case 'unseen': $orderBy = " CASE WHEN lh.content_id IS NULL THEN 0 ELSE 1 END ASC, COALESCE(c.updated_at, c.created_at) DESC, c.content_id DESC "; break; case 'view': default: $orderBy = " COALESCE(vs.view_cnt, 0) DESC, COALESCE(vs.total_all_tm, 0) DESC, COALESCE(c.updated_at, c.created_at) DESC, c.content_id DESC "; break; } // --------------------------------------------------------- // 기본 조건 // --------------------------------------------------------- $where = []; $bind = []; $where[] = "(c.is_active = '1' OR c.is_active = 'Y')"; $where[] = "(c.start_date IS NULL OR c.start_date <= CURDATE())"; $where[] = "(c.end_date IS NULL OR c.end_date >= CURDATE())"; // 리더십 대분류 고정 $where[] = "c.category_code = 'CA10004'"; // 탭 필터 if ($categoryGroup !== '') { $where[] = "c.category_group = :category_group"; $bind[':category_group'] = $categoryGroup; } $whereSql = implode("\n AND ", $where); // --------------------------------------------------------- // TOTAL COUNT // --------------------------------------------------------- $countSql = " SELECT COUNT(*) FROM edu_contents c WHERE {$whereSql} "; $stmtCount = $pdo->prepare($countSql); foreach ($bind as $key => $value) { $stmtCount->bindValue($key, $value, PDO::PARAM_STR); } if ($DEBUG_SQL) { $countDebugSql = buildDebugSql($countSql, $bind); echo '
';
        echo "[COUNT SQL]\n\n";
      //  echo htmlspecialchars($countDebugSql, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        echo $countDebugSql;
        echo "\n\n------------------------------\n\n";
    }
    
    $stmtCount->execute();
    $totalCount = (int)$stmtCount->fetchColumn();
    
    // ---------------------------------------------------------
    // 목록 조회
    // ---------------------------------------------------------
    $sql = "
        SELECT
            c.content_id,
            c.category_code,
            c.category_group,
            c.title,
            c.content_url,
            c.thumbnail_url,
            c.sort_order,
            c.created_at,
            c.updated_at,
            
            lh.content_id AS lh_content_id,
            lh.watch_tm,
            lh.content_tm,
            lh.last_viewed_at,
            
            CASE
                WHEN cw.content_id IS NOT NULL THEN 1
                ELSE 0
            END AS is_bookmarked,
            
            COALESCE(vs.view_cnt, 0) AS view_cnt,
            COALESCE(vs.total_all_tm, 0) AS total_all_tm,
            
            GROUP_CONCAT(
                DISTINCT ck.keyword_code
                ORDER BY ck.keyword_code
                SEPARATOR ','
            ) AS keywords
            
        FROM edu_contents c
        
        LEFT JOIN edu_learning_histories lh
               ON lh.content_id = c.content_id
              AND lh.member_id = :member_id
              AND lh.sys_comp_code = :sys_comp_code
              
        LEFT JOIN edu_content_wishlist cw
               ON cw.content_id = c.content_id
              AND cw.member_id = :member_id2
              AND cw.sys_comp_code = :sys_comp_code2
              AND (cw.is_active = '1' OR cw.is_active = 'Y')
              
        LEFT JOIN (
            SELECT
                content_id,
                COUNT(*) AS view_cnt,
                COALESCE(SUM(all_tm), 0) AS total_all_tm
            FROM edu_learning_histories
            GROUP BY content_id
        ) vs
               ON vs.content_id = c.content_id
               
        LEFT JOIN edu_content_keywords ck
               ON ck.content_id = c.content_id
               
        WHERE {$whereSql}
        
        GROUP BY c.content_id
        
        ORDER BY {$orderBy}

        LIMIT :limit OFFSET :offset

    ";
    
    /*LIMIT :limit OFFSET :offset*/
    $stmt = $pdo->prepare($sql);
    
    $stmt->bindValue(':member_id', $memberId, PDO::PARAM_STR);
    $stmt->bindValue(':sys_comp_code', $sysCompCode, PDO::PARAM_STR);
    $stmt->bindValue(':member_id2', $memberId, PDO::PARAM_STR);
    $stmt->bindValue(':sys_comp_code2', $sysCompCode, PDO::PARAM_STR);
    
    foreach ($bind as $key => $value) {
        $stmt->bindValue($key, $value, PDO::PARAM_STR);
    }
    
    $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
    $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
    
    if ($DEBUG_SQL) {
        $debugParams = [
            ':member_id'      => $memberId,
            ':sys_comp_code'  => $sysCompCode,
            ':member_id2'     => $memberId,
            ':sys_comp_code2' => $sysCompCode,
            ':limit'          => $limit,
            ':offset'         => $offset,
        ];
        
        if (!empty($bind)) {
            $debugParams = array_merge($debugParams, $bind);
        }
        
        $debugSql = buildDebugSql($sql, $debugParams);
        
        echo "[LIST SQL]\n\n";
        //echo htmlspecialchars($debugSql, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        echo $debugSql;
        echo '
'; } $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); if ($DEBUG_SQL) { print_r($rows); exit; } // --------------------------------------------------------- // HTML 조각 생성 // --------------------------------------------------------- ob_start(); foreach ($rows as $row) { $item = mapContentRow($row, $CATEGORY_MAP, $SUBCATE_MAP); $contentId = h($item['id']); $title = h($item['title']); $thumb = h($item['thumbnail']); $categoryNm= h($item['category']); $subcateNm = h($item['subcate']); $checked = $item['bookmark'] ? ' checked' : ''; $viewUrl = "/edu/video_view.php?content_id=" . rawurlencode($item['id']); ?>
  • <?= $title ?> <?= $title ?>
  • true, 'html' => $html, 'total_count' => $totalCount ], JSON_UNESCAPED_UNICODE); } catch (Throwable $e) { error_log('[get_video_list.php] ' . $e->getMessage()); echo json_encode([ 'success' => false, 'html' => '', 'total_count' => 0 ], JSON_UNESCAPED_UNICODE); }