s4s-home / search.php
soiz1's picture
Upload 14 files
240bcc5 verified
<?php
require_once 'config.php';
session_start();
// Googleログイン確認
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$search_query = $_GET['q'] ?? '';
$sort = $_GET['sort'] ?? 'default';
// データベース接続
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
if (!$conn->set_charset("utf8mb4")) {
throw new Exception("文字コード設定失敗: " . $conn->error);
}
// 検索クエリの構築
$sql = "SELECT * FROM projects WHERE is_public = 1";
$params = [];
if (!empty($search_query)) {
$sql .= " AND (project_name LIKE ? OR description LIKE ?)";
$search_param = "%$search_query%";
$params = array_merge($params, [$search_param, $search_param]);
}
// ソート方法
switch ($sort) {
case 'stars':
$sql .= " ORDER BY stars DESC";
break;
case 'views':
$sql .= " ORDER BY views DESC";
break;
case 'custom':
// カスタム数式 (例: stars * 2 + views)
$sql .= " ORDER BY (stars * 2 + views) DESC";
break;
default:
// デフォルト (関連性スコア)
if (!empty($search_query)) {
$sql .= " ORDER BY
(CASE WHEN project_name LIKE ? THEN 3
WHEN description LIKE ? THEN 2
ELSE 1 END) DESC";
$exact_param = "$search_query%";
$params = array_merge($params, [$exact_param, $exact_param]);
} else {
$sql .= " ORDER BY created_at DESC";
}
}
$stmt = $conn->prepare($sql);
// パラメータをバインド
if (!empty($params)) {
$types = str_repeat('s', count($params));
$stmt->bind_param($types, ...$params);
}
$stmt->execute();
$result = $stmt->get_result();
$projects = $result->fetch_all(MYSQLI_ASSOC);
$conn->close();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-PJXRKX3F');</script>
<!-- End Google Tag Manager -->
<title>プロジェクト検索 - Scratch School</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root {
--primary-color: #4d97ff;
--secondary-color: #575e75;
--background-color: #f5f5f5;
--card-bg: #ffffff;
--text-color: #333333;
--error-color: #ff4444;
--success-color: #00c851;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Noto Sans JP', sans-serif;
background-color: var(--background-color);
color: var(--text-color);
line-height: 1.6;
padding: 20px;
}
.container {
max-width: 98%;
margin: 0 auto;
background-color: var(--card-bg);
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 30px;
}
h1 {
color: var(--primary-color);
margin-bottom: 20px;
text-align: center;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input[type="text"],
textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: inherit;
font-size: 16px;
}
textarea {
min-height: 120px;
resize: vertical;
}
.thumbnail-preview {
width: 200px;
height: 150px;
background-color: #f0f0f0;
border-radius: 4px;
margin-bottom: 10px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.thumbnail-preview img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.btn {
background-color: var(--primary-color);
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #3a7bd5;
}
.btn-secondary {
background-color: var(--secondary-color);
}
.btn-secondary:hover {
background-color: #424858;
}
.error {
color: var(--error-color);
margin-bottom: 15px;
}
.success {
color: var(--success-color);
margin-bottom: 15px;
}
.checkbox-group {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.checkbox-group input {
margin-right: 10px;
}
.search-container {
display: flex;
margin-bottom: 20px;
}
.search-input {
flex-grow: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
font-size: 16px;
}
.search-button {
padding: 10px 20px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
}
.sort-options {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.sort-option {
padding: 5px 10px;
background-color: #f0f0f0;
border-radius: 4px;
cursor: pointer;
}
.sort-option.active {
background-color: var(--primary-color);
color: white;
}
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.project-card {
background-color: var(--card-bg);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
transition: transform 0.3s, box-shadow 0.3s;
cursor: pointer;
}
.project-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.project-thumbnail {
width: 100%;
height: 150px;
background-color: #f0f0f0;
overflow: hidden;
position: relative;
}
.project-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.project-info {
padding: 15px;
}
.project-title {
font-weight: 500;
margin-bottom: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.project-author {
color: var(--secondary-color);
font-size: 14px;
margin-bottom: 10px;
}
.project-stats {
display: flex;
justify-content: space-between;
font-size: 14px;
color: var(--secondary-color);
}
.loading-thumbnail {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #999;
}
</style>
</head>
<body>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PJXRKX3F"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<div class="container">
<h1>プロジェクトを探す</h1>
<form method="GET" class="search-container">
<input type="text" name="q" class="search-input" placeholder="プロジェクトを検索..." value="<?php echo htmlspecialchars($search_query); ?>">
<button type="submit" class="search-button">検索</button>
</form>
<div class="sort-options">
<a href="?q=<?php echo urlencode($search_query); ?>&sort=default" class="sort-option <?php echo $sort === 'default' ? 'active' : ''; ?>">関連順</a>
<a href="?q=<?php echo urlencode($search_query); ?>&sort=stars" class="sort-option <?php echo $sort === 'stars' ? 'active' : ''; ?>">星の数</a>
<a href="?q=<?php echo urlencode($search_query); ?>&sort=views" class="sort-option <?php echo $sort === 'views' ? 'active' : ''; ?>">閲覧数</a>
<a href="?q=<?php echo urlencode($search_query); ?>&sort=custom" class="sort-option <?php echo $sort === 'custom' ? 'active' : ''; ?>">おすすめ</a>
</div>
<?php if (empty($projects)): ?>
<p>プロジェクトが見つかりませんでした</p>
<?php else: ?>
<div class="projects-grid">
<?php foreach ($projects as $project): ?>
<a href="view.php?id=<?php echo $project['id']; ?>" class="project-card" target="_blank">
<div class="project-thumbnail" data-drive-id="<?php echo htmlspecialchars($project['drive_id']); ?>">
<div class="loading-thumbnail">...</div>
<!-- 画像はJavaScriptで動的に挿入されます -->
</div>
<div class="project-info">
<h3 class="project-title"><?php echo htmlspecialchars($project['project_name']); ?></h3>
<p class="project-author">by <?php echo htmlspecialchars($project['author_name']); ?></p>
<div class="project-stats">
<span>★ <?php echo $project['stars']; ?></span>
<span>👁️ <?php echo $project['views']; ?></span>
</div>
</div>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const accessToken = '<?php echo $_SESSION['access_token'] ?? ''; ?>';
const thumbnailContainers = document.querySelectorAll('.project-thumbnail');
thumbnailContainers.forEach(container => {
const driveId = container.dataset.driveId;
const loadingElement = container.querySelector('.loading-thumbnail');
if (!driveId) {
loadingElement.textContent = 'サムネイルなし';
return;
}
const thumbnailName = `Scratch-Thumbnail-${driveId}.png`;
// サムネイルを検索
fetch(`https://www.googleapis.com/drive/v3/files?q=name='${encodeURIComponent(thumbnailName)}'`, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
})
.then(response => {
if (!response.ok) {
throw new Error('サムネイル検索に失敗しました');
}
return response.json();
})
.then(data => {
if (data.files && data.files.length > 0) {
const thumbnailId = data.files[0].id;
const img = document.createElement('img');
img.src = `https://drive.google.com/thumbnail?id=${thumbnailId}&sz=w300`;
img.alt = 'プロジェクトサムネイル';
img.onload = () => {
loadingElement.remove();
container.appendChild(img);
};
img.onerror = () => {
loadingElement.textContent = 'サムネイル読み込みエラー';
};
} else {
loadingElement.textContent = 'サムネイルなし';
}
})
.catch(error => {
console.error('サムネイル取得エラー:', error);
loadingElement.textContent = '読み込みエラー';
if (error.message.includes('401')) {
// トークンが無効な場合、ログインページにリダイレクト
window.location.href = 'login.php';
}
});
});
});
</script>
</body>
</html>