connect_error) { throw new Exception("データベース接続失敗: " . $conn->connect_error); } // 接続後の文字コード設定(重要) if (!$conn->set_charset("utf8mb4")) { throw new Exception("文字コード設定失敗: " . $conn->error); } $project = null; if ($drive_id) { $stmt = $conn->prepare("SELECT * FROM projects WHERE drive_id = ?"); if (!$stmt) { throw new Exception("プリペアドステートメント作成失敗: " . $conn->error); } $stmt->bind_param("s", $drive_id); if (!$stmt->execute()) { throw new Exception("クエリ実行失敗: " . $stmt->error); } $result = $stmt->get_result(); $project = $result->fetch_assoc(); if (!$project) { $project_name = str_replace('.sb3.txt', '', 'プロジェクト名'); } else { $project_name = $project['project_name']; } } // フォーム送信処理 if ($_SERVER['REQUEST_METHOD'] === 'POST') { $project_name = $_POST['project_name'] ?? ''; $description = $_POST['description'] ?? ''; $is_public = isset($_POST['is_public']); // 検索可能かどうか $is_accessible = isset($_POST['is_accessible']); // すべての人がアクセス可能かどうか $can_comment = isset($_POST['can_comment']); // コメント可能かどうか $password = $_POST['password'] ?? ''; // 表示用パスワード if (empty($project_name)) { $error = 'プロジェクト名は必須です'; } else { // 検索可能に設定する場合は強制的にすべての人がアクセス可能にする if ($is_public) { $is_accessible = true; $password = ''; // 検索可能の場合はパスワードを空にする } // コメント設定はすべての人がアクセス可能な場合のみ有効 if (!$is_accessible) { $can_comment = false; $password = ''; // 非公開の場合はパスワードを空にする } // 検索可能がオフで全ての人がアクセス可能な場合のみパスワードを保持 // それ以外は空にする if (!(!$is_public && $is_accessible)) { $password = ''; } // コメントデータをJSON形式で準備 $comment_data = [ 'can_comment' => $can_comment, 'history' => $project ? json_decode($project['comment'], true)['history'] ?? [] : [] ]; // ランダムな7桁のID生成 (重複確認付き) if (!$project) { $unique_id = false; $max_attempts = 10; $attempts = 0; while (!$unique_id && $attempts < $max_attempts) { $new_id = str_pad(mt_rand(0, 9999999), 7, '0', STR_PAD_LEFT); $check_stmt = $conn->prepare("SELECT id FROM projects WHERE id = ?"); $check_stmt->bind_param("s", $new_id); $check_stmt->execute(); $check_result = $check_stmt->get_result(); if ($check_result->num_rows === 0) { $unique_id = true; } $attempts++; $check_stmt->close(); } if (!$unique_id) { throw new Exception("一意のIDを生成できませんでした"); } } if ($project) { $stmt = $conn->prepare("UPDATE projects SET project_name = ?, description = ?, is_public = ?, comment = ?, password = ? WHERE id = ?"); if (!$stmt) { throw new Exception("更新ステートメント作成失敗: " . $conn->error); } $stmt->bind_param("ssisss", $project_name, $description, $is_public, json_encode($comment_data), $password, $project['id']); } else { $stmt = $conn->prepare("INSERT INTO projects (id, drive_id, project_name, description, author_name, author_id, is_public, comment, password) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); if (!$stmt) { throw new Exception("挿入ステートメント作成失敗: " . $conn->error); } $stmt->bind_param("ssssssiss", $new_id, $drive_id, $project_name, $description, $_SESSION['user_name'], $_SESSION['user_id'], $is_public, json_encode($comment_data), $password); } if (!$stmt->execute()) { throw new Exception("クエリ実行失敗: " . $stmt->error); } $success = 'プロジェクト情報を保存しました'; // サムネイルアップロード処理 // サムネイルアップロード処理 if (isset($_FILES['thumbnail']) && $_FILES['thumbnail']['error'] === UPLOAD_ERR_OK) { // ファイルサイズチェック (10MB以下) if ($_FILES['thumbnail']['size'] > 10 * 1024 * 1024) { $error = 'サムネイル画像は10MB以下にしてください'; } else { $thumbnail_tmp = $_FILES['thumbnail']['tmp_name']; $thumbnail_ext = strtolower(pathinfo($_FILES['thumbnail']['name'], PATHINFO_EXTENSION)); $thumbnail_name = "Scratch-Thumbnail-$drive_id." . ($thumbnail_ext === 'gif' ? 'gif' : 'png'); // 画像を読み込んで適切な形式に変換 if ($thumbnail_ext === 'gif') { $image = imagecreatefromgif($thumbnail_tmp); } elseif ($thumbnail_ext === 'png') { $image = imagecreatefrompng($thumbnail_tmp); } else { $image = imagecreatefromjpeg($thumbnail_tmp); } // 一時ファイルに保存 $temp_file = tempnam(sys_get_temp_dir(), 'thumb') . '.png'; imagepng($image, $temp_file); imagedestroy($image); $file_content = file_get_contents($temp_file); unlink($temp_file); // Google Driveにサムネイルをアップロード $access_token = $_SESSION['access_token']; // 既存のサムネイルを検索 $thumbnailResponse = file_get_contents("https://www.googleapis.com/drive/v3/files?q=name='$thumbnail_name'", false, stream_context_create([ 'http' => [ 'header' => "Authorization: Bearer $access_token\r\n" ] ])); $thumbnailData = json_decode($thumbnailResponse, true); $thumbnailId = null; if ($thumbnailData['files'] && count($thumbnailData['files']) > 0) { $thumbnailId = $thumbnailData['files'][0]['id']; } // アップロード処理 $boundary = '-------' . md5(microtime()); $mime_type = 'image/png'; // 常にPNG形式でアップロード $headers = [ 'Authorization: Bearer ' . $access_token, 'Content-Type: multipart/related; boundary="' . $boundary . '"' ]; $post_data = "--$boundary\r\n"; $post_data .= "Content-Type: application/json; charset=UTF-8\r\n\r\n"; $post_data .= json_encode([ 'name' => $thumbnail_name, 'mimeType' => $mime_type, 'parents' => ['root'] ]) . "\r\n"; $post_data .= "--$boundary\r\n"; $post_data .= "Content-Type: $mime_type\r\n\r\n"; $post_data .= $file_content . "\r\n"; $post_data .= "--$boundary--"; $ch = curl_init(); if ($thumbnailId) { // 既存のサムネイルを更新 curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/upload/drive/v3/files/$thumbnailId?uploadType=multipart"); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH"); } else { // 新しいサムネイルをアップロード curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"); curl_setopt($ch, CURLOPT_POST, 1); } curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode >= 200 && $httpCode < 300) { $thumbnailId = json_decode($response, true)['id'] ?? $thumbnailId; // サムネイルのアクセス権限設定 if ($thumbnailId) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/drive/v3/files/$thumbnailId/permissions"); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $access_token, 'Content-Type: application/json' ]); if ($is_accessible) { // すべての人がアクセス可能にする curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'role' => 'reader', 'type' => 'anyone' ])); } else { // アクセス権限を削除 (本人のみアクセス可能) // まず既存のanyone権限を取得 curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/drive/v3/files/$thumbnailId/permissions?fields=permissions(id,type)"); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); $response = curl_exec($ch); $permissions = json_decode($response, true)['permissions'] ?? []; // anyone権限を削除 foreach ($permissions as $perm) { if ($perm['type'] === 'anyone') { $ch_del = curl_init(); curl_setopt($ch_del, CURLOPT_URL, "https://www.googleapis.com/drive/v3/files/$thumbnailId/permissions/" . $perm['id']); curl_setopt($ch_del, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $access_token ]); curl_setopt($ch_del, CURLOPT_CUSTOMREQUEST, "DELETE"); curl_exec($ch_del); curl_close($ch_del); } } } curl_close($ch); } } else { error_log("サムネイルアップロード失敗: " . $response); } } } // Google Driveのアクセス権限設定 if (isset($_SESSION['access_token']) && $drive_id) { $access_token = $_SESSION['access_token']; // プロジェクトファイルのアクセス権限設定 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/drive/v3/files/$drive_id/permissions"); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $access_token, 'Content-Type: application/json' ]); if ($is_accessible) { // すべての人がアクセス可能にする curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'role' => 'reader', 'type' => 'anyone' ])); } else { // アクセス権限を削除 (本人のみアクセス可能) // まず既存のanyone権限を取得 curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/drive/v3/files/$drive_id/permissions?fields=permissions(id,type)"); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); $response = curl_exec($ch); $permissions = json_decode($response, true)['permissions'] ?? []; // anyone権限を削除 foreach ($permissions as $perm) { if ($perm['type'] === 'anyone') { $ch_del = curl_init(); curl_setopt($ch_del, CURLOPT_URL, "https://www.googleapis.com/drive/v3/files/$drive_id/permissions/" . $perm['id']); curl_setopt($ch_del, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $access_token ]); curl_setopt($ch_del, CURLOPT_CUSTOMREQUEST, "DELETE"); curl_exec($ch_del); curl_close($ch_del); } } } curl_close($ch); } $stmt = $conn->prepare("SELECT * FROM projects WHERE drive_id = ?"); if (!$stmt) { throw new Exception("選択ステートメント作成失敗: " . $conn->error); } $stmt->bind_param("s", $drive_id); if (!$stmt->execute()) { throw new Exception("クエリ実行失敗: " . $stmt->error); } $result = $stmt->get_result(); $project = $result->fetch_assoc(); } } $conn->close(); } catch (Exception $e) { error_log("[" . date('Y-m-d H:i:s') . "] エラー: " . $e->getMessage() . "\n", 3, __DIR__ . '/logs.txt'); $error = 'システムエラーが発生しました。管理者にお問い合わせください。'; http_response_code(500); } ?> プロジェクト共有 - Scratch School
申し訳ありません、システムエラーが発生しました。問題が解決しない場合は管理者にお問い合わせください。

プロジェクトを共有

プロジェクトサムネイル サムネイルなし

公開設定

>
>
>

非公開設定

>

※このパスワードはプロジェクトへのアクセス権限とは関係ありません。表示用のパスワードです。

Googleドライブからプロジェクトを選択してください。