534 lines
22 KiB
PHP
534 lines
22 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../bbs/auth.php';
|
|
edu_require_login();
|
|
|
|
require_once __DIR__ . '/../bbs/db_conn.php';
|
|
|
|
$q = trim((string)($_GET['q'] ?? ''));
|
|
$results = [];
|
|
|
|
function h(string $value): string
|
|
{
|
|
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
}
|
|
|
|
function parse_youtube_id(string $raw): string
|
|
{
|
|
if (preg_match('/(?:v=|youtu\.be\/|youtube\.com\/embed\/)([A-Za-z0-9_-]{11})/', $raw, $m)) {
|
|
return $m[1];
|
|
}
|
|
if (preg_match('/^[A-Za-z0-9_-]{11}$/', $raw)) {
|
|
return $raw;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function make_thumb_url(string $thumb, string $contentUrl): string
|
|
{
|
|
$thumb = trim($thumb);
|
|
if ($thumb !== '') {
|
|
return $thumb;
|
|
}
|
|
|
|
$videoId = parse_youtube_id(trim($contentUrl));
|
|
if ($videoId !== '') {
|
|
return 'https://img.youtube.com/vi/' . $videoId . '/sddefault.jpg';
|
|
}
|
|
|
|
return '/img/video/img_thumb_01.png';
|
|
}
|
|
|
|
function highlight_keyword(string $text, string $keyword): string
|
|
{
|
|
$escaped = h($text);
|
|
$keyword = trim($keyword);
|
|
if ($keyword === '') {
|
|
return $escaped;
|
|
}
|
|
|
|
$pattern = '/' . preg_quote(h($keyword), '/') . '/iu';
|
|
return (string)preg_replace($pattern, '<span class="search-highlight">$0</span>', $escaped);
|
|
}
|
|
|
|
try {
|
|
$pdo = db_conn();
|
|
|
|
$memberId = trim((string)($_SESSION['member_id'] ?? ''));
|
|
$sysCompCode = trim((string)($_SESSION['sys_comp_code'] ?? ''));
|
|
|
|
$sql = "
|
|
SELECT
|
|
c.content_id,
|
|
c.title,
|
|
c.description,
|
|
c.description2,
|
|
c.content_url,
|
|
c.thumbnail_url,
|
|
c.category_code,
|
|
c.category_group,
|
|
COALESCE(ec.code_name, c.category_code) AS category_name,
|
|
COALESCE(vs.view_cnt, 0) AS view_cnt,
|
|
CASE WHEN cw.content_id IS NOT NULL THEN 1 ELSE 0 END AS is_bookmarked
|
|
FROM edu_contents c
|
|
LEFT JOIN edu_codes ec
|
|
ON ec.base_code = c.category_code
|
|
AND ec.group_code = 'CA100'
|
|
AND ec.is_active = 1
|
|
LEFT JOIN (
|
|
SELECT
|
|
content_id,
|
|
COUNT(DISTINCT member_id) AS view_cnt
|
|
FROM edu_learning_histories
|
|
GROUP BY content_id
|
|
) vs
|
|
ON vs.content_id = c.content_id
|
|
LEFT JOIN edu_content_wishlist cw
|
|
ON cw.member_id = :mid
|
|
AND cw.sys_comp_code = :sc
|
|
AND cw.is_active = 1
|
|
AND cw.content_id = c.content_id
|
|
WHERE c.is_active = 1
|
|
AND (
|
|
:kw = ''
|
|
OR c.title LIKE :like_kw1
|
|
OR c.description LIKE :like_kw2
|
|
OR c.description2 LIKE :like_kw3
|
|
)
|
|
ORDER BY c.updated_at DESC, c.created_at DESC, c.content_id DESC
|
|
LIMIT 300
|
|
";
|
|
|
|
$stmt = $pdo->prepare($sql);
|
|
$stmt->execute([
|
|
':mid' => $memberId,
|
|
':sc' => $sysCompCode,
|
|
':kw' => $q,
|
|
':like_kw1' => '%' . $q . '%',
|
|
':like_kw2' => '%' . $q . '%',
|
|
':like_kw3' => '%' . $q . '%',
|
|
]);
|
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
|
} catch (Throwable $e) {
|
|
error_log('[search_result] ' . $e->getMessage());
|
|
$results = [];
|
|
}
|
|
|
|
$totalCount = count($results);
|
|
|
|
// 카테고리별 그룹화
|
|
$groupedResults = [];
|
|
$categoryMeta = []; // category_code => ['name' => string, 'count' => int]
|
|
foreach ($results as $row) {
|
|
$catCode = (string)($row['category_code'] ?? 'etc');
|
|
$catName = (string)($row['category_name'] ?? $row['category_code'] ?? '기타');
|
|
if (!isset($groupedResults[$catCode])) {
|
|
$groupedResults[$catCode] = [];
|
|
$categoryMeta[$catCode] = ['name' => $catName, 'count' => 0];
|
|
}
|
|
$groupedResults[$catCode][] = $row;
|
|
$categoryMeta[$catCode]['count']++;
|
|
}
|
|
|
|
$videosForJs = array_map(static function (array $row): array {
|
|
$contentUrl = (string)($row['content_url'] ?? '');
|
|
$thumb = make_thumb_url((string)($row['thumbnail_url'] ?? ''), $contentUrl);
|
|
|
|
return [
|
|
'id' => (string)($row['content_id'] ?? ''),
|
|
'content_id' => (string)($row['content_id'] ?? ''),
|
|
'url' => $contentUrl,
|
|
'content_url' => $contentUrl,
|
|
'thumbnail' => $thumb,
|
|
'category' => (string)($row['category_name'] ?? $row['category_code'] ?? ''),
|
|
'category_name' => (string)($row['category_name'] ?? $row['category_code'] ?? ''),
|
|
'category_code' => (string)($row['category_code'] ?? ''),
|
|
'subcate' => (string)($row['category_group'] ?? ''),
|
|
'title' => (string)($row['title'] ?? ''),
|
|
'description' => trim((string)($row['description'] ?? '')),
|
|
'description2' => trim((string)($row['description2'] ?? '')),
|
|
'bookmark' => ((string)($row['is_bookmarked'] ?? '0') === '1'),
|
|
'type' => 'main',
|
|
'keywords' => [],
|
|
'watch_tm' => 0,
|
|
'content_tm' => 0,
|
|
'all_tm' => 0,
|
|
'view_cnt' => (int)($row['view_cnt'] ?? 0),
|
|
];
|
|
}, $results);
|
|
|
|
$videosJson = json_encode($videosForJs, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP);
|
|
?>
|
|
<!doctype html>
|
|
<!-- dynamic-search-result-v2 -->
|
|
<html lang="ko">
|
|
<head>
|
|
<?php include(__DIR__ . '/_include/_head.php') ?>
|
|
<link rel="stylesheet" type="text/css" href="/css/main.css" />
|
|
<style>
|
|
.search-result .search-highlight {
|
|
color: #ff7a00;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.search-result .item-desc-wrapper {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
width: 100%;
|
|
gap: 8px;
|
|
}
|
|
|
|
.search-result .item-view-count {
|
|
margin-left: auto;
|
|
font-size: 13px;
|
|
color: #6f6f6f;
|
|
line-height: 1.5;
|
|
white-space: nowrap;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wrap search-result">
|
|
<?php include(__DIR__ . '/_include/_header.php') ?>
|
|
<div class="container">
|
|
<div class="search-result-wrap">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h3 class="blind">검색결과</h3>
|
|
<p class="search-summary">
|
|
'<?php echo h($q); ?>'에 대한 검색결과가 <em class="search-count"><?php echo (int)$totalCount; ?></em>건 있습니다.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($q === ''): ?>
|
|
<p style="padding: 20px 0;">검색어를 입력해주세요.</p>
|
|
<?php elseif ($totalCount === 0): ?>
|
|
<p style="padding: 20px 0;">검색 결과가 없습니다.</p>
|
|
<?php else: ?>
|
|
|
|
<!-- 필터 탭 -->
|
|
<div class="filter-tabs">
|
|
<button class="filter-tab active" data-filter="all">
|
|
전체 <span class="count"><?php echo (int)$totalCount; ?></span>
|
|
</button>
|
|
<?php foreach ($categoryMeta as $catCode => $meta): ?>
|
|
<button class="filter-tab" data-filter="<?php echo h($catCode); ?>">
|
|
<?php echo h($meta['name']); ?> <span class="count"><?php echo (int)$meta['count']; ?></span>
|
|
</button>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<!-- 검색 결과 섹션 -->
|
|
<div class="search-sections" id="videoCardsContainer">
|
|
|
|
<!-- [전체] 평면 목록: 카테고리 구분 없이 전체 결과를 순서대로 출력 -->
|
|
<section class="content-section" data-section="all">
|
|
<div class="section-header">
|
|
<h3 class="section-title">전체 <span class="section-count"><?php echo (int)$totalCount; ?></span></h3>
|
|
<!-- <div class="list-options">
|
|
<div class="select-wrap">
|
|
<select class="select-sort" title="정렬">
|
|
<option>조회수</option>
|
|
<option selected>업데이트</option>
|
|
<option>내가본컨텐츠</option>
|
|
<option>안본컨텐츠</option>
|
|
</select>
|
|
</div>
|
|
</div> -->
|
|
</div>
|
|
<ul class="video-grid">
|
|
<?php foreach ($results as $idx => $row):
|
|
$id = (string)($row['content_id'] ?? '');
|
|
$title = (string)($row['title'] ?? '');
|
|
$description = trim((string)($row['description'] ?? ''));
|
|
$description2 = trim((string)($row['description2'] ?? ''));
|
|
$thumb = make_thumb_url((string)($row['thumbnail_url'] ?? ''), (string)($row['content_url'] ?? ''));
|
|
$category = (string)($row['category_name'] ?? $row['category_code'] ?? '');
|
|
$bookmark = ((string)($row['is_bookmarked'] ?? '0') === '1');
|
|
$viewCnt = (int)($row['view_cnt'] ?? 0);
|
|
$bookmarkId = 'like_chk_all_' . ($idx + 1);
|
|
$titleHighlighted = highlight_keyword($title, $q);
|
|
$descHighlighted = highlight_keyword($description, $q);
|
|
$desc2Highlighted = highlight_keyword($description2, $q);
|
|
?>
|
|
<li class="video-item">
|
|
<a href="#" class="card-link card" data-video-id="<?php echo h($id); ?>">
|
|
<label class="bookmark" for="<?php echo h($bookmarkId); ?>" onclick="event.stopPropagation();"><input type="checkbox" id="<?php echo h($bookmarkId); ?>" <?php echo $bookmark ? 'checked' : ''; ?> title="좋아요"></label>
|
|
<div class="item-thumb">
|
|
<img src="<?php echo h($thumb); ?>" alt="" />
|
|
</div>
|
|
<div class="item-info">
|
|
<strong class="item-title"><?php echo $titleHighlighted; ?></strong>
|
|
<?php if ($description !== '' || $description2 !== ''): ?>
|
|
<div class="item-desc" style="color:#6f6f6f; font-size:13px; line-height:1.5;">
|
|
<?php if ($description !== ''): ?><p class="desc-1"><?php echo $descHighlighted; ?></p><?php endif; ?>
|
|
<?php if ($description2 !== ''): ?><p class="desc-2" style="margin-top:2px;"><?php echo $desc2Highlighted; ?></p><?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<div class="tag-list item-desc-wrapper">
|
|
<span class="tag"><?php echo h($category); ?></span>
|
|
<span class="item-view-count">시청수 <?php echo (int)$viewCnt; ?> 회</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</section>
|
|
|
|
<!-- [카테고리별] 섹션: 기본 hidden, 특정 탭 선택 시 표시 -->
|
|
<?php
|
|
$globalIdx = 0;
|
|
foreach ($groupedResults as $catCode => $catRows):
|
|
$catName = $categoryMeta[$catCode]['name'];
|
|
$catCount = $categoryMeta[$catCode]['count'];
|
|
?>
|
|
<section class="content-section" data-section="<?php echo h($catCode); ?>" style="display:none;">
|
|
<div class="section-header">
|
|
<h3 class="section-title"><?php echo h($catName); ?> <span class="section-count"><?php echo (int)$catCount; ?></span></h3>
|
|
<!-- <div class="list-options">
|
|
<div class="select-wrap">
|
|
<select class="select-sort" title="정렬">
|
|
<option>조회수</option>
|
|
<option selected>업데이트</option>
|
|
<option>내가본컨텐츠</option>
|
|
<option>안본컨텐츠</option>
|
|
</select>
|
|
</div>
|
|
</div> -->
|
|
</div>
|
|
<ul class="video-grid">
|
|
<?php foreach ($catRows as $row):
|
|
$globalIdx++;
|
|
$id = (string)($row['content_id'] ?? '');
|
|
$title = (string)($row['title'] ?? '');
|
|
$description = trim((string)($row['description'] ?? ''));
|
|
$description2 = trim((string)($row['description2'] ?? ''));
|
|
$thumb = make_thumb_url((string)($row['thumbnail_url'] ?? ''), (string)($row['content_url'] ?? ''));
|
|
$category = (string)($row['category_name'] ?? $row['category_code'] ?? '');
|
|
$bookmark = ((string)($row['is_bookmarked'] ?? '0') === '1');
|
|
$viewCnt = (int)($row['view_cnt'] ?? 0);
|
|
$bookmarkId = 'like_chk_search_' . $globalIdx;
|
|
$titleHighlighted = highlight_keyword($title, $q);
|
|
$descHighlighted = highlight_keyword($description, $q);
|
|
$desc2Highlighted = highlight_keyword($description2, $q);
|
|
?>
|
|
<li class="video-item">
|
|
<a href="#" class="card-link card" data-video-id="<?php echo h($id); ?>">
|
|
<label class="bookmark" for="<?php echo h($bookmarkId); ?>" onclick="event.stopPropagation();"><input type="checkbox" id="<?php echo h($bookmarkId); ?>" <?php echo $bookmark ? 'checked' : ''; ?> title="좋아요"></label>
|
|
<div class="item-thumb">
|
|
<img src="<?php echo h($thumb); ?>" alt="" />
|
|
</div>
|
|
<div class="item-info">
|
|
<strong class="item-title"><?php echo $titleHighlighted; ?></strong>
|
|
<?php if ($description !== '' || $description2 !== ''): ?>
|
|
<div class="item-desc" style="color:#6f6f6f; font-size:13px; line-height:1.5;">
|
|
<?php if ($description !== ''): ?><p class="desc-1"><?php echo $descHighlighted; ?></p><?php endif; ?>
|
|
<?php if ($description2 !== ''): ?><p class="desc-2" style="margin-top:2px;"><?php echo $desc2Highlighted; ?></p><?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<div class="tag-list item-desc-wrapper">
|
|
<span class="tag"><?php echo h($category); ?></span>
|
|
<span class="item-view-count">시청수 <?php echo (int)$viewCnt; ?> 회</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</section>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/js/learning/config.js" defer></script>
|
|
<script src="/js/learning/markers.js" defer></script>
|
|
<script src="/js/learning/modal.js" defer></script>
|
|
<script src="/js/bridges/learning-modal-bridge.js" defer></script>
|
|
<script>
|
|
window.puzzleConfig = {
|
|
...(window.puzzleConfig || {}),
|
|
COMPLETION_MODE: 'COMPLETED',
|
|
CURRENT_MEMBER_ID: <?php echo json_encode($memberId, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP); ?>,
|
|
};
|
|
</script>
|
|
<script src="/js/puzzle-onboarding.js" defer></script>
|
|
<script src="/js/bridges/onboarding-modal-bridge.js" defer></script>
|
|
<script src="/js/main/Videomodalmanager.js" defer></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const currentVideos = <?= $videosJson ?: '[]' ?>;
|
|
const container = document.getElementById('videoCardsContainer');
|
|
|
|
// ── 모달 초기화 ──
|
|
let modalManager = null;
|
|
if (container && Array.isArray(currentVideos) && currentVideos.length > 0 && typeof VideoModalManager !== 'undefined') {
|
|
modalManager = new VideoModalManager({ videos: currentVideos });
|
|
}
|
|
|
|
// ── 필터 탭 ──
|
|
const filterTabs = document.querySelectorAll('.filter-tab');
|
|
filterTabs.forEach(function (tab) {
|
|
tab.addEventListener('click', function () {
|
|
filterTabs.forEach(function (t) { t.classList.remove('active'); });
|
|
this.classList.add('active');
|
|
|
|
const filter = this.dataset.filter;
|
|
document.querySelectorAll('.content-section').forEach(function (section) {
|
|
const sec = section.dataset.section;
|
|
if (filter === 'all') {
|
|
// 전체 탭: all 섹션만 표시, 카테고리 섹션 모두 숨김
|
|
section.style.display = (sec === 'all') ? 'block' : 'none';
|
|
} else {
|
|
// 카테고리 탭: all 섹션 숨기고 해당 카테고리만 표시
|
|
section.style.display = (sec === filter) ? 'block' : 'none';
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// ── 북마크 ──
|
|
if (!container) return;
|
|
|
|
async function saveWishlist(videoId, isActive) {
|
|
const params = new URLSearchParams({
|
|
content_id: String(videoId || ''),
|
|
is_active: isActive ? '1' : '0',
|
|
});
|
|
const res = await fetch('/bbs/api/save_wishlist.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
|
body: params.toString(),
|
|
});
|
|
return res.json();
|
|
}
|
|
|
|
function setBookmarkState(videoId, isBookmarked) {
|
|
const id = String(videoId || '');
|
|
currentVideos.forEach(function (video) {
|
|
if (String(video?.id ?? '') === id) { video.bookmark = !!isBookmarked; }
|
|
});
|
|
container.querySelectorAll('.card[data-video-id]').forEach(function (cardEl) {
|
|
if (String(cardEl.getAttribute('data-video-id') || '') !== id) return;
|
|
const checkbox = cardEl.querySelector('.bookmark input[type="checkbox"]');
|
|
if (checkbox) checkbox.checked = !!isBookmarked;
|
|
});
|
|
if (modalManager && modalManager.config) { modalManager.config.videos = currentVideos; }
|
|
}
|
|
|
|
function getVideoDataById(videoId) {
|
|
const id = String(videoId || '');
|
|
return currentVideos.find(function (v) {
|
|
return String(v?.id ?? '') === id || String(v?.content_id ?? '') === id;
|
|
}) || null;
|
|
}
|
|
|
|
function toBridgeInfo(videoData) {
|
|
const contentId = String(videoData?.content_id ?? videoData?.id ?? '');
|
|
const description = [
|
|
String(videoData?.description || '').trim(),
|
|
String(videoData?.description2 || '').trim(),
|
|
].filter(Boolean).join('\n');
|
|
|
|
return {
|
|
contentId: contentId,
|
|
videoUrl: String(videoData?.content_url ?? videoData?.url ?? ''),
|
|
title: String(videoData?.title ?? ''),
|
|
description: description,
|
|
categoryName: String(videoData?.category_name ?? videoData?.category ?? ''),
|
|
subCategory: String(videoData?.subcate ?? ''),
|
|
watchTm: Number(videoData?.watch_tm || 0),
|
|
contentTm: Number(videoData?.content_tm || 0),
|
|
bookmark: !!videoData?.bookmark,
|
|
};
|
|
}
|
|
|
|
async function openSearchResultVideoByCategory(videoData) {
|
|
const categoryCode = String(videoData?.category_code || '').toUpperCase();
|
|
const bridgeInfo = toBridgeInfo(videoData);
|
|
|
|
// 브리지가 준비된 카테고리부터 우선 적용한다.
|
|
if (categoryCode === 'CA10006' && typeof window._biztrendOpenModal === 'function') {
|
|
window._biztrendOpenModal(bridgeInfo);
|
|
return;
|
|
}
|
|
|
|
if (categoryCode === 'CA10005' && typeof window._insightOpenModal === 'function') {
|
|
window._insightOpenModal(bridgeInfo);
|
|
return;
|
|
}
|
|
|
|
if (categoryCode === 'CA10004' && typeof window._leadershipOpenModal === 'function') {
|
|
window._leadershipOpenModal(bridgeInfo);
|
|
return;
|
|
}
|
|
|
|
if (categoryCode === 'CA10003' && typeof window._learningOpenModal === 'function') {
|
|
window._learningOpenModal(bridgeInfo);
|
|
return;
|
|
}
|
|
|
|
if (categoryCode === 'CA10002' && typeof window._onboardingOpenModal === 'function') {
|
|
window._onboardingOpenModal(bridgeInfo);
|
|
return;
|
|
}
|
|
|
|
// 기본값: 기존 공통 모달 흐름 유지
|
|
if (modalManager && typeof modalManager.openVideo === 'function') {
|
|
await modalManager.openVideo(String(videoData?.id ?? videoData?.content_id ?? ''));
|
|
}
|
|
}
|
|
|
|
container.addEventListener('click', function (e) {
|
|
if (e.target.closest('.bookmark')) { e.stopPropagation(); return; }
|
|
|
|
const card = e.target.closest('.card[data-video-id]');
|
|
if (!card) return;
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const videoId = String(card.getAttribute('data-video-id') || '');
|
|
if (!videoId) return;
|
|
|
|
const videoData = getVideoDataById(videoId);
|
|
if (!videoData) return;
|
|
|
|
openSearchResultVideoByCategory(videoData).catch(function (err) {
|
|
console.warn('[search_result] open by category failed:', err?.message || err);
|
|
});
|
|
});
|
|
|
|
container.addEventListener('change', async function (e) {
|
|
const checkbox = e.target.closest('input[type="checkbox"]');
|
|
if (!checkbox || !checkbox.closest('.bookmark')) return;
|
|
|
|
const card = checkbox.closest('.card[data-video-id]');
|
|
const videoId = card?.getAttribute('data-video-id');
|
|
if (!videoId) return;
|
|
|
|
const nextState = !!checkbox.checked;
|
|
checkbox.disabled = true;
|
|
try {
|
|
const result = await saveWishlist(videoId, nextState);
|
|
if (!result || !result.success) { checkbox.checked = !nextState; return; }
|
|
setBookmarkState(videoId, nextState);
|
|
} catch (err) {
|
|
checkbox.checked = !nextState;
|
|
console.warn('[search wishlist]', err?.message || err);
|
|
} finally {
|
|
checkbox.disabled = false;
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|