=== ФАЙЛ: add_comment.php === "error", "message" => "Empty fields"]); exit(); } // ================================================================================= // [ИЗМЕНЕНО] АВТО-МОДЕРАЦИЯ И СИСТЕМА СТРАЙКОВ (С ИММУНИТЕТОМ) // ================================================================================= require_once 'moderation_dict.php'; // [НОВОЕ] Проверяем, есть ли у пользователя иммунитет (Админ/Тестер) $is_immune = has_moderation_immunity($user_id, $conn); // [ИЗМЕНЕНО] Запускаем фильтры только если у пользователя НЕТ иммунитета if (!$is_immune) { // 1. Проверяем, не забанен ли пользователь if (is_user_banned($user_id, $conn)) { echo json_encode(["status" => "error", "message" => "Вы заблокированы за нарушение правил."]); exit(); } // 2. Проверяем текст на плохие слова if (contains_bad_words($text)) { apply_strike($user_id, $conn); // Выдаем страйк echo json_encode(["status" => "error", "message" => "Сообщение содержит запрещенные слова."]); exit(); } } // ================================================================================= // Вставляем комментарий $stmt = $conn->prepare("INSERT INTO comments (user_id, post_id, text) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $user_id, $post_id, $text); if ($stmt->execute()) { $new_id = $stmt->insert_id; $stmt->close(); // Обновляем счетчик у поста (если таблица posts имеет эту колонку) // Используем try-catch логику через SQL, чтобы не крашилось если поста нет $conn->query("UPDATE posts SET comments_count = comments_count + 1 WHERE id = '$post_id'"); // ================================================================================= // [НОВОЕ] ХИРУРГИЧЕСКАЯ ВСТАВКА: УВЕДОМЛЕНИЕ АВТОРУ ПОСТА // ================================================================================= // 1. Узнаем, кто автор поста $post_res = $conn->query("SELECT author_id, image_url FROM posts WHERE id = '$post_id'"); if ($post_res && $p_row = $post_res->fetch_assoc()) { $author_id = $p_row['author_id']; $post_img = $p_row['image_url']; // Если автор поста - не я сам (чтобы не спамить себе) if ($author_id != $user_id) { // 2. Узнаем имя и аватар комментатора (меня) $sender_name = "Пользователь"; $sender_ava = ""; // ВАЖНО: Берем username, как в сообщениях $u_res = $conn->query("SELECT username, avatar_url FROM users WHERE id = '$user_id'"); if ($u_res && $u_row = $u_res->fetch_assoc()) { $sender_name = !empty($u_row['username']) ? $u_row['username'] : "Пользователь"; $sender_ava = $u_row['avatar_url']; // Исправляем путь к аватару, если он не полный if (!empty($sender_ava) && strpos($sender_ava, 'http') === false) { $sender_ava = "http://216.250.14.77/api/v1/uploads/" . $sender_ava; } } // 3. Формируем текст для Пуша $short_text = mb_substr($text, 0, 40) . (mb_strlen($text) > 40 ? "..." : ""); $notif_msg = "Прокомментировал: " . $short_text; // 4. Записываем в таблицу notifications // type='comment', action_id=ID поста (чтобы открыть пост при клике) $notif_sql = "INSERT INTO notifications (user_id, sender_id, type, action_id, title, message, user_avatar, post_preview, created_at, is_read) VALUES (?, ?, 'comment', ?, ?, ?, ?, ?, NOW(), 0)"; $n_stmt = $conn->prepare($notif_sql); if ($n_stmt) { // sssssss = 7 строк $n_stmt->bind_param("sssssss", $author_id, $user_id, $post_id, $sender_name, $notif_msg, $sender_ava, $post_img); $n_stmt->execute(); $n_stmt->close(); } } } // ================================================================================= echo json_encode([ "status" => "success", "message" => "Comment added", "id" => (string)$new_id // Приводим к строке для Android ]); } else { echo json_encode(["status" => "error", "message" => "DB Error: " . $conn->error]); } $conn->close(); ?> === ФАЙЛ: add_favorite.php === "error", "message" => "Missing fields"]); exit(); } // [НОВОЕ] Создаем безопасную переменную для UPDATE запроса ниже $safe_item = $conn->real_escape_string($item_id); // Используем INSERT IGNORE, чтобы не было ошибки, если лайк уже стоит $sql = "INSERT IGNORE INTO favorites (user_id, item_id, type) VALUES (?, ?, ?)"; $stmt = $conn->prepare($sql); $stmt->bind_param("sss", $user_id, $item_id, $type); if ($stmt->execute()) { // [НОВОЕ] ГЛАВНОЕ ИЗМЕНЕНИЕ: // Мы увеличиваем счетчик ТОЛЬКО если запись реально добавилась (affected_rows > 0) // Если лайк уже стоял, affected_rows будет 0, и мы не накрутим лишнего. if ($stmt->affected_rows > 0) { if ($type === 'STORE_PRODUCT' || $type === 'grand') { // Обновляем статистику магазина $conn->query("UPDATE grand_products SET favorites_count = favorites_count + 1 WHERE id = '$safe_item'"); } elseif ($type === 'PRODUCT' || $type === 'private') { // Обновляем статистику частного объявления $conn->query("UPDATE products SET favorites_count = favorites_count + 1 WHERE id = '$safe_item'"); } } echo json_encode(["status" => "success", "message" => "Added"]); } else { echo json_encode(["status" => "error", "message" => "DB Error: " . $conn->error]); } $conn->close(); ?> === ФАЙЛ: add_grand_product.php === "error", "message" => "Critical Error: Owner ID is missing"]); exit(); } // 4. ЗАГРУЗКА ФОТОГРАФИЙ (МАССИВ) $uploaded_urls = []; // [ИЗМЕНЕНО] Используем __DIR__ (Абсолютный путь), чтобы сервер точно знал, куда класть $target_dir = __DIR__ . "/uploads/"; // [НОВОЕ] Если папки нет — создаем её с правами записи if (!file_exists($target_dir)) { mkdir($target_dir, 0777, true); } // ВАЖНО: IP вашего сервера (оставляем как было) $base_url = "http://216.250.14.77/api/v1/uploads/"; if (isset($_FILES['mediaFiles'])) { $file_count = count($_FILES['mediaFiles']['name']); // [НОВОЕ] Пишем в лог, сколько файлов пришло file_put_contents('debug_grand_products.txt', " -> Files received: $file_count\n", FILE_APPEND); for ($i = 0; $i < $file_count; $i++) { $error = $_FILES['mediaFiles']['error'][$i]; if ($error === UPLOAD_ERR_OK) { $tmp_name = $_FILES['mediaFiles']['tmp_name'][$i]; $original_name = basename($_FILES['mediaFiles']['name'][$i]); $ext = strtolower(pathinfo($original_name, PATHINFO_EXTENSION)); // Уникальное имя файла $new_name = "img_" . $product_id . "_" . $i . "." . $ext; $target_file = $target_dir . $new_name; if (move_uploaded_file($tmp_name, $target_file)) { $uploaded_urls[] = $base_url . $new_name; } else { // Логируем ошибку перемещения file_put_contents('debug_grand_products.txt', "ERROR: Failed to move file to $target_file\n", FILE_APPEND); } } else { // [НОВОЕ] Логируем, если файл слишком большой или другая ошибка PHP file_put_contents('debug_grand_products.txt', "UPLOAD ERROR Code: $error for file index $i\n", FILE_APPEND); } } } // Превращаем массив ссылок в JSON строку ["url1", "url2"] $images_json = json_encode($uploaded_urls, JSON_UNESCAPED_SLASHES); // 5. ЗАПИСЬ В БАЗУ ДАННЫХ // Готовим SQL (32 параметра!) $sql = "INSERT INTO grand_products ( id, store_id, owner_id, name, description, sku, price, old_price, is_sale, category, sub_category, brand, images_json, item_condition, is_new, sizes, color, country, memory, auto_year, auto_mileage, auto_engine, auto_body, auto_gearbox, allow_likes ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )"; $stmt = $conn->prepare($sql); if ($stmt) { // Типы данных для bind_param (s=string, d=double, i=int) // Внимательно считаем параметры! Всего 25 переменных. // id(s), store_id(s), owner_id(s), name(s), description(s), sku(s) -> ssssss // price(d), old_price(d), is_sale(i) -> ddi // category(s), sub_category(s), brand(s) -> sss // images_json(s) -> s // item_condition(s), is_new(i), sizes(s), color(s), country(s), memory(s) -> sissss // auto_year(s), auto_mileage(s), auto_engine(s), auto_body(s), auto_gearbox(s) -> sssss // allow_likes(i) -> i // Итого строка типов: "ssssssddissssissssssssssi" $stmt->bind_param( "ssssssddissssissssssssssi", $product_id, $store_id, $owner_id, $name, $description, $sku, $price, $old_price, $is_sale, $category, $sub_category, $brand, $images_json, $item_condition, $is_new, $sizes, $color, $country, $memory, $auto_year, $auto_mileage, $auto_engine, $auto_body, $auto_gearbox, $allow_likes ); if ($stmt->execute()) { // --- 🚀 БЛОК УВЕДОМЛЕНИЙ (НАЧАЛО) --- try { // 1. ОПРЕДЕЛЯЕМ ОТПРАВИТЕЛЯ (Магазин или Владелец) $sender_name = "Магазин"; $sender_avatar = ""; $default_avatar = "https://cdn-icons-png.flaticon.com/512/149/149071.png"; // Пытаемся найти данные МАГАЗИНА (Таблица 'shops', колонка 'avatar_url') if (!empty($store_id)) { $store_sql = "SELECT name, avatar_url FROM shops WHERE id = '$store_id'"; $s_res = $conn->query($store_sql); if ($s_res && $s_row = $s_res->fetch_assoc()) { if (!empty($s_row['name'])) $sender_name = $s_row['name']; if (!empty($s_row['avatar_url'])) { $raw_logo = $s_row['avatar_url']; $sender_avatar = (strpos($raw_logo, 'http') === 0) ? $raw_logo : $base_url . $raw_logo; } } } // Если это не магазин, берем данные ВЛАДЕЛЬЦА if (empty($sender_avatar)) { $owner_sql = "SELECT name, avatar_url FROM users WHERE id = '$owner_id'"; $o_res = $conn->query($owner_sql); if ($o_res && $o_row = $o_res->fetch_assoc()) { if (empty($store_id) && !empty($o_row['name'])) $sender_name = $o_row['name']; if (!empty($o_row['avatar_url'])) { $raw_ava = $o_row['avatar_url']; $sender_avatar = (strpos($raw_ava, 'http') === 0) ? $raw_ava : $base_url . $raw_ava; } } } if (empty($sender_avatar)) $sender_avatar = $default_avatar; // 2. КАРТИНКА ТОВАРА $preview_img = (!empty($uploaded_urls)) ? $uploaded_urls[0] : ""; // 3. РАССЫЛКА ПОДПИСЧИКАМ (Таблица 'follows') $subs_sql = "SELECT follower_id FROM follows WHERE following_id = '$owner_id'"; $subs_res = $conn->query($subs_sql); if ($subs_res && $subs_res->num_rows > 0) { $notif_title = $sender_name; $notif_msg = "Новинка в витрине: " . $name; // ВАЖНО: Используем 'new_ad', чтобы на телефоне открывалось как обычный товар $notif_type = "new_grand_ad"; $ins = $conn->prepare("INSERT INTO notifications (sender_id, user_id, type, action_id, title, message, post_preview, user_avatar, created_at, is_read) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), 0)"); if ($ins) { while ($sub = $subs_res->fetch_assoc()) { $target_id = $sub['follower_id']; $ins->bind_param("ssssssss", $owner_id, $target_id, $notif_type, $product_id, $notif_title, $notif_msg, $preview_img, $sender_avatar); $ins->execute(); } $ins->close(); file_put_contents('debug_grand_products.txt', " -> Notifications Sent to " . $subs_res->num_rows . " users.\n", FILE_APPEND); } } } catch (Exception $e) { file_put_contents('debug_grand_products.txt', "NOTIF ERROR: " . $e->getMessage() . "\n", FILE_APPEND); } // --- БЛОК УВЕДОМЛЕНИЙ (КОНЕЦ) --- echo json_encode([ "status" => "success", "message" => "Product published successfully", "product_id" => $product_id ]); } else { $error_msg = "DB Execute Error: " . $stmt->error; echo json_encode(["status" => "error", "message" => $error_msg]); file_put_contents('debug_grand_products.txt', $error_msg . "\n", FILE_APPEND); } $stmt->close(); } else { $error_msg = "DB Prepare Error: " . $conn->error; echo json_encode(["status" => "error", "message" => $error_msg]); file_put_contents('debug_grand_products.txt', $error_msg . "\n", FILE_APPEND); } $conn->close(); ?> === ФАЙЛ: add_highlight.php === "error", "message" => "Only POST allowed"]); exit(); } $id = $_POST['id'] ?? ''; $user_id = $_POST['user_id'] ?? ''; $title = $_POST['title'] ?? ''; $stories_json = $_POST['stories_json'] ?? '[]'; $cover_url_input = $_POST['cover_url'] ?? ''; // Настраиваем базовый URL сервера $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http"; $host = $_SERVER['HTTP_HOST']; $SERVER_URL = "$protocol://$host/api/v1"; // 2. ЛОГИКА ОБЛОЖКИ (Главное исправление) $final_cover_url = $cover_url_input; // По умолчанию берем то, что пришло текстом // Проверяем, пришел ли ФАЙЛ (картинка из галереи) if (isset($_FILES['cover_file']) && $_FILES['cover_file']['error'] === UPLOAD_ERR_OK) { $tmpName = $_FILES['cover_file']['tmp_name']; $fileName = $_FILES['cover_file']['name']; // Безопасное расширение $extension = pathinfo($fileName, PATHINFO_EXTENSION); if (!$extension) $extension = 'jpg'; // Генерируем уникальное имя $newFileName = "highlight_cover_" . uniqid() . "_" . time() . "." . $extension; $targetDir = __DIR__ . "/uploads/"; // Используем абсолютный путь // Создаем папку, если нет if (!file_exists($targetDir)) { mkdir($targetDir, 0777, true); } $targetPath = $targetDir . $newFileName; // Перемещаем файл if (move_uploaded_file($tmpName, $targetPath)) { // Если успех - сохраняем ССЫЛКУ НА СЕРВЕР, а не локальный путь $final_cover_url = $SERVER_URL . "/uploads/" . $newFileName; } } // 3. ПРОВЕРКА ДАННЫХ if (empty($id) || empty($user_id)) { echo json_encode(["status" => "error", "message" => "Missing fields"]); exit(); } // 4. СОХРАНЕНИЕ В БД $created_at = time() * 1000; $stmt = $conn->prepare("INSERT INTO user_highlights (id, user_id, title, cover_url, stories_json, created_at) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE title=VALUES(title), cover_url=VALUES(cover_url), stories_json=VALUES(stories_json)"); if ($stmt) { $stmt->bind_param("sssssi", $id, $user_id, $title, $final_cover_url, $stories_json, $created_at); if ($stmt->execute()) { echo json_encode(["status" => "success", "message" => "Highlight saved", "cover" => $final_cover_url]); } else { echo json_encode(["status" => "error", "message" => "DB Error: " . $stmt->error]); } $stmt->close(); } else { echo json_encode(["status" => "error", "message" => "Prepare failed: " . $conn->error]); } $conn->close(); ?> === ФАЙЛ: add_post.php === "", "error" => "Missing fields"]); exit(); } $id = $_POST['id']; $authorId = $_POST['authorId']; $content = $_POST['content'] ?? ''; $title = $_POST['title'] ?? ''; $type = $_POST['type'] ?? 'IMAGE'; $timestamp = $_POST['timestamp'] ?? time() * 1000; $location = $_POST['location'] ?? null; // [ИЗМЕНЕНО] ЖЕСТКОЕ ПРИВЕДЕНИЕ К ЦЕЛОМУ ЧИСЛУ (Для MySQL: 0 или 1) $rawAllow = $_POST['allow_comments'] ?? 'true'; // Проверяем все варианты "лжи" if ($rawAllow === 'false' || $rawAllow === '0' || $rawAllow === 0) { $allowComments = 0; // Запишем 0 (INT) } else { $allowComments = 1; // Запишем 1 (INT) } // [ЖУЧОК] Пишем в лог, что решили file_put_contents($logFile, "ALLOW_COMMENTS DECISION: Input='$rawAllow' -> Saved=$allowComments\n", FILE_APPEND); $musicTrackName = null; if (isset($_POST['music_track_name']) && !empty($_POST['music_track_name'])) { $musicTrackName = trim($_POST['music_track_name']); file_put_contents($logFile, "MUSIC FOUND: $musicTrackName\n", FILE_APPEND); } else { file_put_contents($logFile, "MUSIC EMPTY: Client sent nothing.\n", FILE_APPEND); } // URL сервера $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http"; $host = $_SERVER['HTTP_HOST']; $SERVER_URL = "$protocol://$host/api/v1"; // 3. ОБРАБОТКА ЗАГРУЗКИ МЕДИА (ФОТО/ВИДЕО) $uploadedMediaUrls = []; if (isset($_FILES['mediaFiles'])) { $files = $_FILES['mediaFiles']; if (is_array($files['name'])) { $count = count($files['name']); for ($i = 0; $i < $count; $i++) { $fileName = $files['name'][$i]; $tmpName = $files['tmp_name'][$i]; $error = $files['error'][$i]; if ($error === UPLOAD_ERR_OK) { $extension = pathinfo($fileName, PATHINFO_EXTENSION); if (!$extension) $extension = "jpg"; $newFileName = uniqid() . "_" . time() . "_" . $i . "." . $extension; $targetDir = "/var/www/html/api/v1/uploads/"; if (!file_exists($targetDir)) mkdir($targetDir, 0777, true); $targetPath = $targetDir . $newFileName; if (move_uploaded_file($tmpName, $targetPath)) { $uploadedMediaUrls[] = $SERVER_URL . "/uploads/" . $newFileName; } } } } } // 4. ОПРЕДЕЛЯЕМ TИП КОНТЕНТА (ВИДЕО ИЛИ КАРТИНКА) $imageUrl = null; $videoUrl = null; if (count($uploadedMediaUrls) > 0) { $firstFile = $uploadedMediaUrls[0]; if (preg_match('/\.(mp4|mov|avi|mkv)$/i', $firstFile)) { $videoUrl = $firstFile; } else { $imageUrl = $firstFile; } } $mediaUrlsJson = json_encode($uploadedMediaUrls); $tagsJson = json_encode([]); // 5. [НОВОЕ] ОБРАБОТКА МУЗЫКИ (MP3) - БРОНЕБОЙНЫЙ ВАРИАНТ (ТОЛЬКО ОДИН РАЗ!) $audioUrl = null; if (isset($_FILES['audio_file']) && $_FILES['audio_file']['error'] === UPLOAD_ERR_OK) { file_put_contents($logFile, "\n--- AUDIO ATTEMPT (COPY METHOD) ---\n", FILE_APPEND); $audioFile = $_FILES['audio_file']; $ext = pathinfo($audioFile['name'], PATHINFO_EXTENSION); if (!$ext) $ext = "mp3"; $newAudioName = "audio_" . uniqid() . "_" . time() . "." . $ext; $targetAudioDir = "/var/www/html/api/v1/uploads/audio/"; $targetAudioPath = $targetAudioDir . $newAudioName; // 1. Создаем папку, если нет if (!file_exists($targetAudioDir)) { mkdir($targetAudioDir, 0777, true); chmod($targetAudioDir, 0777); } // 2. Пытаемся переместить стандартным способом if (move_uploaded_file($audioFile['tmp_name'], $targetAudioPath)) { $audioUrl = $SERVER_URL . "/uploads/audio/" . $newAudioName; file_put_contents($logFile, "SUCCESS: Moved via standard method.\n", FILE_APPEND); } // 3. ЕСЛИ НЕ ВЫШЛО -> ИСПОЛЬЗУЕМ ПЛАН "Б" (COPY) elseif (copy($audioFile['tmp_name'], $targetAudioPath)) { $audioUrl = $SERVER_URL . "/uploads/audio/" . $newAudioName; file_put_contents($logFile, "SUCCESS: Moved via COPY method (Plan B).\n", FILE_APPEND); // Удаляем временный файл вручную unlink($audioFile['tmp_name']); } // 4. ЕСЛИ И ЭТО НЕ ВЫШЛО -> ПЕЧАТАЕМ ОШИБКУ else { $error = error_get_last(); file_put_contents($logFile, "FATAL FAIL: Both Move and Copy failed.\n", FILE_APPEND); file_put_contents($logFile, "System Error: " . $error['message'] . "\n", FILE_APPEND); } // Финальная полировка прав if ($audioUrl) { chmod($targetAudioPath, 0644); } } else { // Логируем, если файл вообще не пришел if (isset($_FILES['audio_file'])) { file_put_contents($logFile, "UPLOAD ERROR code: " . $_FILES['audio_file']['error'] . "\n", FILE_APPEND); } } // 6. СОХРАНЕНИЕ В БАЗУ $sql = "INSERT INTO posts (id, author_id, title, content, image_url, video_url, media_urls, tags, type, created_at, last_updated, location, music_track_name, audio_url, allow_comments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE content=VALUES(content), title=VALUES(title), image_url=VALUES(image_url), video_url=VALUES(video_url), media_urls=VALUES(media_urls), last_updated=VALUES(last_updated), location=VALUES(location), music_track_name=VALUES(music_track_name), audio_url=VALUES(audio_url), allow_comments=VALUES(allow_comments)"; $stmt = $conn->prepare($sql); if (!$stmt) { echo json_encode(["id" => $id, "error" => "SQL Error: " . $conn->error]); exit(); } // [ИЗМЕНЕНО] НОВАЯ СТРОКА ТИПОВ: "ssssssssssssssi" (15 символов) // 14 штук 's' (включая timestamp, чтобы не было переполнения числа) // 1 штука 'i' в конце (для allowComments) $stmt->bind_param("ssssssssssssssi", $id, $authorId, $title, $content, $imageUrl, $videoUrl, $mediaUrlsJson, $tagsJson, $type, $timestamp, // [ВАЖНО] s (строка), так безопаснее для больших чисел $timestamp, // [ВАЖНО] s (строка) $location, $musicTrackName, $audioUrl, $allowComments // [ВАЖНО] i (число 0 или 1) ); // 6. СОХРАНЕНИЕ В БАЗУ if ($stmt->execute()) { // [НОВОЕ] Срочно получаем актуальные данные автора для Android $userRes = $conn->query("SELECT name, avatar_url FROM users WHERE id = '$authorId'"); $uData = $userRes->fetch_assoc(); $authorName = $uData['name'] ?? 'Пользователь'; $authorAvatar = $uData['avatar_url'] ?? ''; $responsePost = [ "id" => $id, "authorId" => $authorId, "authorName" => $authorName, "authorAvatar" => $authorAvatar, "title" => $title, "content" => $content, "imageUrl" => $imageUrl, "videoUrl" => $videoUrl, "mediaUrls" => $uploadedMediaUrls, "audioUrl" => $audioUrl, "musicTrackName" => $musicTrackName, "type" => $type, "timestamp" => (int)$timestamp, "location" => $location, // [ХИРУРГИЯ] Возвращаем true/false для Android "allowComments" => ($allowComments === 1), "likes" => 0, "commentsCount" => 0, "isLiked" => false, "isSaved" => false ]; echo json_encode($responsePost); } else { // Ловим ошибки SQL http_response_code(500); echo json_encode(["status" => "error", "message" => "Database Error: " . $stmt->error]); file_put_contents($logFile, "SQL ERROR: " . $stmt->error . "\n", FILE_APPEND); } ?> === ФАЙЛ: add_product.php === set_charset("utf8mb4"); function sendJson($data) { ob_end_clean(); echo json_encode($data, JSON_UNESCAPED_UNICODE); exit(); } if ($_SERVER['REQUEST_METHOD'] !== 'POST') { sendJson(array("status" => "error", "message" => "Only POST allowed")); } // 1. DATA INPUT $id = isset($_POST['id']) ? $_POST['id'] : uniqid('prod_', true); $owner_id = isset($_POST['owner_id']) ? $_POST['owner_id'] : ''; $store_id = isset($_POST['store_id']) && $_POST['store_id'] !== 'null' ? $_POST['store_id'] : NULL; $name = isset($_POST['name']) ? $_POST['name'] : 'No Name'; $description = isset($_POST['description']) ? $_POST['description'] : ''; $price = isset($_POST['price']) ? (double)$_POST['price'] : 0.0; $old_price = isset($_POST['old_price']) && $_POST['old_price'] > 0 ? (double)$_POST['old_price'] : NULL; $category = isset($_POST['category']) ? $_POST['category'] : 'Other'; $subcategory = isset($_POST['subcategory']) ? $_POST['subcategory'] : ''; $brand = isset($_POST['brand']) ? $_POST['brand'] : ''; $sku = isset($_POST['sku']) ? $_POST['sku'] : ''; logToDebug("📥 Input: ID=$id, Owner=$owner_id, Name=$name"); // 2. IMAGE PROCESSING $upload_dir = 'uploads/'; if (!file_exists($upload_dir)) { mkdir($upload_dir, 0777, true); } $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http"; $server_base = "$protocol://" . $_SERVER['HTTP_HOST'] . "/api/v1/uploads/"; $image_urls = array(); if (isset($_FILES['mediaFiles'])) { $files = $_FILES['mediaFiles']; logToDebug("📸 Files received: " . count($files['name'])); if (is_array($files['name'])) { $count = count($files['name']); for ($i = 0; $i < $count; $i++) { if ($files['error'][$i] === UPLOAD_ERR_OK) { $tmp_name = $files['tmp_name'][$i]; $name_part = basename($files['name'][$i]); $extension = strtolower(pathinfo($name_part, PATHINFO_EXTENSION)); $allowed = array('jpg', 'jpeg', 'png', 'webp', 'mp4', 'mov'); if (in_array($extension, $allowed)) { $new_name = "prod_" . time() . "_{$i}." . $extension; $target_path = $upload_dir . $new_name; if (move_uploaded_file($tmp_name, $target_path)) { $image_urls[] = $server_base . $new_name; logToDebug("✅ Uploaded: $new_name"); } else { logToDebug("❌ Move failed: $name_part"); } } } else { logToDebug("❌ PHP Upload Error Code: " . $files['error'][$i]); } } } } else { logToDebug("⚠️ No 'mediaFiles' in POST request"); } $images_json = json_encode($image_urls, JSON_UNESCAPED_SLASHES); logToDebug("🖼 JSON Images: $images_json"); $created_at = round(microtime(true) * 1000); // ============================================================================== // [ИЗМЕНЕНО] Бронебойная проверка на первое частное объявление через таблицу users // ============================================================================== $is_first_private_ad = false; $bonus_amount = 0; if (empty($store_id)) { // Если это частное объявление (нет магазина) // Смотрим исторический флаг в таблице users $check_sql = "SELECT received_ad_bonus FROM users WHERE id = ? LIMIT 1"; $c_stmt = $conn->prepare($check_sql); if ($c_stmt) { $c_stmt->bind_param("s", $owner_id); $c_stmt->execute(); $c_res = $c_stmt->get_result(); if ($row = $c_res->fetch_assoc()) { // Если бонус еще НИКОГДА не выдавался (значение 0) if ($row['received_ad_bonus'] == 0) { $is_first_private_ad = true; $bonus_amount = 200; // 1. Автоматически пополняем рекламный баланс $conn->query("INSERT INTO ad_wallets (owner_id, balance) VALUES ('$owner_id', 200) ON DUPLICATE KEY UPDATE balance = balance + 200"); logToDebug("🎁 БОНУС: 200 TMT автоматически начислены пользователю $owner_id"); // 2. Создаем системное уведомление $notif_title = "Подарок от CLIK 🎁"; $notif_msg = "Вам начислено 200 TMT на рекламный баланс за первое частное объявление!"; $notif_sql = "INSERT INTO notifications (sender_id, user_id, type, action_id, title, message, post_preview, user_avatar, created_at, is_read) VALUES ('$owner_id', '$owner_id', 'system_bonus', 'wallet', '$notif_title', '$notif_msg', '', '', NOW(), 0)"; $conn->query($notif_sql); // 3. СТАВИМ ФЛАГ (Блокируем абуз навсегда) $conn->query("UPDATE users SET received_ad_bonus = 1 WHERE id = '$owner_id'"); } else { logToDebug("⚠️ БОНУС 200 TMT ОТКЛОНЕН: Пользователь $owner_id уже получал его ранее (абуз предотвращен)."); } } $c_stmt->close(); } } // ============================================================================== // 3. INSERT PRODUCT INTO DB $sql = "INSERT INTO products (id, owner_id, store_id, name, description, price, old_price, category, sub_category, image_urls, created_at, brand, sku, is_new) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)"; $stmt = $conn->prepare($sql); if (!$stmt) { logToDebug("❌ SQL PREPARE ERROR: " . $conn->error); sendJson(array("status" => "error", "message" => "SQL Prepare Error")); } $stmt->bind_param("sssssddssssss", $id, $owner_id, $store_id, $name, $description, $price, $old_price, $category, $subcategory, $images_json, $created_at, $brand, $sku); if ($stmt->execute()) { logToDebug("✅ Product DB Insert Success. ID: $id"); // --- NOTIFICATION BLOCK START --- try { logToDebug("--- NOTIFICATION LOGIC START for User: $owner_id ---"); // [DEBUG] A. AVATAR FETCH (FIXED COLUMN NAME) $seller_avatar = ""; // [CORRECTION] We select 'avatar_url' because that is where update_profile.php saves it $avatar_sql = "SELECT avatar_url FROM users WHERE id = '$owner_id'"; $av_res = $conn->query($avatar_sql); if ($av_res) { if ($row_av = $av_res->fetch_assoc()) { // [CORRECTION] Read 'avatar_url', not 'avatar' $raw_avatar = isset($row_av['avatar_url']) ? $row_av['avatar_url'] : ""; logToDebug(" -> Raw Avatar from DB: '$raw_avatar'"); if (!empty($raw_avatar)) { // update_profile.php saves the full URL, so we usually just use it directly. // But we add a check just in case it's a relative path. if (strpos($raw_avatar, 'http') === 0) { $seller_avatar = $raw_avatar; } else { $seller_avatar = $server_base . $raw_avatar; } } else { logToDebug(" -> User has no avatar_url in DB. Sending empty."); } } else { logToDebug(" -> Avatar Query returned 0 rows"); } } else { logToDebug(" -> Avatar Query Failed: " . $conn->error); } logToDebug(" -> Final Seller Avatar: '$seller_avatar'"); // [DEBUG] B. PRODUCT PREVIEW IMAGE $preview_img = ""; if (!empty($image_urls) && count($image_urls) > 0) { $preview_img = $image_urls[0]; } else { // Fallback: decode JSON if array is somehow lost $decoded = json_decode($images_json, true); if (!empty($decoded) && count($decoded) > 0) { $preview_img = $decoded[0]; } } logToDebug(" -> Final Preview Image: '$preview_img'"); // CRITICAL CHECK // [ИЗМЕНЕНО] ПРОФЕССИОНАЛЬНЫЙ СТИЛЬ УВЕДОМЛЕНИЙ $notif_type = "new_ad"; // 1. Получаем имя продавца для заголовка $seller_name = "Продавец"; // Значение по умолчанию $name_sql = "SELECT name FROM users WHERE id = '$owner_id'"; $name_res = $conn->query($name_sql); if ($name_res && $row_nm = $name_res->fetch_assoc()) { if (!empty($row_nm['name'])) { $seller_name = $row_nm['name']; } } // 2. Формируем красивые тексты (Instagram стиль) $notif_title = $seller_name; // Текст: Действие + Название товара (например, "Опубликовал(а): iPhone 15 Pro") $notif_msg = "Добавил(а) товар: " . $name; // [DEBUG] C. FIND SUBSCRIBERS $sql_subs = "SELECT follower_id FROM follows WHERE following_id = '$owner_id'"; $res_subs = $conn->query($sql_subs); if ($res_subs && $res_subs->num_rows > 0) { logToDebug(" -> Subscribers found: " . $res_subs->num_rows); $ins_n = $conn->prepare("INSERT INTO notifications (sender_id, user_id, type, action_id, title, message, post_preview, user_avatar, created_at, is_read) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), 0)"); if ($ins_n) { while ($row = $res_subs->fetch_assoc()) { $target_user_id = $row['follower_id']; // DEBUG: Log exactly what we are binding // logToDebug(" -> Inserting for $target_user_id | Av: $seller_avatar | Img: $preview_img"); $ins_n->bind_param("ssssssss", $owner_id, $target_user_id, $notif_type, $id, $notif_title, $notif_msg, $preview_img, $seller_avatar); if (!$ins_n->execute()) { logToDebug("❌ Insert Notification Failed: " . $ins_n->error); } } $ins_n->close(); logToDebug("🏁 Notifications Sent Successfully."); } else { logToDebug("❌ Prepare Notification Failed: " . $conn->error); } } else { logToDebug("⚠️ No subscribers found for seller."); } } catch (Exception $e) { logToDebug("🔥 Notification Exception: " . $e->getMessage()); } // ------------------------------------ // ============================================================================== // [ИЗМЕНЕНО] Возвращаем флаги бонуса в Android, чтобы показать Snackbar // ============================================================================== sendJson(array( "status" => "success", "message" => "Product Added", "product_id" => $id, "is_first_private_ad" => $is_first_private_ad, // [НОВОЕ] Передаем флаг "bonus_amount" => $bonus_amount // [НОВОЕ] Передаем сумму (200) )); } else { logToDebug("❌ SQL Error (Insert Product): " . $stmt->error); sendJson(array("status" => "error", "message" => "SQL Error: " . $stmt->error)); } $stmt->close(); $conn->close(); ?> === ФАЙЛ: add_reaction.php === "error", "message" => "Missing parameters"]); exit(); } // 1. Проверяем, есть ли уже реакция от этого пользователя на это сообщение $check_sql = "SELECT id, emoji FROM message_reactions WHERE message_id = ? AND user_id = ?"; $stmt = $conn->prepare($check_sql); $stmt->bind_param("is", $message_id, $user_id); $stmt->execute(); $result = $stmt->get_result(); if ($row = $result->fetch_assoc()) { if ($row['emoji'] === $emoji) { // Если эмодзи тот же самый - значит мы УБИРАЕМ реакцию (Toggle) $del_sql = "DELETE FROM message_reactions WHERE id = ?"; $del_stmt = $conn->prepare($del_sql); $del_stmt->bind_param("i", $row['id']); $del_stmt->execute(); $del_stmt->close(); } else { // Если эмодзи другой - ОБНОВЛЯЕМ (меняем лайк на смех, например) $upd_sql = "UPDATE message_reactions SET emoji = ? WHERE id = ?"; $upd_stmt = $conn->prepare($upd_sql); $upd_stmt->bind_param("si", $emoji, $row['id']); $upd_stmt->execute(); $upd_stmt->close(); } } else { // Если реакций нет - ДОБАВЛЯЕМ новую $ins_sql = "INSERT INTO message_reactions (message_id, user_id, emoji) VALUES (?, ?, ?)"; $ins_stmt = $conn->prepare($ins_sql); $ins_stmt->bind_param("iss", $message_id, $user_id, $emoji); $ins_stmt->execute(); $ins_stmt->close(); } $stmt->close(); $conn->close(); echo json_encode(["status" => "success", "message" => "Reaction processed"]); ?> === ФАЙЛ: add_store.php === "error", "message" => "Missing required fields (owner_id, name)"]); exit(); } // 3. ФУНКЦИЯ ЗАГРУЗКИ ФАЙЛОВ // Возвращает полный URL загруженного файла или NULL function uploadStoreImage($fileKey, $prefix) { // Папка должна быть writable (обычно uploads/ уже настроена) $target_dir = "uploads/"; // ВАЖНО: Укажите здесь ваш реальный IP или Домен, если он изменится $server_url = "http://216.250.14.77/api/v1/uploads/"; if (isset($_FILES[$fileKey]) && $_FILES[$fileKey]['error'] === UPLOAD_ERR_OK) { $temp_name = $_FILES[$fileKey]['tmp_name']; $original_name = basename($_FILES[$fileKey]['name']); $imageFileType = strtolower(pathinfo($original_name, PATHINFO_EXTENSION)); // Генерируем уникальное имя: store_logo_TIMESTAMP_RAND.jpg $new_name = $prefix . "_" . time() . "_" . rand(100, 999) . "." . $imageFileType; $target_file = $target_dir . $new_name; if (move_uploaded_file($temp_name, $target_file)) { return $server_url . $new_name; } else { // Логируем ошибку загрузки file_put_contents('debug_stores.txt', "UPLOAD ERROR: Failed to move $fileKey\n", FILE_APPEND); } } return null; // Если файла нет или ошибка } // 4. ЗАГРУЖАЕМ КАРТИНКИ $logo_url = uploadStoreImage('logo', 'store_logo'); // Ключ 'logo' из Android $cover_url = uploadStoreImage('cover', 'store_cover'); // Ключ 'cover' из Android // ============================================================================== // [ИЗМЕНЕНО] Бронебойная проверка на первый магазин (Витрину) через таблицу users // ============================================================================== $is_first_store = false; $check_sql = "SELECT received_store_bonus FROM users WHERE id = ? LIMIT 1"; $c_stmt = $conn->prepare($check_sql); if ($c_stmt) { $c_stmt->bind_param("s", $owner_id); $c_stmt->execute(); $c_res = $c_stmt->get_result(); // [ИСПРАВЛЕНО] Убран лишний вызов get_result() if ($row = $c_res->fetch_assoc()) { // Если бонус за магазин еще НИКОГДА не выдавался (значение 0) if ($row['received_store_bonus'] == 0) { $is_first_store = true; // Приложение покажет окно выбора бонуса // СТАВИМ ФЛАГ СРАЗУ ЗДЕСЬ (чтобы заблокировать абуз даже до выбора бонуса) $conn->query("UPDATE users SET received_store_bonus = 1 WHERE id = '$owner_id'"); file_put_contents('debug_stores.txt', "🎁 ПЕРВЫЙ МАГАЗИН: Разрешаем выбор бонуса пользователю $owner_id\n", FILE_APPEND); } else { file_put_contents('debug_stores.txt', "⚠️ ПЕРВЫЙ МАГАЗИН ОТКЛОНЕН: Пользователь $owner_id уже получал бонус ранее (абуз предотвращен)\n", FILE_APPEND); } } $c_stmt->close(); } // ============================================================================== // 5. СОХРАНЕНИЕ В БД (Prepared Statement для защиты) // ============================================================================== // [ИЗМЕНЕНО] Добавили колонку subscription_expires_at и 10-й знак вопроса // ============================================================================== $sql = "INSERT INTO stores (owner_id, name, description, category, address, phone, whatsapp, logo_url, cover_url, subscription_expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; $stmt = $conn->prepare($sql); if ($stmt) { // ============================================================================== // [ИЗМЕНЕНО] Добавили букву 's' в начале и переменную $expires_at в конце // ============================================================================== $stmt->bind_param("ssssssssss", $owner_id, $name, $description, $category, $address, $phone, $whatsapp, $logo_url, $cover_url, $expires_at); if ($stmt->execute()) { $new_store_id = $stmt->insert_id; echo json_encode([ "status" => "success", "message" => "Store created successfully", "store_id" => $new_store_id, "logo_url" => $logo_url, "is_first_store" => $is_first_store, "bonus_amount" => 500 ]); } else { echo json_encode(["status" => "error", "message" => "DB Execute Error: " . $stmt->error]); file_put_contents('debug_stores.txt', "DB ERROR: " . $stmt->error . "\n", FILE_APPEND); } $stmt->close(); } else { echo json_encode(["status" => "error", "message" => "DB Prepare Error: " . $conn->error]); } $conn->close(); ?> === ФАЙЛ: add_store_product_review.php === "error", "message" => "Empty fields"]); exit(); } // 1. ПРОВЕРКА НА ДУБЛИКАТ (НОВОЕ) $checkSql = "SELECT id FROM store_reviews WHERE product_id = ? AND user_id = ?"; $checkStmt = $conn->prepare($checkSql); $checkStmt->bind_param("ss", $product_id, $user_id); $checkStmt->execute(); $checkResult = $checkStmt->get_result(); if ($checkResult->num_rows > 0) { // Отзыв уже есть! echo json_encode(["status" => "error", "message" => "already_reviewed"]); exit(); } // 2. ДОБАВЛЕНИЕ (Если проверки пройдены) $sql = "INSERT INTO store_reviews (product_id, user_id, author_name, rating, review_text) VALUES (?, ?, ?, ?, ?)"; $stmt = $conn->prepare($sql); $stmt->bind_param("sssds", $product_id, $user_id, $author_name, $rating, $text); if ($stmt->execute()) { echo json_encode(["status" => "success", "message" => "Review added"]); } else { echo json_encode(["status" => "error", "message" => "Database error: " . $stmt->error]); } ?> === ФАЙЛ: admin_dashboard.php === CLIK Control Center

CLIK Control Center

Выйти
🔥 Активные (Новые) 📂 Архив 💬 Чаты 🗑️ Корзина 🏴‍☠️ Нарушители 🚨 Жалобы
query($sql); if ($result && $result->num_rows > 0) { while($row = $result->fetch_assoc()) { $imgUrl = "http://216.250.14.77/api/v1/" . $row['screenshot_url']; echo ""; echo ""; echo ""; echo ""; echo ""; $dateShow = ($view == 'archive') ? $row['processed_at'] : $row['created_at']; echo ""; echo ""; } } else { renderEmptyState($view == 'pending' ? "Новых заявок нет" : "Архив пуст"); } ?> query($sqlTrash); if ($resTrash && $resTrash->num_rows > 0) { while($row = $resTrash->fetch_assoc()) { $typeLabel = ""; $typeColor = "#6b7280"; if ($row['source_table'] == 'post') { $typeLabel = "ПОСТ/REEL"; $typeColor = "#8b5cf6"; } elseif ($row['source_table'] == 'grand') { $typeLabel = "МАГАЗИН"; $typeColor = "#10b981"; } elseif ($row['source_table'] == 'product') { $typeLabel = "ОБЪЯВЛЕНИЕ"; $typeColor = "#f59e0b"; } $displayDate = date('d.m.Y H:i', strtotime($row['deleted_at'])); echo ""; echo ""; echo ""; // Текст удаленного поста (Красный курсив) $textContent = !empty($row['content_text']) ? htmlspecialchars($row['content_text']) : 'Без текста'; echo ""; echo ""; echo ""; echo ""; } } else { renderEmptyState("Корзина пуста 🍃"); } } catch (Exception $e) { echo ""; } } // ============================================================================== // [НОВОЕ] ЛОГИКА ДЛЯ СПИСКА НАРУШИТЕЛЕЙ (ЧЕРНЫЙ СПИСОК) // ============================================================================== elseif ($cleanView === 'banned') { try { $sqlBanned = "SELECT id, name, phone, strikes_count, banned_until FROM users WHERE strikes_count > 0 OR (banned_until IS NOT NULL AND banned_until > NOW()) ORDER BY strikes_count DESC"; $resBanned = $conn->query($sqlBanned); if ($resBanned && $resBanned->num_rows > 0) { while($row = $resBanned->fetch_assoc()) { $isNowBanned = (!empty($row['banned_until']) && strtotime($row['banned_until']) > time()); $banStatus = $isNowBanned ? "🚫 Забанен до " . date('d.m.Y H:i', strtotime($row['banned_until'])) . "" : "⚠️ Под наблюдением"; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } } else { renderEmptyState("Нарушителей не обнаружено. Все ведут себя хорошо! 😊"); } } catch (Exception $e) { echo ""; } } // ============================================================================== // [НОВОЕ] ЛОГИКА ДЛЯ ЖАЛОБ (REPORTS) // ============================================================================== elseif ($cleanView === 'reports') { try { // [ИЗМЕНЕНО] Умный SQL с поддержкой префиксов "user_" для точного поиска $sqlReports = "SELECT r.*, u.name as reporter_name, u.phone as reporter_phone, target_u.name as target_name, target_u.phone as target_phone FROM reports r LEFT JOIN users u ON r.reporter_id = u.id LEFT JOIN users target_u ON (r.reported_item_id = target_u.id OR r.reported_item_id = CONCAT('user_', target_u.id) OR CONCAT('user_', r.reported_item_id) = target_u.id) WHERE r.status = 'pending' ORDER BY r.created_at DESC"; $resReports = $conn->query($sqlReports); if ($resReports && $resReports->num_rows > 0) { while($row = $resReports->fetch_assoc()) { $tableSource = 'post'; $typeBadge = "ПОСТ/REEL"; $typeColor = "#8b5cf6"; if ($row['item_type'] == 'ad') { $tableSource = 'ad'; $typeBadge = "РЕКЛАМА"; $typeColor = "#ef4444"; } elseif ($row['item_type'] == 'product') { $tableSource = 'product'; $typeBadge = "ОБЪЯВЛЕНИЕ"; $typeColor = "#f59e0b"; } elseif ($row['item_type'] == 'grand') { $tableSource = 'grand'; $typeBadge = "МАГАЗИН"; $typeColor = "#10b981"; } elseif ($row['item_type'] == 'user') { $tableSource = 'user'; $typeBadge = "ПРОФИЛЬ"; $typeColor = "#dc2626"; // Красный } $displayDate = date('d.m.Y H:i', strtotime($row['created_at'])); // ============================================================================== // [ИЗМЕНЕНО] Бронебойный вывод Имени и Телефона нарушителя // ============================================================================== $targetInfo = "#{$row['reported_item_id']}"; if ($row['item_type'] == 'user') { // Если вдруг имя не указано, напишем "Скрытый профиль" $tName = !empty($row['target_name']) ? $row['target_name'] : 'Скрытый профиль'; // Если нет телефона, выведем его ID $tPhone = !empty($row['target_phone']) ? $row['target_phone'] : "ID: " . $row['reported_item_id']; $targetInfo = "" . htmlspecialchars($tName) . "
" . htmlspecialchars($tPhone) . ""; } echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } } else { renderEmptyState("Жалоб нет. Все супер! ✨"); } } catch (Exception $e) { echo ""; } } // ============================================================================== // [НОВОЕ] ЛОГИКА ОТОБРАЖЕНИЯ ЛИЧНЫХ СООБЩЕНИЙ (ЧАТЫ) // ============================================================================== elseif ($cleanView === 'chats') { try { $sqlChats = "SELECT m.id, m.sender_id, m.receiver_id, m.message_text, m.image_url, m.audio_url, m.created_at, u1.name as sender_name, u1.phone as sender_phone, u2.name as receiver_name FROM messages m LEFT JOIN users u1 ON m.sender_id = u1.id LEFT JOIN users u2 ON m.receiver_id = u2.id ORDER BY m.created_at DESC LIMIT 50"; $resChats = $conn->query($sqlChats); if ($resChats && $resChats->num_rows > 0) { while($row = $resChats->fetch_assoc()) { $displayDate = date('d.m.Y H:i', strtotime($row['created_at'])); // Обработка Медиа (Фото / Видео / Голос) $mediaHtml = "Только текст"; $baseUrl = "http://" . $_SERVER['HTTP_HOST'] . "/api/v1/"; if (!empty($row['audio_url'])) { $audioUrl = (strpos($row['audio_url'], 'http') === 0) ? $row['audio_url'] : $baseUrl . $row['audio_url']; $mediaHtml = ""; } elseif (!empty($row['image_url'])) { $mediaUrl = (strpos($row['image_url'], 'http') === 0) ? $row['image_url'] : $baseUrl . "uploads/" . basename($row['image_url']); if (strpos(strtolower($mediaUrl), '.mp4') !== false || strpos(strtolower($mediaUrl), '.mov') !== false) { $mediaHtml = ""; } else { $mediaHtml = ""; } } $msgText = !empty($row['message_text']) ? htmlspecialchars($row['message_text']) : 'Без текста'; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } } else { renderEmptyState("Сообщений пока нет. В чатах тихо! 💬"); } } catch (Exception $e) { echo ""; } } // ============================================================================== // БРОНЕБОЙНАЯ ЛОГИКА ДЛЯ "НОВЫХ" И "АРХИВА" (Одобренные) // ============================================================================== else { try { $targetStatus = ($cleanView === 'archive') ? 1 : 0; $allItems = []; // 1. ПОСТЫ $res1 = $conn->query("SELECT p.id, p.author_id as user_id, p.content as text, p.image_url, p.video_url, p.created_at, u.name as user_name, u.phone FROM posts p LEFT JOIN users u ON p.author_id = u.id WHERE p.is_moderated = " . $targetStatus); if ($res1) { while($r = $res1->fetch_assoc()) { $r['source_table'] = 'post'; $allItems[] = $r; } } // 2. ОФИЦИАЛЬНЫЕ МАГАЗИНЫ $res2 = $conn->query("SELECT g.id, g.owner_id as user_id, g.description as text, g.images_json as image_url, NULL as video_url, g.created_at, u.name as user_name, u.phone FROM grand_products g LEFT JOIN users u ON g.owner_id = u.id WHERE g.is_moderated = " . $targetStatus); if ($res2) { while($r = $res2->fetch_assoc()) { $r['source_table'] = 'grand'; $allItems[] = $r; } } // 3. ЧАСТНЫЕ ОБЪЯВЛЕНИЯ $res3 = $conn->query("SELECT pr.id, pr.owner_id as user_id, pr.description as text, pr.image_urls as image_url, NULL as video_url, pr.created_at, u.name as user_name, u.phone FROM products pr LEFT JOIN users u ON pr.owner_id = u.id WHERE pr.is_moderated = " . $targetStatus); if ($res3) { while($r = $res3->fetch_assoc()) { $r['source_table'] = 'product'; $allItems[] = $r; } } if (count($allItems) == 0) { renderEmptyState($targetStatus == 0 ? "Все чисто! Жалоб нет 🛡️" : "Архив пуст"); } else { foreach($allItems as $row) { $typeLabel = ""; $typeColor = ""; if ($row['source_table'] == 'post') { $typeLabel = "ПОСТ/REEL"; $typeColor = "#8b5cf6"; } elseif ($row['source_table'] == 'grand') { $typeLabel = "МАГАЗИН"; $typeColor = "#10b981"; } elseif ($row['source_table'] == 'product') { $typeLabel = "ОБЪЯВЛЕНИЕ"; $typeColor = "#f59e0b"; } $mediaHtml = "Нет медиа"; $baseUrl = "http://216.250.14.77/api/v1/"; if (!empty($row['video_url'])) { $vidUrl = (strpos($row['video_url'], 'http') === 0) ? $row['video_url'] : $baseUrl . $row['video_url']; $mediaHtml = ""; } elseif (!empty($row['image_url'])) { $imgStr = $row['image_url']; $firstImg = $imgStr; $imgArr = json_decode($imgStr, true); if (is_array($imgArr) && count($imgArr) > 0) { $firstImg = $imgArr[0]; } $finalImg = (strpos($firstImg, 'http') === 0) ? $firstImg : $baseUrl . "uploads/" . $firstImg; $mediaHtml = ""; } $rawDate = $row['created_at'] ?? ''; if (is_numeric($rawDate) && strlen((string)$rawDate) > 10) { $displayDate = date('d.m.Y H:i', (int)($rawDate / 1000)); } else { $displayDate = date('d.m.Y H:i', strtotime((string)$rawDate)); } echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } } } catch (Exception $e) { echo ""; } } ?>
IDПользовательСуммаЧекДатаСтатус
#{$row['id']}{$row['amount']} TMT{$dateShow}"; if ($row['status'] == 'pending') { echo "
"; } else { $stClass = ($row['status'] == 'approved') ? 'status-approved' : 'status-rejected'; $stText = ($row['status'] == 'approved') ? '✅ Одобрено' : '❌ Отклонено'; echo "$stText"; } echo "
ID ОригиналаНарушительУдаленный текст (Архив)КатегорияДата удаления ПользовательСтрайкиСтатус банаДействие Кто пожаловалсяТип / IDПричина жалобыДатаДействие IDОтправитель ➔ ПолучательМедиа / ГолосТекст сообщенияДатаДействие IDАвторКонтентТекст / ДеталиДатаДействие
#{$row['original_id']}\"" . $textContent . "\"{$typeLabel}{$displayDate}
ОШИБКА:
" . $e->getMessage() . "
ID: {$row['id']}{$row['strikes_count']} / 3$banStatus
Ошибка: " . $e->getMessage() . "
{$typeBadge}
{$targetInfo}
" . htmlspecialchars($row['reason']) . "{$displayDate}
"; if ($row['item_type'] == 'user') { echo ""; } else { echo ""; } echo ""; echo "
ОШИБКА:
" . $e->getMessage() . "
#{$row['id']}{$mediaHtml}{$msgText}{$displayDate}
Ошибка: " . $e->getMessage() . "
#{$row['id']}
{$typeLabel}
{$mediaHtml}" . htmlspecialchars($row['text'] ?? '') . "" . $displayDate . ""; if ($targetStatus == 0) { echo "
"; } else { echo "✅ Одобрено"; } echo "
СИСТЕМНАЯ ОШИБКА PHP:
" . $e->getMessage() . "

$msg "; } ?> === ФАЙЛ: admin_login.php === CLIK Security Gate

CLIK Admin

Control Center Access

$error
"; ?>
=== ФАЙЛ: admin_logout.php === === ФАЙЛ: ai_check_status.php === set_charset("utf8mb4"); if ($_SERVER['REQUEST_METHOD'] !== 'GET') { echo json_encode(["status" => "error", "message" => "Only GET allowed"]); exit(); } $task_id = $_GET['task_id'] ?? ''; if (empty($task_id)) { echo json_encode(["status" => "error", "message" => "Missing task_id"]); exit(); } $sql = "SELECT status, result_image_url, error_message FROM ai_tasks WHERE task_id = ?"; $stmt = $conn->prepare($sql); $stmt->bind_param("s", $task_id); $stmt->execute(); $res = $stmt->get_result(); if ($row = $res->fetch_assoc()) { echo json_encode([ "status" => "success", // Отправляем для всех возможных вариантов парсера Android: "taskStatus" => $row['status'], "task_status" => $row['status'], "resultImageUrl" => $row['result_image_url'], "result_image_url" => $row['result_image_url'], "errorMessage" => $row['error_message'], "error_message" => $row['error_message'] ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } else { echo json_encode([ "status" => "error", "message" => "Task not found" ]); } $stmt->close(); $conn->close(); ?> === ФАЙЛ: ai_get_styles.php === "nature_stones", "name" => "White Stones", "description" => "Eco-style with green leaves", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "style_stones.jpg" ], [ "id" => "nature_beach", "name" => "Ocean Breeze", "description" => "Sand, shells, and waves", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "style_beach.jpg" ], [ "id" => "luxury_floral", "name" => "Delicate Flowers", "description" => "Premium 3D floral background", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "style_floral.jpg" ], [ "id" => "interior_window", "name" => "Bright Window", "description" => "Airy interior with flowing curtains", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "style_window.jpg" ], [ "id" => "interior_plants", "name" => "Indoor Tropics", "description" => "Warm parquet and Monstera shadows", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "style_plants.jpg" ], [ "id" => "interior_brick", "name" => "White Loft", "description" => "Brick wall and pendant lamps", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "style_brick.jpg" ], // --- GADGETS & TECH --- [ "id" => "tech_wave", "name" => "Emerald Silk", "description" => "Green waves and gold accents", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "tech_wave.jpg" ], [ "id" => "tech_cubes", "name" => "Floating Cubes", "description" => "Silver, gold, and levitation", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "tech_cubes.jpg" ], [ "id" => "tech_neon", "name" => "Cyber Glass", "description" => "Neon, glass, and speed", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "tech_neon.jpg" ], // --- AUTOMOTIVE --- [ "id" => "auto_highway", "name" => "Sunrise Highway", "description" => "City skyline and morning sun", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "auto_highway.jpg" ], [ "id" => "auto_warehouse", "name" => "Neon Warehouse", "description" => "Concrete floor and glowing arrows", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "auto_warehouse.jpg?v=3" ], [ "id" => "auto_horizon", "name" => "Mirrored Horizon", "description" => "Futuristic landscape and mountains", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "auto_horizon.jpg?v=3" ], // --- FASHION & APPAREL --- [ "id" => "fashion_sale", "name" => "SALE Minimal", "description" => "Minimalism with SALE typography", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "fashion_sale.jpg" ], [ "id" => "fashion_glow", "name" => "Ethereal Glow", "description" => "Light background with gentle rays", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "fashion_glow.jpg" ], [ "id" => "fashion_navy", "name" => "Dark Baroque", "description" => "Luxurious pattern and velvet", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "fashion_navy.jpg" ], // --- NATURE --- [ "id" => "nature_hills", "name" => "Green Hills", "description" => "Rolling hills in the fog", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "nature_hills.jpg" ], [ "id" => "nature_snow", "name" => "Snowy Mountains", "description" => "Mountains reflected in ice", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "nature_snow.jpg" ], [ "id" => "nature_garden", "name" => "Blooming Garden", "description" => "White flowers and trees", "cost_clik_coins" => 2, "preview_image_url" => $server_base . "nature_garden.jpg" ] ]; echo json_encode(["status" => "success", "data" => $styles], JSON_UNESCAPED_UNICODE); ?> === ФАЙЛ: ai_process_image.php === set_charset("utf8mb4"); if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(["status" => "error", "message" => "Only POST allowed"]); exit(); } $user_id = $_POST['user_id'] ?? ''; $item_id = isset($_POST['item_id']) && $_POST['item_id'] !== 'null' ? $_POST['item_id'] : NULL; $style_preset = $_POST['style_preset'] ?? 'studio_bg'; // [Изменено] Жестко задаем цену на сервере для безопасности (2 TMT) $cost = 2.00; $task_id = uniqid('ai_task_'); // ========================================================================= // [ИЗМЕНЕНО] БЛОК БИЛЛИНГА: ПРОВЕРКА, СПИСАНИЕ И ЖУЧКИ // ========================================================================= if (!$user_id) { echo json_encode(["status" => "error", "message" => "User ID required"]); exit(); } // 1. Проверяем текущий баланс $stmt_balance = $conn->prepare("SELECT balance FROM users WHERE id = ?"); $stmt_balance->bind_param("s", $user_id); $stmt_balance->execute(); $res_balance = $stmt_balance->get_result(); $user_data = $res_balance->fetch_assoc(); $stmt_balance->close(); $current_balance = (float)($user_data['balance'] ?? 0); // [НОВОЕ] ЖУЧОК 1: Логируем попытку списания $log_file = __DIR__ . '/debug_ai_billing.txt'; file_put_contents($log_file, date('[Y-m-d H:i:s] ') . "СТАРТ: Юзер $user_id | Текущий баланс: $current_balance TMT | Пытаемся списать: $cost TMT\n", FILE_APPEND); if ($current_balance < $cost) { file_put_contents($log_file, date('[Y-m-d H:i:s] ') . "ОТКАЗ: Недостаточно средств.\n", FILE_APPEND); // 2. Денег нет. Отбиваем запрос ДО загрузки картинки и старта ИИ echo json_encode([ "status" => "error", "message" => "insufficient_funds", "details" => "Недостаточно средств. Пополните баланс." ]); exit(); } // 3. Деньги есть. Списываем 2 TMT $update_stmt = $conn->prepare("UPDATE users SET balance = balance - ? WHERE id = ?"); $update_stmt->bind_param("ds", $cost, $user_id); if ($update_stmt->execute()) { $affected = $update_stmt->affected_rows; // [НОВОЕ] ЖУЧОК 2: Успех списания file_put_contents($log_file, date('[Y-m-d H:i:s] ') . "УСПЕХ: Запрос выполнен. Изменено строк: $affected. Баланс в базе должен стать: " . ($current_balance - $cost) . " TMT\n", FILE_APPEND); } else { // [НОВОЕ] ЖУЧОК 3: Ошибка SQL file_put_contents($log_file, date('[Y-m-d H:i:s] ') . "ОШИБКА БД: " . $update_stmt->error . "\n", FILE_APPEND); } $update_stmt->close(); // 4. Отправляем системный "чек" в уведомления $msg = "Списано $cost TMT за генерацию ИИ-фона."; $sysAvatar = "https://cdn-icons-png.flaticon.com/512/2040/2040946.png"; $nSql = "INSERT INTO notifications (user_id, sender_id, type, action_id, title, message, user_avatar, created_at, is_read) VALUES (?, 'SYSTEM', 'system', 'ai_studio', 'ИИ-Студия ✨', ?, ?, NOW(), 0)"; $nStmt = $conn->prepare($nSql); if ($nStmt) { $nStmt->bind_param("sss", $user_id, $msg, $sysAvatar); $nStmt->execute(); $nStmt->close(); } // ========================================================================= // КОНЕЦ БЛОКА БИЛЛИНГА // ========================================================================= // 1. ЗАГРУЖАЕМ ОРИГИНАЛЬНОЕ ФОТО $upload_dir = __DIR__ . '/uploads/ai_originals/'; if (!file_exists($upload_dir)) mkdir($upload_dir, 0777, true); $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http"; $server_base = "$protocol://" . $_SERVER['HTTP_HOST'] . "/api/v1/uploads/ai_originals/"; $original_url = ""; if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) { $ext = strtolower(pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION)); if (!$ext) $ext = 'jpg'; $new_name = $task_id . "_orig." . $ext; $target_path = $upload_dir . $new_name; if (move_uploaded_file($_FILES['image']['tmp_name'], $target_path)) { $original_url = $server_base . $new_name; } else { echo json_encode(["status" => "error", "message" => "Failed to save image on server"]); exit(); } } else { echo json_encode(["status" => "error", "message" => "No image uploaded"]); exit(); } // 2. ЗАПИСЫВАЕМ ЗАДАЧУ В БД (Статус: pending) $sql = "INSERT INTO ai_tasks (task_id, user_id, item_id, style_preset, cost, status, original_image_url) VALUES (?, ?, ?, ?, ?, 'pending', ?)"; $stmt = $conn->prepare($sql); $stmt->bind_param("ssssis", $task_id, $user_id, $item_id, $style_preset, $cost, $original_url); if ($stmt->execute()) { // 3. [ГЛАВНАЯ МАГИЯ] ЗАПУСКАЕМ ФОНОВЫЙ СКРИПТ АСИНХРОННО! $worker_script = __DIR__ . "/ai_worker_background.php"; $crash_log = __DIR__ . "/debug_worker_crash.txt"; // Используем полный путь /usr/bin/php и записываем любые ошибки в debug_worker_crash.txt $cmd = "/usr/bin/php $worker_script $task_id >> $crash_log 2>&1 &"; exec($cmd); // 4. Моментально отвечаем Android echo json_encode([ "status" => "success", "message" => "Task added to queue", "task_id" => $task_id ]); } else { echo json_encode(["status" => "error", "message" => "DB Insert Error: " . $stmt->error]); } $stmt->close(); $conn->close(); ?> === ФАЙЛ: ai_worker_background.php === set_charset("utf8mb4"); $task_id = $argv[1] ?? null; if (!$task_id) die("No task ID provided"); $log_file = __DIR__ . '/debug_ai_worker.txt'; file_put_contents($log_file, "--- СТАРТ СЭНДВИЧ-ФОНА: $task_id ---\n", FILE_APPEND); $conn->query("UPDATE ai_tasks SET status = 'processing' WHERE task_id = '$task_id'"); $res = $conn->query("SELECT style_preset FROM ai_tasks WHERE task_id = '$task_id'"); if ($res->num_rows === 0) die(); $task = $res->fetch_assoc(); $style_preset = $task['style_preset']; $api_key = "AIzaSyAlpUF1pZIRDZaYUh2aCseQ1YeaCwTfUak"; // ============================================================================== // ПРОМПТЫ ДЛЯ ПУСТЫХ ФОНОВ (Специально для Сэндвича) // ============================================================================== $style_prompts = [ // 1. Белые камни и листья "nature_stones" => "An empty flat lay product background featuring smooth white river stones and pebbles of various sizes. Fresh, vibrant green ficus leaves scattered naturally across the stones. Bright, natural overhead sunlight creating sharp, high-contrast crisp shadows. Clean, organic, minimalist spa aesthetic. Ultra-detailed 8k, photorealistic, empty center space for product placement, blank stage, no objects", // 2. Пляж, ракушки и песок "nature_beach" => "An empty flat lay product background of pristine, smooth warm beige beach sand. A gentle, foamy ocean wave washing in from the upper edge. Framed beautifully by tropical green palm fronds, various detailed seashells, and a white starfish around the corners. Soft, warm natural sunlight. Summer vacation aesthetic. Ultra-detailed 8k, photorealistic, empty center space, blank stage, no products", // 3. 3D-цветы и птичка "luxury_floral" => "An empty elegant flat lay product background featuring intricate 3D sculpted pastel flowers in soft pink, white, and pale purple, attached to delicate white branches. A sculpted pastel blue bird sitting on a branch. High-end luxury cosmetic aesthetic, pale off-white background, soft diffused lighting. Extremely detailed 3D paper-craft style. Empty center space, NO text, NO words, NO letters, blank stage", // 4. Окно и занавески "interior_window" => "An empty professional architectural photography background, bright and airy minimalist interior. A large elegant arched window with sheer translucent white linen curtains softly draped on both sides. Abundant natural morning light streaming through, creating a high-key ethereal glow and soft shadows on the light-colored floor. The center floor space is completely empty and flat for placing furniture. Whites, creams, and light beige palette. Photorealistic, ultra-detailed, 8k, cinematic lighting, no objects in the center, blank stage", // 5. Зелень по краям "interior_plants" => "An empty professional interior photography background. A minimalist serene room with a smooth warm cream-colored blank wall and a beautiful herringbone pattern wooden parquet floor. Two large tropical indoor plants in minimalist stone pots frame the scene on the far left and right (Monstera and fan palm). Bright warm natural sunlight streams from the left, casting a beautiful window-pane grid shadow on the wall. The center of the floor and wall is completely empty for furniture placement. Photorealistic, ultra-detailed 8k, architectural digest style, blank stage, no objects in the center", // 6. Белый кирпич "interior_brick" => "An empty professional interior photography background. A textured white-painted brick wall with a light-colored natural wood plank floor. Three minimalist pendant lamps hang from the top, emitting a soft warm golden glow. Dramatic dappled shadows of tree branches and leaves are cast across the left side of the brick wall. High-key lighting, clean minimalist aesthetic. The center floor space is perfectly empty for placing furniture. Ultra-detailed 8k, photorealistic, cinematic lighting, sharp focus on brick textures, blank stage, no objects", // <--- ЗДЕСЬ ДОБАВЛЕНА ЗАПЯТАЯ // 1. Изумрудный шелк и золото (Ваш промпт) "tech_wave" => "Abstract digital art, luxurious fluid wave composition. Flowing, organic folds of silk-like material in a sophisticated gradient of emerald green, deep forest green, and soft mint. A prominent, winding metallic gold ribbon with a shimmering, granular glitter texture cuts diagonally through the green waves. Smooth, satin-like surfaces with soft, diffused highlights and subtle depth-defining shadows. Elegant 3D render, high-end wallpaper aesthetic, minimalist luxury. Ultra-detailed, 8k resolution, Octane Render style, ray-traced reflections, fluid dynamics, serene and sophisticated atmosphere, vertical orientation. IMPORTANT: The exact center of the image MUST be completely empty, flat, and blank for product placement. No ribbons or waves in the dead center", // 2. Парящие серебряные и золотые кубы (Ваш промпт) "tech_cubes" => "Ultra-detailed photorealistic 3D render of numerous floating metallic cubes of various sizes suspended in a soft light gray-blue gradient background, vertical composition. Cubes feature premium matte-metallic texture with subtle micro-surface details and fine brushed finish. Color palette consists of cool silver and pearl-white metallic faces combined with luxurious warm golden-bronze and copper-gold metallic faces. Several prominent bicolor cubes show a sharp, clean split — exactly half silver and half gold on adjacent faces. Cubes are dynamically scattered at different depths and angles, creating elegant floating movement and three-dimensional depth. Soft diffused cinematic lighting with gentle specular highlights on edges and soft realistic shadows. Strong shallow depth of field. Premium modern minimalist aesthetic, octane render, intricate material textures, maximum detail, elegant composition, 8k resolution, masterpiece, best quality. IMPORTANT: Leave a perfectly clear, empty space in the exact center for product placement. No cubes in the middle", // 3. Кибер-стекло и свет (Ваш промпт) "tech_neon" => "Cinematic abstract digital art, vertical composition, dark futuristic scene with deep black background. Multiple transparent glowing glass-like rectangular frames and squares of different sizes floating and overlapping in three-dimensional space. Dynamic light streaks and motion blur effects: intense horizontal glowing orange-red light trails with bright white and silver highlights. Strong neon glow and lens flare effects on the edges of the geometric structures. Sharp clean lines combined with beautiful motion blur and light painting. Color palette: deep black, fiery orange, crimson red, bright white and cool silver. Dramatic volumetric lighting, high contrast, elegant cyberpunk aesthetic. Shallow depth of field, some elements in sharp focus while others show strong motion blur. Premium futuristic minimalism, intricate light reflections and refractions on glass surfaces, ultra-detailed, photorealistic rendering, octane render, 8k resolution, masterpiece, best quality. IMPORTANT: The center area must remain completely empty and dark for product overlay, blank stage", // --- СТИЛИ ДЛЯ АВТОМОБИЛЕЙ (Ваши промпты) --- // 1. Трасса на рассвете "auto_highway" => "Photorealistic cinematic wide shot of an empty asphalt highway stretching into the distance, vertical composition. Dark textured asphalt road with visible tire marks, white and yellow road markings. In the background — modern city skyline with numerous high-rise buildings. Dramatic sky with blue and soft pinkish clouds at sunrise or sunset. Bright sun flare on the left side creating beautiful atmospheric glow and lens flare. Empty road with strong perspective, highly detailed asphalt texture, realistic clouds, cinematic color grading, professional photography style, sharp focus, natural depth of field, ultra-realistic, 8k resolution, masterpiece, best quality. IMPORTANT: The exact center and foreground of the highway MUST be completely empty and clear to place a car", // 2. Неоновый ангар "auto_warehouse" => "Photorealistic wide-angle view of a large empty industrial warehouse hall with high ceiling and metal truss structure, vertical composition. Dark polished concrete floor with strong reflections. On both left and right walls — large glowing neon light installations in the shape of bright white-blue arrows pointing forward. Multiple overhead round ceiling lights. Minimalist futuristic atmosphere, cool color tones, dramatic lighting with strong reflections on the floor, clean and modern industrial style, high detail, cinematic lighting, octane render, 8k resolution, masterpiece, best quality. IMPORTANT: Leave a perfectly clear, empty space on the concrete floor in the exact center for placing a vehicle. No objects in the middle", // 3. Зеркальный горизонт "auto_horizon" => "Futuristic minimalist landscape, vertical composition. Vast reflective glossy surface (like glass or water) in the foreground with perfect mirror reflections, creating infinite perspective lines leading toward the horizon. In the distance — dark mountain range under a beautiful gradient sky from deep blue to soft pink and light blue at sunrise or sunset. Calm, serene and cinematic atmosphere. Ultra-smooth reflective surface, soft clouds in the sky, delicate atmospheric perspective, high detail, dreamy futuristic aesthetic, photorealistic rendering, octane render, 8k resolution, masterpiece, best quality. IMPORTANT: The reflective surface in the center foreground must remain completely empty and flat for car placement, blank stage", // --- ПРОМПТЫ ДЛЯ ОДЕЖДЫ --- "fashion_sale" => "Minimalist vertical background for clothing marketplace, solid elegant dark gray color (#4A4A4A). Large typographic composition with the word 'SALE' repeated vertically in modern sans-serif font. Top lines are thin light pink outline, fourth line is bold filled soft peach-pink, bottom line is thin outline. Luxury minimalist style. Even soft lighting, high resolution, clean vector look, 9:16 aspect ratio, premium aesthetic. IMPORTANT: Leave ample empty space in the center over the text for product placement, blank stage", "fashion_glow" => "Luxurious bright white ethereal background for clothing marketplace, vertical composition. Dreamy soft white and light gray gradient with gentle glowing volumetric light rays coming from top center. Subtle sparkling golden particles floating in the air. Highly reflective glossy marble-like floor with soft reflections. Clean minimalist luxury atmosphere, very bright and airy, elegant and expensive feel, cinematic soft glow, ultra detailed, 9:16 aspect ratio, octane render, 8k. IMPORTANT: The center area must remain completely empty and bright for product overlay, blank stage", "fashion_navy" => "Rich dark navy blue and black luxurious damask pattern background for clothing marketplace, vertical seamless texture. Elegant ornate baroque floral and scrollwork design with deep 3D embossed effect and subtle velvet texture. Sophisticated royal and premium feel, dark moody atmosphere with soft highlights. High detail texture, expensive look, subtle sheen, dramatic lighting, 9:16 aspect ratio, ultra realistic textile texture, 8k resolution. IMPORTANT: The exact center must be flat and unobstructed for clothing placement, blank stage", // --- ПРОМПТЫ ДЛЯ ПРИРОДЫ --- "nature_hills" => "Epic photorealistic landscape of rolling lush green hills covered in vibrant emerald grass, vertical composition. Dense sea of white fog and mist flowing between the hills creating beautiful atmospheric layers. Distant mountain ranges fading into soft blue haze. Bright clear blue sky with light wispy clouds. Golden sunlight gently illuminating the hilltops. Realistic volumetric fog, cinematic depth, professional landscape photography style, natural depth of field, 8k resolution. IMPORTANT: Leave the central foreground area completely empty for product placement, blank stage", "nature_snow" => "Stunning photorealistic winter landscape, vertical composition. Majestic snow-covered mountain peaks under a clear bright blue sky with soft white clouds. In the foreground — vast flat frozen reflective surface perfectly mirroring the mountains and sky. Crisp white snow texture with subtle blue tones, dramatic atmosphere. Professional landscape photography, realistic reflections, cold color palette, cinematic lighting, 8k resolution. IMPORTANT: The reflective surface in the center foreground MUST remain completely empty and flat for product placement, blank stage", "nature_garden" => "Magical photorealistic spring landscape, vertical composition. Winding dirt path through a meadow covered in dense white flowers with vibrant green grass. Blooming white cherry blossom trees arching over creating a natural tunnel. Bright blue sky with soft sunlight. Romantic atmosphere, soft natural lighting, professional nature photography style, beautiful bokeh in background, 8k resolution. IMPORTANT: The central path area in the foreground must be completely empty and clear for product placement, blank stage", ]; // [ИСПРАВЛЕНО] Меняем дефолтный стиль на существующий (например, nature_stones) $final_prompt = $style_prompts[$style_preset] ?? $style_prompts["nature_stones"]; file_put_contents($log_file, "🎨 Запрос: $final_prompt\n", FILE_APPEND); // ============================================================================== // [ИЗМЕНЕНО] ВОЗВРАЩАЕМ ВАШ РОДНОЙ И РАБОЧИЙ МЕТОД GEMINI // ============================================================================== $api_key = "AIzaSyAlpUF1pZIRDZaYUh2aCseQ1YeaCwTfUak"; // [НОВОЕ] Используем стандартный generateContent, который гарантированно пропускает провайдер. // Мы используем новейшую модель gemini-3.1-flash-image (вы также можете вписать сюда 2.5) $flash_url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-image:generateContent?key=" . trim($api_key); $flash_payload = [ "contents" => [[ "parts" => [ ["text" => $final_prompt] ] ]] ]; $payload_json = json_encode($flash_payload); $ch = curl_init($flash_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload_json); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json' ]); // Безопасные настройки сети без редиректов и хакерских обходов curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_TIMEOUT, 60); $resp = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curl_err = curl_error($ch); curl_close($ch); // Записываем ответ сервера file_put_contents($log_file, "📡 HTTP КОД GOOGLE: $http_code\n📡 RAW ОТВЕТ: $resp\n====================\n", FILE_APPEND); $flash_data = json_decode($resp, true); // ============================================================================== // [ИЗМЕНЕНО] Извлекаем картинку из стандартного ответа Gemini (inlineData) // ============================================================================== $b64_flash = null; if (isset($flash_data['candidates'][0]['content']['parts'])) { foreach ($flash_data['candidates'][0]['content']['parts'] as $part) { // Проверяем оба варианта написания ключа, которые отдает Gemini if (isset($part['inlineData']['data'])) { $b64_flash = $part['inlineData']['data']; break; } elseif (isset($part['inline_data']['data'])) { $b64_flash = $part['inline_data']['data']; break; } } } $generated_image_url = null; $error_message = null; $final_status = "failed"; if ($b64_flash) { file_put_contents($log_file, "🎉 Успех! Картинка найдена в inlineData и сохранена.\n", FILE_APPEND); $new_filename = "bg_ai_" . time() . "_" . rand(100, 999) . ".jpg"; $new_filepath = __DIR__ . "/uploads/ai_originals/" . $new_filename; file_put_contents($new_filepath, base64_decode($b64_flash)); $generated_image_url = "https://ldr.com.tm/api/v1/uploads/ai_originals/" . $new_filename; $final_status = "completed"; } else { // Пишем ошибку в лог, если картинки нет $error_json = substr(json_encode($flash_data, JSON_UNESCAPED_UNICODE), 0, 1000) . "..."; file_put_contents($log_file, "💀 ОШИБКА ИЗВЛЕЧЕНИЯ: $error_json\n", FILE_APPEND); $error_message = "Ошибка извлечения картинки"; } // Обновляем БД $update_sql = "UPDATE ai_tasks SET status = ?, result_image_url = ?, error_message = ? WHERE task_id = ?"; $update_stmt = $conn->prepare($update_sql); $update_stmt->bind_param("ssss", $final_status, $generated_image_url, $error_message, $task_id); $update_stmt->execute(); $update_stmt->close(); $conn->close(); file_put_contents($log_file, "--- ЗАВЕРШЕНО ---\n", FILE_APPEND); ?> === ФАЙЛ: apply_store_bonus.php === "error", "message" => "Missing parameters"]); exit(); } // 1. Проверяем, не получал ли он бонус ранее $check = $conn->query("SELECT bonus_received, subscription_expires_at FROM stores WHERE id = '$store_id' AND owner_id = '$user_id'"); if ($check->num_rows === 0) { echo json_encode(["status" => "error", "message" => "Магазин не найден"]); exit(); } $store = $check->fetch_assoc(); if ($store['bonus_received'] == 1) { echo json_encode(["status" => "error", "message" => "Бонус уже был получен ранее!"]); exit(); } // 2. Начисляем бонус if ($choice === 'subscription_6m') { // Продлеваем подписку на 6 месяцев $current_exp = strtotime($store['subscription_expires_at']); if ($current_exp < time()) $current_exp = time(); $new_exp = date('Y-m-d H:i:s', strtotime('+6 months', $current_exp)); $conn->query("UPDATE stores SET subscription_expires_at = '$new_exp', bonus_received = 1 WHERE id = '$store_id'"); // ============================================================================== // [НОВОЕ] Уведомление о продлении // ============================================================================== $n_title = "Витрина CLIK 🛍"; $n_msg = "Ваш подарок активирован: подписка магазина продлена на 6 месяцев!"; $conn->query("INSERT INTO notifications (sender_id, user_id, type, action_id, title, message, post_preview, user_avatar, created_at, is_read) VALUES ('$user_id', '$user_id', 'system_bonus', 'store', '$n_title', '$n_msg', '', '', NOW(), 0)"); } else { // Начисляем 500 TMT на рекламу $conn->query("INSERT INTO ad_wallets (owner_id, balance) VALUES ('$user_id', 500) ON DUPLICATE KEY UPDATE balance = balance + 500"); $conn->query("UPDATE stores SET bonus_received = 1 WHERE id = '$store_id'"); // ============================================================================== // [НОВОЕ] Уведомление о пополнении // ============================================================================== $n_title = "Витрина CLIK 🛍"; $n_msg = "Ваш подарок активирован: 500 TMT успешно зачислены на рекламный баланс!"; $conn->query("INSERT INTO notifications (sender_id, user_id, type, action_id, title, message, post_preview, user_avatar, created_at, is_read) VALUES ('$user_id', '$user_id', 'system_bonus', 'store', '$n_title', '$n_msg', '', '', NOW(), 0)"); } echo json_encode(["status" => "success", "message" => "Бонус успешно применен!"]); $conn->close(); ?> === ФАЙЛ: approve_topup.php === "error", "message" => "Unauthorized: Please login first"]); exit(); } require_once 'db.php'; $request_id = $_POST['request_id'] ?? ''; $action = $_POST['action'] ?? ''; // 'approve' или 'reject' if (empty($request_id) || empty($action)) { echo json_encode(["status" => "error", "message" => "Missing data"]); exit(); } // Получаем данные заявки $safeId = $conn->real_escape_string($request_id); $sql = "SELECT * FROM payment_requests WHERE id = '$safeId' AND status = 'pending' LIMIT 1"; $res = $conn->query($sql); if ($res->num_rows == 0) { echo json_encode(["status" => "error", "message" => "Request not found or processed"]); exit(); } $req = $res->fetch_assoc(); $user_id = $req['user_id']; $amount = (double)$req['amount']; if ($action == 'approve') { // НАЧИНАЕМ ТРАНЗАКЦИЮ $conn->begin_transaction(); try { // 1. Обновляем статус заявки $conn->query("UPDATE payment_requests SET status = 'approved', processed_at = NOW() WHERE id = '$safeId'"); // 2. Начисляем деньги юзеру $conn->query("UPDATE users SET balance = balance + $amount WHERE id = '$user_id'"); // 3. Отправляем уведомление (Push) $msg = "Ваш баланс пополнен на $amount TMT. Спасибо! 💳"; $sysAvatar = "https://cdn-icons-png.flaticon.com/512/2454/2454282.png"; // Иконка кошелька // Вставляем уведомление $stmt = $conn->prepare("INSERT INTO notifications (user_id, sender_id, type, action_id, title, message, user_avatar, created_at, is_read) VALUES (?, 'SYSTEM', 'system', 'wallet', 'Пополнение 💰', ?, ?, NOW(), 0)"); $stmt->bind_param("sss", $user_id, $msg, $sysAvatar); $stmt->execute(); $conn->commit(); echo json_encode(["status" => "success", "message" => "Approved & Credited"]); } catch (Exception $e) { $conn->rollback(); echo json_encode(["status" => "error", "message" => "DB Error: " . $e->getMessage()]); } } elseif ($action == 'reject') { // ОТКЛОНЕНИЕ ЗАЯВКИ $conn->query("UPDATE payment_requests SET status = 'rejected', processed_at = NOW() WHERE id = '$safeId'"); // Уведомление об отказе $msg = "Ваш чек отклонен. Пожалуйста, проверьте данные или свяжитесь с поддержкой."; $sysAvatar = "https://cdn-icons-png.flaticon.com/512/1828/1828843.png"; // Иконка ошибки $stmt = $conn->prepare("INSERT INTO notifications (user_id, sender_id, type, action_id, title, message, user_avatar, created_at, is_read) VALUES (?, 'SYSTEM', 'system', 'wallet', 'Отклонено ❌', ?, ?, NOW(), 0)"); $stmt->bind_param("sss", $user_id, $msg, $sysAvatar); $stmt->execute(); echo json_encode(["status" => "success", "message" => "Rejected"]); } ?> === ФАЙЛ: block_user.php === "error", "message" => "Missing parameters"]); exit; } // 1. Добавляем в Черный список (IGNORE - если уже есть, не будет ошибки) $sql = "INSERT IGNORE INTO blocked_users (user_id, blocked_id) VALUES (?, ?)"; $stmt = $conn->prepare($sql); $stmt->bind_param("ss", $user_id, $blocked_id); if ($stmt->execute()) { // 2. БОНУС: Сразу отписываемся от этого человека (удаляем из follows) // Чтобы не видеть его посты в ленте, даже если он был в подписках $unfollow_sql = "DELETE FROM follows WHERE follower_id = ? AND following_id = ?"; $unfollow_stmt = $conn->prepare($unfollow_sql); $unfollow_stmt->bind_param("ss", $user_id, $blocked_id); $unfollow_stmt->execute(); echo json_encode(["status" => "success", "message" => "User blocked"]); } else { echo json_encode(["status" => "error", "message" => "Database error"]); } $stmt->close(); $conn->close(); ?> === ФАЙЛ: change_password.php === false, "message" => "All fields are required"]); exit; } // 1. Получаем текущий хеш пароля из базы $sql = "SELECT password FROM users WHERE id = ?"; $stmt = $conn->prepare($sql); $stmt->bind_param("s", $user_id); $stmt->execute(); $result = $stmt->get_result(); if ($row = $result->fetch_assoc()) { $current_hash = $row['password']; // 2. Проверяем, совпадает ли СТАРЫЙ пароль с тем, что в базе if (password_verify($old_password, $current_hash)) { // 3. Если совпал - хешируем НОВЫЙ пароль $new_hash = password_hash($new_password, PASSWORD_DEFAULT); // 4. Обновляем в базе $update_sql = "UPDATE users SET password = ? WHERE id = ?"; $update_stmt = $conn->prepare($update_sql); $update_stmt->bind_param("ss", $new_hash, $user_id); if ($update_stmt->execute()) { echo json_encode(["success" => true, "message" => "Password updated successfully"]); } else { echo json_encode(["success" => false, "message" => "Update failed"]); } } else { // Старый пароль введен неверно echo json_encode(["success" => false, "message" => "Incorrect old password"]); } } else { echo json_encode(["success" => false, "message" => "User not found"]); } $stmt->close(); $conn->close(); ?> === ФАЙЛ: charge_ad.php === "error", "message" => "No IDs"]); exit(); } $owner_id = null; $source_table = ""; $safe_id = $conn->real_escape_string($target_id); // ================================================================= // 1. ПОИСК ВЛАДЕЛЬЦА (УМНЫЙ ПОИСК) // ================================================================= if (!$owner_id) { // Строим запрос динамически, чтобы не сравнивать Строку с Числом $sql = "SELECT owner_id FROM ad_campaigns WHERE (campaign_uuid = '$safe_id' OR target_id = '$safe_id')"; // Добавляем поиск по ID, ТОЛЬКО если пришло число if (is_numeric($safe_id)) { $sql .= " OR id = '$safe_id'"; } $sql .= " LIMIT 1"; $res = $conn->query($sql); if ($res && $row = $res->fetch_assoc()) { $owner_id = $row['owner_id']; $source_table = "ad_campaigns"; } } // Поиск в Магазинах if (!$owner_id) { $res = $conn->query("SELECT owner_id FROM grand_products WHERE id = '$safe_id'"); if ($res && $row = $res->fetch_assoc()) { $owner_id = $row['owner_id']; $source_table = "grand_products"; } } // Поиск у Частников if (!$owner_id) { $res = $conn->query("SELECT owner_id FROM products WHERE id = '$safe_id'"); if ($res && $row = $res->fetch_assoc()) { $owner_id = $row['owner_id']; $source_table = "products"; } } // ================================================================= // [НОВОЕ] ПРОВЕРКА БЮДЖЕТА ПЕРЕД СПИСАНИЕМ (STOP-LOSS + УВЕДОМЛЕНИЕ) // ================================================================= if ($owner_id) { // 1. Используем безопасный запрос (выбираем ID, лимит, расход и флаг уведомления) $sqlCheck = "SELECT id, budget_limit, spent, notified_complete FROM ad_campaigns WHERE (campaign_uuid = '$safe_id' OR target_id = '$safe_id')"; if (is_numeric($safe_id)) { $sqlCheck .= " OR id = '$safe_id'"; } $sqlCheck .= " LIMIT 1"; $checkBudget = $conn->query($sqlCheck); if ($checkBudget && $rowB = $checkBudget->fetch_assoc()) { // 2. Проверяем: Если Потрачено >= Лимит if ((float)$rowB['spent'] >= (float)$rowB['budget_limit']) { // А. МЕНЯЕМ СТАТУС НА 'COMPLETED' (Если еще не изменен) // Ищем по тем же критериям, чтобы обновить именно эту кампанию $whereClause = "(campaign_uuid = '$safe_id' OR target_id = '$safe_id')"; if (is_numeric($safe_id)) $whereClause .= " OR id = '$safe_id'"; $conn->query("UPDATE ad_campaigns SET status = 'COMPLETED' WHERE $whereClause"); // Б. ОТПРАВЛЯЕМ УВЕДОМЛЕНИЕ (Только 1 раз, если notified_complete == 0) if ($rowB['notified_complete'] == 0) { // 1. Ставим флажок "Уведомлен", чтобы не спамить $conn->query("UPDATE ad_campaigns SET notified_complete = 1 WHERE $whereClause"); // 2. Подготавливаем данные для уведомления $notifTitle = "Реклама завершена 🏁"; $notifMsg = "Бюджет вашей кампании исчерпан. Показ остановлен."; // Иконка системы (или логотип приложения) $sysAvatar = "https://cdn-icons-png.flaticon.com/512/10308/10308979.png"; // 3. Вставка в таблицу notifications (Как в toggle_like.php) // sender_id ставим равным owner_id (самому себе), или ID админа, если есть. // Здесь используем owner_id, чтобы уведомление точно привязалось к пользователю. $nSql = "INSERT INTO notifications (user_id, sender_id, type, action_id, title, message, user_avatar, created_at, is_read) VALUES (?, ?, 'system', ?, ?, ?, ?, NOW(), 0)"; // action_id берем реальный числовой ID кампании из базы ($rowB['id']) $realCampId = $rowB['id']; $nStmt = $conn->prepare($nSql); if ($nStmt) { // user_id = $owner_id (получатель) // sender_id = $owner_id (отправитель - система от имени юзера, чтобы не нарушать целостность) $nStmt->bind_param("ssssss", $owner_id, $owner_id, $realCampId, $notifTitle, $notifMsg, $sysAvatar); $nStmt->execute(); $nStmt->close(); } } // В. Отказываем в списании (чтобы баланс не ушел в минус) echo json_encode(["status" => "error", "message" => "Budget exceeded"]); exit(); // ⛔ СТОП МАШИНА } } } // ================================================================= // 2. СПИСАНИЕ СРЕДСТВ // ================================================================= if ($owner_id) { $conn->begin_transaction(); try { $cost = 0.05; // Цена за просмотр // 1. Списываем с баланса ЮЗЕРА $conn->query("UPDATE users SET balance = balance - $cost WHERE id = '$owner_id'"); // 2. Лог транзакции $stmt = $conn->prepare("INSERT INTO ad_billing_logs (user_id, campaign_id) VALUES (?, ?)"); $stmt->bind_param("ss", $visitor_id, $target_id); $stmt->execute(); // 3. Обновляем статистику (spent) // --- А. Если источник = КАМПАНИЯ --- if ($source_table == "ad_campaigns") { $conn->query("UPDATE ad_campaigns SET spent = spent + $cost, views_count = views_count + 1 WHERE campaign_uuid = '$safe_id' OR target_id = '$safe_id'"); if (is_numeric($safe_id)) { $conn->query("UPDATE ad_campaigns SET spent = spent + $cost, views_count = views_count + 1 WHERE id = '$safe_id'"); } } // --- Б. Если источник = ТОВАР --- elseif ($source_table == "grand_products") { $conn->query("UPDATE grand_products SET views_count = views_count + 1 WHERE id = '$safe_id'"); } elseif ($source_table == "products") { $conn->query("UPDATE products SET views = views + 1 WHERE id = '$safe_id'"); } // --- В. СИНХРОНИЗАЦИЯ (ТОВАР -> РЕКЛАМА) --- if ($source_table == "grand_products" || $source_table == "products") { $conn->query("UPDATE ad_campaigns SET spent = spent + $cost, views_count = views_count + 1 WHERE target_id = '$safe_id' AND status = 'ACTIVE'"); } $conn->commit(); echo json_encode(["status" => "success", "message" => "Charged"]); } catch (Exception $e) { $conn->rollback(); echo json_encode(["status" => "error", "message" => "DB Error: " . $e->getMessage()]); } } else { echo json_encode(["status" => "error", "message" => "Owner not found"]); } ?> === ФАЙЛ: charge_debug.php === connect_error) { die("ОШИБКА БД: " . $conn->connect_error . "\n"); } echo "2. БД подключена успешно.\n"; // Тест 2: Прямой запрос в grand_products echo "3. Ищем в таблице 'grand_products'...\n"; $sql = "SELECT * FROM grand_products WHERE id = '" . $conn->real_escape_string($target_id) . "'"; echo " SQL: $sql\n"; $res = $conn->query($sql); if (!$res) { echo " ОШИБКА SQL ЗАПРОСА: " . $conn->error . "\n"; } else { echo " Найдено строк: " . $res->num_rows . "\n"; if ($row = $res->fetch_assoc()) { echo " ✅ УСПЕХ! Владелец: " . $row['owner_id'] . "\n"; // Попытка списания (ТЕСТОВАЯ) $owner_id = $row['owner_id']; $update = $conn->query("UPDATE users SET balance = balance - 0.01 WHERE id = '$owner_id'"); if ($update) { echo " 💰 ТЕСТОВОЕ СПИСАНИЕ: Успешно (0.01 TMT)\n"; } else { echo " ⚠️ Ошибка списания: " . $conn->error . "\n"; } } else { echo " ❌ НЕ НАЙДЕНО (Хотя в базе есть! Проверьте пробелы в ID)\n"; } } echo "--- КОНЕЦ ДИАГНОСТИКИ ---\n"; ?> === ФАЙЛ: charge_view.php === "error", "message" => "Missing parameters"]); exit; } // 1. Получаем кампанию и её владельца $stmt = $conn->prepare("SELECT user_id, budget, spent FROM ad_campaigns WHERE id = ?"); $stmt->bind_param("s", $campaign_id); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows === 0) { echo json_encode(["status" => "error", "message" => "Campaign not found"]); exit; } $campaign = $result->fetch_assoc(); $user_id = $campaign['user_id']; $budget = floatval($campaign['budget']); $spent = floatval($campaign['spent']); $cost_val = floatval($cost); // 2. Проверяем, не исчерпан ли бюджет кампании if (($spent + $cost_val) > $budget) { echo json_encode(["status" => "error", "message" => "Campaign budget limit reached"]); exit; } // 3. Получаем баланс пользователя $user_stmt = $conn->prepare("SELECT balance FROM users WHERE id = ?"); $user_stmt->bind_param("s", $user_id); $user_stmt->execute(); $user_res = $user_stmt->get_result(); $user = $user_res->fetch_assoc(); $balance = floatval($user['balance']); // 4. Проверяем баланс пользователя if ($balance < $cost_val) { echo json_encode(["status" => "error", "message" => "Insufficient user funds"]); exit; } // 5. ТРАНЗАКЦИЯ (Самое важное!) // Начинаем транзакцию, чтобы всё выполнилось или ничего $conn->begin_transaction(); try { // Списываем с баланса пользователя $update_user = $conn->prepare("UPDATE users SET balance = balance - ? WHERE id = ?"); $update_user->bind_param("ds", $cost_val, $user_id); $update_user->execute(); // Добавляем к потраченному в кампании и увеличиваем счетчик просмотров $update_campaign = $conn->prepare("UPDATE ad_campaigns SET spent = spent + ?, views_count = views_count + 1 WHERE id = ?"); $update_campaign->bind_param("ds", $cost_val, $campaign_id); $update_campaign->execute(); // Фиксируем изменения $conn->commit(); echo json_encode([ "status" => "success", "message" => "Charged successfully", "new_balance" => $balance - $cost_val ]); } catch (Exception $e) { $conn->rollback(); // Если ошибка - отменяем всё echo json_encode(["status" => "error", "message" => "Transaction failed: " . $e->getMessage()]); } $conn->close(); ?> === ФАЙЛ: check_ads.php === query($sql); if ($result->num_rows > 0) { while($row = $result->fetch_assoc()) { $file_ext = pathinfo($row['media_url'], PATHINFO_EXTENSION); echo "ID: " . $row['id'] . " | Заголовок: " . $row['title'] . "\n"; echo " -> URL: " . $row['media_url'] . "\n"; echo " -> Расширение файла: ." . $file_ext . "\n"; echo " -> Где должна быть (Placements): " . $row['placements'] . "\n"; echo " -> Тип контента (ContentType): " . $row['content_type'] . "\n"; echo " -> Статус: " . $row['status'] . "\n"; echo "--------------------------------------------------\n"; } } else { echo "РЕКЛАМА В БАЗЕ НЕ НАЙДЕНА!\n"; } ?> === ФАЙЛ: check_ban_status.php === false]); exit(); } // Проверяем время бана $stmt = $conn->prepare("SELECT banned_until FROM users WHERE id = ?"); $stmt->bind_param("s", $user_id); $stmt->execute(); $res = $stmt->get_result(); $is_banned = false; $banned_until = null; if ($row = $res->fetch_assoc()) { $ban_time = $row['banned_until']; // Если время бана установлено и оно больше текущего времени if (!empty($ban_time) && strtotime($ban_time) > time()) { $is_banned = true; // Форматируем красиво дату для Android $banned_until = date('d.m.Y H:i', strtotime($ban_time)); } } $stmt->close(); echo json_encode([ 'is_banned' => $is_banned, 'banned_until' => $banned_until ]); ?> === ФАЙЛ: check_columns.php === connect_error) { die("Ошибка подключения: " . $conn->connect_error); } echo "=== СТРУКТУРА ТАБЛИЦЫ USERS ===\n"; $result = $conn->query("SHOW COLUMNS FROM users"); if ($result) { while ($row = $result->fetch_assoc()) { echo "Поле: " . $row['Field'] . " | Тип: " . $row['Type'] . "\n"; } } else { echo "Ошибка: Таблица 'users' не найдена или ошибка SQL: " . $conn->error; } ?> === ФАЙЛ: check_count.php === query("SELECT COUNT(*) as cnt FROM posts"); $row = $res->fetch_assoc(); $total = $row['cnt']; // Считаем по типам $resType = $conn->query("SELECT type, COUNT(*) as cnt FROM posts GROUP BY type"); $types = []; while($r = $resType->fetch_assoc()) { $types[] = $r; } echo json_encode([ "total_posts_in_db" => $total, "breakdown" => $types ], JSON_PRETTY_PRINT); ?> === ФАЙЛ: check_db.php === query($sql); printf("%-5s | %-20s | %-6s | %-6s\n", "ID", "TARGET_ID", "VIEWS", "CLICKS"); echo "--------------------------------------------------------\n"; if ($result) { while ($row = $result->fetch_assoc()) { printf("%-5s | %-20s | %-6s | %-6s\n", $row['id'], substr($row['target_id'], 0, 20), $row['views_count'], $row['clicks_count'] ); } } // 2. СМОТРИМ ФАЙЛ ЛОГОВ (Самое важное!) echo "\n\n=== ПОСЛЕДНИЕ 10 ЗАПИСЕЙ В ЖУРНАЛЕ (debug_clicks.txt) ===\n"; $logFile = __DIR__ . '/debug_clicks.txt'; if (file_exists($logFile)) { $lines = file($logFile); $last = array_slice($lines, -10); foreach ($last as $l) { echo $l; } } else { echo "Файл логов пуст или не существует."; } ?> === ФАЙЛ: check_db_structure.php === query($sql); if ($result && $result->num_rows > 0) { $row = $result->fetch_assoc(); echo "✅ Колонка 'allow_comments' НАЙДЕНА!\n"; echo "Тип данных: " . $row['Type'] . "\n"; echo "По умолчанию: " . ($row['Default'] === null ? "NULL" : $row['Default']) . "\n"; } else { echo "❌ Колонка 'allow_comments' ОТСУТСТВУЕТ.\n"; echo "Внимание: Функция отключения комментариев работать не будет, пока мы не добавим колонку.\n"; } echo "\n================================"; ?> === ФАЙЛ: check_favorite.php === "error", "message" => "Missing data"]); exit(); } // Просто проверяем, есть ли такая запись $sql = "SELECT id FROM favorites WHERE user_id = ? AND item_id = ? AND type = ?"; $stmt = $conn->prepare($sql); $stmt->bind_param("sss", $user_id, $item_id, $type); $stmt->execute(); $stmt->store_result(); if ($stmt->num_rows > 0) { echo json_encode(["status" => "success", "is_favorite" => true]); } else { echo json_encode(["status" => "success", "is_favorite" => false]); } ?> === ФАЙЛ: check_notifications.php === set_charset("utf8mb4"); // Функция для логов function logCheck($msg) { file_put_contents(__DIR__ . '/debug_notif_check.txt', date('[H:i:s] ') . $msg . PHP_EOL, FILE_APPEND); } $user_id = isset($_POST['user_id']) ? $_POST['user_id'] : ''; if (empty($user_id)) { echo json_encode(array("status" => "error", "message" => "No User ID")); exit(); } // [ИЗМЕНЕНО] Добавили фильтр по дате (последние 3 дня) $sql = "SELECT id, title, message, type, action_id, sender_id, post_preview, user_avatar, created_at, is_read FROM notifications WHERE user_id = ? AND is_read = 0 AND created_at > (NOW() - INTERVAL 3 DAY) ORDER BY created_at DESC"; $stmt = $conn->prepare($sql); if (!$stmt) { logCheck("❌ SQL Prepare Error: " . $conn->error); echo json_encode(array("status" => "error", "message" => "SQL Error")); exit(); } $stmt->bind_param("s", $user_id); $stmt->execute(); $result = $stmt->get_result(); $notifications = array(); $ids_to_mark_read = array(); // [ВАЖНО] Создаем массив для ID while ($row = $result->fetch_assoc()) { // 1. Логика картинок (Товары) if (($row['type'] === 'new_ad' || $row['type'] === 'new_grand_ad') && empty($row['user_avatar'])) { $row['user_avatar'] = $row['post_preview']; } // 2. Исправляем пути if (!empty($row['user_avatar']) && strpos($row['user_avatar'], 'http') === false) { $row['user_avatar'] = "http://216.250.14.77/api/v1/uploads/" . $row['user_avatar']; } // Логи if ($row['type'] === 'new_ad') { logCheck("📤 Sending NEW_AD to $user_id | Img: " . substr($row['post_preview'], -20) . " | Ava: " . ($row['user_avatar'] ? 'YES' : 'EMPTY')); } $notifications[] = $row; // [!!! ВОТ ЭТО ВЫ ЗАБЫЛИ !!!] // Запоминаем ID, чтобы потом пометить как прочитанное $ids_to_mark_read[] = $row['id']; } $stmt->close(); // Закрываем запрос SELECT // [ОБНОВЛЕНИЕ СТАТУСА] if (!empty($ids_to_mark_read)) { // Превращаем массив [1, 5, 8] в строку "1,5,8" $ids_string = implode(',', $ids_to_mark_read); // Выполняем массовое обновление $updateSql = "UPDATE notifications SET is_read = 1 WHERE id IN ($ids_string)"; // Используем $conn->query, так как это простой запрос без параметров пользователя if ($conn->query($updateSql)) { logCheck("✅ Отмечены как прочитанные ID: $ids_string"); } else { logCheck("⚠️ Ошибка обновления статуса: " . $conn->error); } } echo json_encode(array( "status" => "success", "count" => count($notifications), "data" => $notifications ), JSON_UNESCAPED_UNICODE); $conn->close(); ?> === ФАЙЛ: check_reels.php === query("SELECT COUNT(*) as c FROM posts WHERE (type = 'VIDEO' OR type = 'REEL' OR video_url != '') AND type != 'STORY'"); $total = $q1->fetch_assoc()['c']; // Считаем только ВАШИ видео $q2 = $conn->query("SELECT COUNT(*) as c FROM posts WHERE author_id = '$uid' AND (type = 'VIDEO' OR type = 'REEL' OR video_url != '') AND type != 'STORY'"); $my = $q2->fetch_assoc()['c']; // Считаем видео ваших подписок $q3 = $conn->query("SELECT COUNT(*) as c FROM posts WHERE author_id IN (SELECT following_id FROM follows WHERE follower_id = '$uid') AND (type = 'VIDEO' OR type = 'REEL' OR video_url != '') AND type != 'STORY'"); $friends = $q3->fetch_assoc()['c']; echo "=== ОТЧЕТ ПО БАЗЕ ДАННЫХ ===\n"; echo "Всего видео (Reels) во всем приложении: $total\n"; echo "Лично ваших видео: $my\n"; echo "Видео ваших подписок: $friends\n"; echo "Сумма для вашей ленты: " . ($my + $friends) . "\n"; echo "=============================\n"; ?> === ФАЙЛ: check_reviews.php === query('SELECT * FROM store_reviews'); echo 'TOTAL REVIEWS: ' . $res->num_rows . "\n"; while($r=$res->fetch_assoc()){ echo 'ProdID: '.['product_id'].' | Rate: '.['rating']."\n"; } ?> === ФАЙЛ: check_stats.php === connect_error) { die("Ошибка подключения: " . $conn->connect_error); } // 1. СЧИТАЕМ КОЛИЧЕСТВО ПО ТИПАМ $sql = "SELECT type, COUNT(*) as count FROM posts GROUP BY type"; $result = $conn->query($sql); echo "1. СТАТИСТИКА ПО ТИПАМ КОНТЕНТА:\n"; echo "---------------------------------\n"; if ($result && $result->num_rows > 0) { while($row = $result->fetch_assoc()) { echo "[ " . str_pad($row['type'], 10) . " ] : " . $row['count'] . "\n"; } } else { echo "База пуста или ошибка запроса.\n"; } echo "---------------------------------\n\n"; // 2. ПРОВЕРЯЕМ ПОСЛЕДНИЕ 5 ЗАПИСЕЙ (Чтобы видеть поля) echo "2. ПОСЛЕДНИЕ 5 ЗАПИСЕЙ (Проверка полей):\n"; $sql2 = "SELECT id, type, author_id, video_url, image_url, allow_comments FROM posts ORDER BY created_at DESC LIMIT 5"; $result2 = $conn->query($sql2); if ($result2) { while($row = $result2->fetch_assoc()) { $ac = isset($row['allow_comments']) ? $row['allow_comments'] : "NET"; echo "ID: " . substr($row['id'], 0, 8) . "... | Тип: " . $row['type'] . " | AllowComm: " . $ac . " | Vid: " . (empty($row['video_url']) ? "NO" : "YES") . "\n"; } } echo "\n========================================"; ?> === ФАЙЛ: check_user_avatar.php === query($sql); if ($result && $row = $result->fetch_assoc()) { echo "Имя: " . $row['name'] . "\n"; echo "Аватар в БД: '" . $row['avatar'] . "'\n"; // Покажет точно, что внутри кавычек if (empty($row['avatar'])) { echo "❌ ВНИМАНИЕ: Поле аватара ПУСТОЕ! Скрипт add_product не виноват.\n"; } else { echo "✅ Аватар есть! Проблема в add_product.php\n"; } } else { echo "❌ Пользователь не найден!\n"; } ?> === ФАЙЛ: clean_posts.php === set_charset("utf8mb4"); $action = isset($_GET['action']) ? $_GET['action'] : 'scan'; echo "

🧹 Очистка битых постов

"; // 1. ЛОГИКА ПОИСКА "СИРОТ" // Мы ищем посты, где при соединении с таблицей users результат NULL $sqlFind = "SELECT p.id, p.author_id, p.content, p.created_at FROM posts p LEFT JOIN users u ON TRIM(p.author_id) = TRIM(u.id) WHERE u.id IS NULL"; $result = $conn->query($sqlFind); $count = $result->num_rows; if ($action == 'delete') { // === РЕЖИМ УДАЛЕНИЯ === if ($count > 0) { // Удаляем именно те строки, которые нашли выше $sqlDelete = "DELETE p FROM posts p LEFT JOIN users u ON TRIM(p.author_id) = TRIM(u.id) WHERE u.id IS NULL"; if ($conn->query($sqlDelete)) { echo "

✅ УСПЕШНО УДАЛЕНО: $count ПОСТОВ

"; echo "

База данных теперь чиста. Обновите ленту в приложении.

"; echo ""; } else { echo "

❌ Ошибка удаления: " . $conn->error . "

"; } } else { echo "

Нет постов для удаления.

"; } } else { // === РЕЖИМ СКАНИРОВАНИЯ === echo "

Статус: СКАНИРОВАНИЕ...

"; if ($count > 0) { echo "

Найдено битых постов: $count

"; echo "

Это посты, у которых автор не найден в базе (или ID испорчен символами).

"; echo ""; while ($row = $result->fetch_assoc()) { echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } echo "
Post IDAuthor ID (Битый)КонтентДействие
" . $row['id'] . "'" . $row['author_id'] . "'" . substr($row['content'], 0, 50) . "...⚠️ БИТЫЙ
"; echo "

"; echo " "; } else { echo "

✅ База данных идеально чиста!

"; echo "

Нет постов без авторов.

"; } } ?> === ФАЙЛ: confirm_sms_sent.php === "error", "message" => "ID required"]); exit(); } $stmt = $conn->prepare("UPDATE otp_queue SET status = ? WHERE id = ?"); $stmt->bind_param("ss", $status, $id); if ($stmt->execute()) { echo json_encode(["status" => "success"]); } else { echo json_encode(["status" => "error"]); } $conn->close(); ?> === ФАЙЛ: create_campaign.php === "error", "message" => "DB Connection failed"]); exit(); } // 2. ЧИТАЕМ RAW ДАННЫЕ ОТ ANDROID $input = file_get_contents("php://input"); logToDebug("📥 RAW INPUT (Что прислал Android): " . $input); $data = json_decode($input, true); if (json_last_error() !== JSON_ERROR_NONE) { logToDebug("❌ JSON ERROR: Некорректный формат JSON. Код ошибки: " . json_last_error()); echo json_encode(["status" => "error", "message" => "Invalid JSON"]); exit(); } if (empty($data)) { logToDebug("⚠️ WARNING: Пришел пустой JSON"); echo json_encode(["status" => "error", "message" => "No data received"]); exit(); } // 3. РАЗБОР ДАННЫХ $campaign_uuid = $data['campaign_uuid'] ?? uniqid('ad_'); $owner_id = $data['owner_id'] ?? ''; $target_type = $data['target_type'] ?? 'PRODUCT'; // PRODUCT, STORE, POST $target_id = $data['target_id'] ?? ''; $content_type = $data['content_type'] ?? 'IMAGE'; // IMAGE, VIDEO $media_url = $data['media_url'] ?? ''; // Массив мест размещения превращаем в строку (FEED,STORY...) $placementsRaw = $data['placements'] ?? []; if (is_array($placementsRaw)) { $placements = implode(',', $placementsRaw); } else { $placements = (string)$placementsRaw; } // Таргетинг $location = $data['location'] ?? 'All'; $gender = $data['gender'] ?? 'Any'; $age_from = (int)($data['age_from'] ?? 18); $age_to = (int)($data['age_to'] ?? 65); $interests = $data['interests'] ?? ''; // Финансы $budget = (float)($data['budget'] ?? 0.00); // ============================================================================== // [НОВОЕ] Аукционные параметры eCPM (с защитой старых версий Android) // ============================================================================== $bid = isset($data['bid']) ? (float)$data['bid'] : 0.0500; // Старый тариф по умолчанию $bid_type = isset($data['bid_type']) ? trim($data['bid_type']) : 'CPC'; // CPC или CPM $daily_budget = isset($data['daily_budget']) ? (float)$data['daily_budget'] : $budget; // По умолчанию равен общему бюджету // Тексты $title = $data['title'] ?? ''; $desc = $data['description'] ?? ''; // [ИЗМЕНЕНО] Добавим новые данные в лог для отладки logToDebug("🔍 PARSED DATA: UUID=$campaign_uuid | Owner=$owner_id | Type=$target_type | Bid=$bid ($bid_type) | Daily=$daily_budget"); // 4. ВАЛИДАЦИЯ if (empty($owner_id) || empty($target_id)) { logToDebug("❌ VALIDATION FAIL: Не хватает owner_id или target_id"); echo json_encode(["status" => "error", "message" => "Required fields missing"]); exit(); } // 5. ЗАПИСЬ В БД // [ИЗМЕНЕНО] Добавлены bid, bid_type, daily_budget $sql = "INSERT INTO ad_campaigns (campaign_uuid, owner_id, target_type, target_id, content_type, media_url, placements, location, gender, age_from, age_to, interests, budget_limit, title, description, bid, bid_type, daily_budget) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; // Добавлено 3 вопроса $stmt = $conn->prepare($sql); if ($stmt) { // [ИЗМЕНЕНО] Строка типов параметров теперь содержит 'dsd' в конце (double, string, double) // Всего 18 параметров: "sssssssssissdssdsd" $stmt->bind_param("sssssssssissdssdsd", $campaign_uuid, $owner_id, $target_type, $target_id, $content_type, $media_url, $placements, $location, $gender, $age_from, $age_to, $interests, $budget, $title, $desc, $bid, $bid_type, $daily_budget // [НОВОЕ] Передаем аукционные параметры ); if ($stmt->execute()) { logToDebug("✅ SUCCESS: Кампания создана! ID в базе: " . $stmt->insert_id); echo json_encode([ "status" => "success", "message" => "Campaign created", "campaign_id" => $stmt->insert_id ]); } else { logToDebug("❌ SQL EXECUTE ERROR: " . $stmt->error); // Самый важный жучок http_response_code(500); echo json_encode(["status" => "error", "message" => "Database error: " . $stmt->error]); } } else { logToDebug("❌ SQL PREPARE ERROR: " . $conn->error); http_response_code(500); echo json_encode(["status" => "error", "message" => "Prepare failed: " . $conn->error]); } logToDebug("🏁 END REQUEST"); ?> === ФАЙЛ: db.php === connect_error) { // ВНИМАНИЕ: Не выводим детали ошибки пользователю, чтобы не пугать json header('Content-Type: application/json'); die(json_encode(["status" => "error", "message" => "Database connection failed"])); } // Устанавливаем кодировку (важно для эмодзи и туркменского языка) $conn->set_charset("utf8mb4"); // ВАЖНО: Мы СПЕЦИАЛЬНО удалили закрывающий тег в конце, // чтобы избежать случайных пробелов, которые ломают JSON. === ФАЙЛ: db_spy.php === set_charset("utf8mb4"); echo "=== СТРУКТУРА ТАБЛИЦЫ POSTS (media_urls) ===\n"; $sql = "SELECT id, author_id, media_urls FROM posts ORDER BY created_at DESC LIMIT 5"; $res = $conn->query($sql); if ($res) { while ($row = $res->fetch_assoc()) { echo "------------------------------------------------\n"; echo "ID POST: " . $row['id'] . "\n"; echo "AUTHOR : '" . $row['author_id'] . "'\n"; echo "RAW MEDIA (Как лежит в базе):\n"; var_dump($row['media_urls']); echo "DECODED (Попытка раскодировать):\n"; $decoded = json_decode($row['media_urls'], true); var_dump($decoded); echo "JSON ERROR: " . json_last_error_msg() . "\n"; } } else { echo "Ошибка SQL: " . $conn->error; } echo "================================================\n"; ?> === ФАЙЛ: db_spy_author.php === query($sql); while ($post = $res->fetch_assoc()) { $p_auth = $post['author_id']; $len = strlen($p_auth); echo "\n[POST ID: " . substr($post['id'], 0, 8) . "...]\n"; echo "Post AuthorID: '$p_auth' (Длина: $len)\n"; // Смотрим коды символов (ASCII), чтобы увидеть скрытое echo "HEX коды: "; for ($i = 0; $i < $len; $i++) { echo dechex(ord($p_auth[$i])) . " "; } echo "\n"; // 2. Ищем этого юзера в таблице USERS $u_sql = "SELECT id, name FROM users WHERE id = '" . $conn->real_escape_string($p_auth) . "'"; $u_res = $conn->query($u_sql); if ($u_res && $u_row = $u_res->fetch_assoc()) { echo "✅ FOUND in Users: " . $u_row['name'] . "\n"; } else { echo "❌ NOT FOUND in Users (Прямой поиск провалился)\n"; // Попытка с TRIM $trimmed = trim($p_auth); $t_sql = "SELECT id, name FROM users WHERE id = '" . $conn->real_escape_string($trimmed) . "'"; $t_res = $conn->query($t_sql); if ($t_res && $t_row = $t_res->fetch_assoc()) { echo "⚠️ FOUND via TRIM: " . $t_row['name'] . " (В базе есть пробелы!)\n"; } } } ?> === ФАЙЛ: debug_billing.php === Отчет по рекламе"; // 1. Последние списания $res = $conn->query("SELECT * FROM ad_billing_logs ORDER BY created_at DESC LIMIT 5"); echo "

Последние логи списаний:

"; while($row = $res->fetch_assoc()) { echo ""; } echo "
{$row['created_at']}Юзер: {$row['user_id']}Кампания: {$row['campaign_id']}
"; // 2. Состояние кошелька $res = $conn->query("SELECT name, balance FROM users WHERE balance > 0"); echo "

Балансы:

"; while($row = $res->fetch_assoc()) { echo "{$row['name']}: {$row['balance']} TMT
"; } ?> === ФАЙЛ: debug_structure.php === query("SHOW TABLES"); if ($res) { while ($row = $res->fetch_array()) { $tables[] = $row[0]; echo " - " . $row[0] . "\n"; } } else { echo "❌ Ошибка SHOW TABLES: " . $conn->error . "\n"; } echo "\n"; // 2. Ищем таблицы подписок $suspects = ['follows', 'followers', 'subscribers', 'friends', 'subscription']; echo "[2] ПОИСК ТАБЛИЦ ПОДПИСОК:\n"; foreach ($suspects as $name) { if (in_array($name, $tables)) { echo "✅ НАЙДЕНА ТАБЛИЦА: '$name'\n"; echo " Колонки:\n"; $cols = $conn->query("DESCRIBE $name"); if ($cols) { while ($col = $cols->fetch_assoc()) { echo " - " . $col['Field'] . " (" . $col['Type'] . ")\n"; } } // Считаем записи $count = $conn->query("SELECT COUNT(*) as c FROM $name")->fetch_assoc()['c']; echo " Всего записей: $count\n"; // Показываем последние 3 записи echo " Примеры данных (последние 3):\n"; $data = $conn->query("SELECT * FROM $name ORDER BY 1 DESC LIMIT 3"); // Сортируем, чтобы видеть свежие if ($data) { while ($d = $data->fetch_assoc()) { print_r($d); } } echo "-----------------------------------\n"; } } echo "\n=== КОНЕЦ АНАЛИЗА ==="; ?> === ФАЙЛ: delete_account.php === "error", "message" => "User ID is required"]); exit; } // 3. Начинаем транзакцию $conn->begin_transaction(); // Временно отключаем паранойю базы данных $conn->query("SET FOREIGN_KEY_CHECKS=0"); try { // ТОТАЛЬНАЯ ЗАЧИСТКА $conn->query("DELETE FROM posts WHERE author_id = '$user_id'"); $conn->query("DELETE FROM products WHERE owner_id = '$user_id'"); $conn->query("DELETE FROM grand_products WHERE owner_id = '$user_id'"); $conn->query("DELETE FROM stores WHERE owner_id = '$user_id'"); $conn->query("DELETE FROM ad_wallets WHERE owner_id = '$user_id'"); $conn->query("DELETE FROM ad_campaigns WHERE owner_id = '$user_id'"); $conn->query("DELETE FROM follows WHERE follower_id = '$user_id' OR following_id = '$user_id'"); $conn->query("DELETE FROM notifications WHERE user_id = '$user_id' OR sender_id = '$user_id'"); $conn->query("DELETE FROM messages WHERE sender_id = '$user_id' OR receiver_id = '$user_id'"); // Финальный выстрел: Удаление самого юзера $stmt = $conn->prepare("DELETE FROM users WHERE id = ?"); $stmt->bind_param("s", $user_id); $stmt->execute(); if ($stmt->affected_rows === 0) { throw new Exception("Пользователь $user_id не найден в базе данных."); } $stmt->close(); // Сохраняем изменения $conn->commit(); file_put_contents('debug_delete.txt', date('Y-m-d H:i:s') . " | SUCCESS: User $user_id fully deleted\n", FILE_APPEND); echo json_encode(["status" => "success", "message" => "Account fully deleted"]); } catch (Exception $e) { // Если произошла ошибка, отменяем все удаления $conn->rollback(); file_put_contents('debug_delete.txt', date('Y-m-d H:i:s') . " | ERROR: " . $e->getMessage() . "\n", FILE_APPEND); // Возвращаем ошибку 400 http_response_code(400); echo json_encode(["status" => "error", "message" => $e->getMessage()]); } // Включаем проверки БД обратно $conn->query("SET FOREIGN_KEY_CHECKS=1"); $conn->close(); ?> === ФАЙЛ: delete_campaign.php === "error", "message" => "Missing ID or Owner"]); exit(); } // Удаляем, только если ID и Owner совпадают $sql = "DELETE FROM ad_campaigns WHERE id = ? AND owner_id = ?"; $stmt = $conn->prepare($sql); $stmt->bind_param("ss", $id, $ownerId); if ($stmt->execute()) { if ($stmt->affected_rows > 0) { echo json_encode(["status" => "success", "message" => "Deleted"]); } else { // Если affected_rows == 0, значит либо рекламы нет, либо вы не владелец echo json_encode(["status" => "error", "message" => "Not found or access denied"]); } } else { echo json_encode(["status" => "error", "message" => "SQL Error"]); } ?> === ФАЙЛ: delete_chat.php === "error", "message" => "Missing parameters"]); exit(); } if ($for_everyone === 1) { // [ИЗМЕНЕНО] Удаляем полностью из базы (для обоих пользователей) $sql = "DELETE FROM messages WHERE (sender_id = ? AND receiver_id = ?) OR (sender_id = ? AND receiver_id = ?)"; $stmt = $conn->prepare($sql); $stmt->bind_param("ssss", $user_id, $target_id, $target_id, $user_id); $stmt->execute(); $stmt->close(); } else { // [НОВОЕ] Удаляем только у себя (мягкое скрытие) // Если я был отправителем - скрываем на моей стороне $sql1 = "UPDATE messages SET deleted_by_sender = 1 WHERE sender_id = ? AND receiver_id = ?"; $stmt1 = $conn->prepare($sql1); $stmt1->bind_param("ss", $user_id, $target_id); $stmt1->execute(); $stmt1->close(); // Если я был получателем - скрываем на моей стороне $sql2 = "UPDATE messages SET deleted_by_receiver = 1 WHERE receiver_id = ? AND sender_id = ?"; $stmt2 = $conn->prepare($sql2); $stmt2->bind_param("ss", $user_id, $target_id); $stmt2->execute(); $stmt2->close(); } echo json_encode(["status" => "success", "message" => "Chat updated successfully"]); $conn->close(); ?> === ФАЙЛ: delete_comment.php === "error", "message" => "Missing data"]); exit(); } // 2. ПРОВЕРЯЕМ ВЛАДЕЛЬЦА И ПОЛУЧАЕМ POST_ID // [ВАЖНО] В таблице comments автор записан как 'user_id', а не 'author_id' $check = $conn->prepare("SELECT user_id, post_id FROM comments WHERE id = ?"); $check->bind_param("s", $comment_id); $check->execute(); $res = $check->get_result(); if ($row = $res->fetch_assoc()) { $real_author_id = $row['user_id']; $post_id = $row['post_id']; // Сравниваем: тот, кто просит удалить ($user_id) === тот, кто написал ($real_author_id) if ($real_author_id === $user_id) { // 3. УДАЛЯЕМ КОММЕНТАРИЙ $del = $conn->prepare("DELETE FROM comments WHERE id = ?"); $del->bind_param("s", $comment_id); if ($del->execute()) { // 4. ОБНОВЛЯЕМ СЧЕТЧИК ПОСТА $upd = $conn->prepare("UPDATE posts SET comments_count = GREATEST(comments_count - 1, 0) WHERE id = ?"); $upd->bind_param("s", $post_id); $upd->execute(); // 5. ЧИСТИМ ЛАЙКИ ЭТОГО КОММЕНТАРИЯ (чтобы не засорять базу) $conn->query("DELETE FROM comment_likes WHERE comment_id = '$comment_id'"); echo json_encode(["status" => "success", "message" => "Deleted"]); } else { echo json_encode(["status" => "error", "message" => "Delete failed"]); } } else { echo json_encode(["status" => "error", "message" => "Permission denied"]); } } else { echo json_encode(["status" => "error", "message" => "Comment not found"]); } $conn->close(); ?> === ФАЙЛ: delete_highlight.php === "error", "message" => "Missing fields"]); exit(); } // Удаляем, ТОЛЬКО если user_id совпадает (безопасность: чтобы чужое не удалили) $stmt = $conn->prepare("DELETE FROM user_highlights WHERE id = ? AND user_id = ?"); $stmt->bind_param("ss", $highlight_id, $user_id); if ($stmt->execute()) { if ($stmt->affected_rows > 0) { // Успешно удалено echo json_encode(["status" => "success"]); } else { // Или не найдено, или вы не владелец echo json_encode(["status" => "error", "message" => "Highlight not found or access denied"]); } } else { echo json_encode(["status" => "error", "message" => "DB error"]); } $stmt->close(); $conn->close(); ?> === ФАЙЛ: delete_message.php === "error", "message" => "Missing data (message_id or user_id)"]); exit(); } // 3. БЕЗОПАСНОЕ УДАЛЕНИЕ // Мы проверяем AND sender_id = ?, чтобы пользователь мог удалить ТОЛЬКО свои сообщения $sql = "DELETE FROM messages WHERE id = ? AND sender_id = ?"; if ($stmt = $conn->prepare($sql)) { $stmt->bind_param("ss", $message_id, $user_id); if ($stmt->execute()) { // Проверяем, была ли удалена строка if ($stmt->affected_rows > 0) { echo json_encode(["status" => "success", "message" => "Message deleted successfully"]); } else { // Если affected_rows == 0, значит сообщение не найдено ИЛИ оно чужое echo json_encode(["status" => "error", "message" => "Message not found or you are not the sender"]); } } else { echo json_encode(["status" => "error", "message" => "Execution failed: " . $stmt->error]); } $stmt->close(); } else { echo json_encode(["status" => "error", "message" => "Prepare failed: " . $conn->error]); } $conn->close(); ?> === ФАЙЛ: delete_post.php === "error", "message" => "Missing parameters"]); exit(); } $post_id = $_POST['post_id']; $user_id = $_POST['user_id']; // 1. Проверяем, существует ли пост и принадлежит ли он этому пользователю $check_sql = "SELECT id FROM posts WHERE id = ? AND author_id = ?"; $stmt = $conn->prepare($check_sql); $stmt->bind_param("ss", $post_id, $user_id); $stmt->execute(); $stmt->store_result(); if ($stmt->num_rows == 0) { echo json_encode(["status" => "error", "message" => "Post not found or access denied"]); $stmt->close(); $conn->close(); exit(); } $stmt->close(); // 2. Удаляем пост $delete_sql = "DELETE FROM posts WHERE id = ?"; $del_stmt = $conn->prepare($delete_sql); $del_stmt->bind_param("s", $post_id); if ($del_stmt->execute()) { echo json_encode(["status" => "success", "message" => "Post deleted"]); } else { echo json_encode(["status" => "error", "message" => "Database error"]); } $del_stmt->close(); $conn->close(); ?> === ФАЙЛ: delete_product.php === "error", "message" => "Missing data"]); exit(); } // 1. Сначала получаем список картинок, чтобы удалить файлы (чистим мусор) $sql = "SELECT image_urls FROM products WHERE id = ? AND owner_id = ?"; $stmt = $conn->prepare($sql); $stmt->bind_param("ss", $product_id, $owner_id); $stmt->execute(); $result = $stmt->get_result(); $row = $result->fetch_assoc(); if (!$row) { echo json_encode(["status" => "error", "message" => "Product not found or not yours"]); exit(); } // Удаляем файлы с диска (Опционально, но профессионально) if (!empty($row['image_urls'])) { $files = json_decode($row['image_urls'], true); if (is_array($files)) { foreach ($files as $url) { // Превращаем URL в путь к файлу: http://site.com/api/v1/uploads/file.jpg -> uploads/file.jpg $filename = basename($url); $filepath = __DIR__ . "/uploads/" . $filename; if (file_exists($filepath)) { unlink($filepath); } } } } // 2. Удаляем запись из БД $delSql = "DELETE FROM products WHERE id = ? AND owner_id = ?"; $delStmt = $conn->prepare($delSql); $delStmt->bind_param("ss", $product_id, $owner_id); if ($delStmt->execute()) { echo json_encode(["status" => "success", "message" => "Deleted"]); } else { echo json_encode(["status" => "error", "message" => "DB Error"]); } ?> === ФАЙЛ: delete_store.php === "error", "message" => "Отсутствуют ID пользователя или магазина"]); exit(); } // 1. Проверяем, существует ли магазин и принадлежит ли он этому пользователю $stmt = $conn->prepare("SELECT id FROM stores WHERE id = ? AND owner_id = ?"); $stmt->bind_param("ss", $store_id, $user_id); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows === 0) { echo json_encode(["status" => "error", "message" => "Магазин не найден или у вас нет прав на удаление"]); $stmt->close(); exit(); } $stmt->close(); // === ЗАПУСКАЕМ ТРАНЗАКЦИЮ ДЛЯ ПОЛНОЙ И БЕЗОПАСНОЙ ЗАЧИСТКИ === $conn->begin_transaction(); // ============================================================================== // [НОВОЕ] Отключаем ключи, чтобы БД не блокировала удаление из-за связанных таблиц // ============================================================================== $conn->query("SET FOREIGN_KEY_CHECKS=0"); try { // 2. Удаляем все отзывы к товарам ЭТОГО магазина $stmt1 = $conn->prepare("DELETE r FROM store_reviews r INNER JOIN grand_products p ON r.product_id = p.id WHERE p.store_id = ?"); $stmt1->bind_param("s", $store_id); $stmt1->execute(); $stmt1->close(); // 3. Удаляем все товары ЭТОГО магазина (из grand_products) $stmt2 = $conn->prepare("DELETE FROM grand_products WHERE store_id = ? AND owner_id = ?"); $stmt2->bind_param("ss", $store_id, $user_id); $stmt2->execute(); $stmt2->close(); // ============================================================================== // [НОВОЕ] 3.5 Удаляем товары магазина из ОБЫЧНОЙ ленты товаров (products) // ============================================================================== $stmt_prod = $conn->prepare("DELETE FROM products WHERE store_id = ? AND owner_id = ?"); $stmt_prod->bind_param("ss", $store_id, $user_id); $stmt_prod->execute(); $stmt_prod->close(); // 4. Удаляем сам магазин $stmt3 = $conn->prepare("DELETE FROM stores WHERE id = ? AND owner_id = ?"); $stmt3->bind_param("ss", $store_id, $user_id); if (!$stmt3->execute()) { throw new Exception("Не удалось удалить карточку магазина: " . $stmt3->error); } // [НОВОЕ] Проверка, что строка реально удалилась if ($stmt3->affected_rows === 0) { throw new Exception("Ошибка БД: Магазин не удален"); } $stmt3->close(); // 5. Фиксируем изменения! $conn->commit(); // (Опционально) Пишем в лог, что магазин уничтожен file_put_contents('debug_billing.txt', date('Y-m-d H:i:s') . " | DELETE STORE | User $user_id DELETED Store $store_id\n", FILE_APPEND); echo json_encode(["status" => "success", "message" => "Магазин и все товары удалены"]); } catch (Exception $e) { $conn->rollback(); // Если что-то пошло не так, отменяем все удаления! echo json_encode(["status" => "error", "message" => $e->getMessage()]); } // [НОВОЕ] Включаем проверки обратно $conn->query("SET FOREIGN_KEY_CHECKS=1"); $conn->close(); ?> === ФАЙЛ: delete_store_product.php === "error", "message" => "Missing data"]); exit(); } // 1. Ищем товар в таблице МАГАЗИНОВ (grand_products) $sql = "SELECT images_json FROM grand_products WHERE id = ? AND owner_id = ?"; $stmt = $conn->prepare($sql); $stmt->bind_param("ss", $product_id, $owner_id); $stmt->execute(); $result = $stmt->get_result(); $row = $result->fetch_assoc(); if (!$row) { echo json_encode(["status" => "error", "message" => "Store Product not found"]); exit(); } // 2. Удаляем файлы картинок с диска if (!empty($row['images_json'])) { $files = json_decode($row['images_json'], true); if (is_array($files)) { foreach ($files as $url) { // Превращаем URL в путь к файлу $filename = basename($url); // Используем абсолютный путь, так же как при создании $filepath = __DIR__ . "/uploads/" . $filename; if (file_exists($filepath)) { unlink($filepath); } } } } // 3. Удаляем запись из БД $delSql = "DELETE FROM grand_products WHERE id = ? AND owner_id = ?"; $delStmt = $conn->prepare($delSql); $delStmt->bind_param("ss", $product_id, $owner_id); if ($delStmt->execute()) { echo json_encode(["status" => "success", "message" => "Store Product Deleted"]); } else { echo json_encode(["status" => "error", "message" => "DB Error: " . $conn->error]); } $stmt->close(); $delStmt->close(); $conn->close(); ?> === ФАЙЛ: diagnose_time.php === date('Y-m-d H:i:s'), "2_PHP_TIMEZONE" => date_default_timezone_get(), ]; // Проверяем время внутри самой базы данных MySQL $res = $conn->query("SELECT NOW() as mysql_now"); if ($row = $res->fetch_assoc()) { $diagnostic_data["3_MYSQL_TIME_NOW"] = $row['mysql_now']; } // Смотрим реальные даты ваших магазинов if (!empty($user_id)) { $safe_id = $conn->real_escape_string($user_id); $res_stores = $conn->query("SELECT id, name, subscription_expires_at FROM stores WHERE owner_id = '$safe_id'"); $stores = []; while ($store = $res_stores->fetch_assoc()) { // Считаем, активен ли он по мнению MySQL $expire_time = strtotime($store['subscription_expires_at']); $is_active = ($expire_time > time()) ? "ДА (АКТИВЕН)" : "НЕТ (ВРЕМЯ ВЫШЛО/ЗАМОРОЖЕН)"; $stores[] = [ "store_name" => $store['name'], "expires_at" => $store['subscription_expires_at'], "server_sees_it_as" => $is_active ]; } $diagnostic_data["4_YOUR_STORES"] = $stores; } else { $diagnostic_data["4_YOUR_STORES"] = "Передайте ?user_id=ВАШ_ID в ссылке, чтобы увидеть магазины"; } // Выводим красиво echo json_encode($diagnostic_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); $conn->close(); ?> === ФАЙЛ: diagnostics.php === cat << 'EOF' > /var/www/html/api/v1/diagnostics.php query("SELECT id FROM posts WHERE is_moderated = 0"); echo "1. POSTS (is_moderated=0): " . $res1->num_rows . " штук\n"; } catch (Exception $e) { echo "1. POSTS ОШИБКА: " . $e->getMessage() . "\n"; } try { // 2. Проверка таблицы grand_products $res2 = $conn->query("SELECT id FROM grand_products WHERE is_moderated = 0"); echo "2. GRAND_PRODUCTS (is_moderated=0): " . $res2->num_rows . " штук\n"; } catch (Exception $e) { echo "2. GRAND_PRODUCTS ОШИБКА: " . $e->getMessage() . "\n"; } try { // 3. Проверка таблицы products $res3 = $conn->query("SELECT id FROM products WHERE is_moderated = 0"); echo "3. PRODUCTS (is_moderated=0): " . $res3->num_rows . " штук\n"; } catch (Exception $e) { echo "3. PRODUCTS ОШИБКА: " . $e->getMessage() . "\n"; } echo "=================================\n"; ?> EOF === ФАЙЛ: doctor_products.php === query("SELECT COUNT(*) as cnt FROM grand_products")->fetch_assoc()['cnt']; echo "Всего товаров в базе: $total\n\n"; // 2. Смотрим последние 5 добавленных товаров (самые свежие) echo "--- ПОСЛЕДНИЕ 5 ТОВАРОВ ---\n"; $sql = "SELECT id, name, owner_id, store_id, created_at FROM grand_products ORDER BY id DESC LIMIT 5"; $result = $conn->query($sql); if ($result->num_rows > 0) { while($row = $result->fetch_assoc()) { echo "ID: " . $row['id'] . "\n"; echo " Name: " . $row['name'] . "\n"; echo " Owner ID: [" . $row['owner_id'] . "]\n"; // [ВАЖНО] Проверяем store_id на пустоту $store_val = $row['store_id']; if ($store_val === NULL) $store_txt = "NULL (Пусто в БД)"; elseif ($store_val === "") $store_txt = "Пустая строка"; else $store_txt = $store_val; echo " Store ID: [" . $store_txt . "] <--- СМОТРЕТЬ СЮДА\n"; echo " Дата: " . $row['created_at'] . "\n"; echo "---------------------------\n"; } } else { echo "Таблица пуста!\n"; } echo "\n=== КОНЕЦ АНАЛИЗА ===\n"; ?> === ФАЙЛ: doctor_shares.php === query("SHOW COLUMNS FROM grand_products LIKE 'shares_count'"); if ($check->num_rows > 0) { echo "✅ Колонка 'shares_count' УЖЕ ЕСТЬ. База в порядке.\n"; } else { echo "⚠️ Колонки нет. Добавляем...\n"; // 2. Добавляем колонку (Безопасная операция, данные не сотрутся) $sql = "ALTER TABLE grand_products ADD COLUMN shares_count INT DEFAULT 0"; if ($conn->query($sql) === TRUE) { echo "✅ УСПЕХ! Колонка добавлена. Теперь товары появятся.\n"; } else { echo "❌ ОШИБКА БАЗЫ: " . $conn->error . "\n"; } } ?> === ФАЙЛ: fix_clicks.php === query($sql1)) { echo "✅ AD CLICKS FIXED: Updated " . $conn->affected_rows . " rows.\n"; } else { echo "❌ AD ERROR: " . $conn->error . "\n"; } // 2. Исправляем NULL на 0 в просмотрах рекламы $sql2 = "UPDATE ad_campaigns SET views_count = 0 WHERE views_count IS NULL"; if ($conn->query($sql2)) { echo "✅ AD VIEWS FIXED: Updated " . $conn->affected_rows . " rows.\n"; } echo "--- DONE ---\n"; ?> === ФАЙЛ: fix_comments.php === query($sql) === TRUE) { echo "SUCCESS! Updated posts: " . $conn->affected_rows . "\n"; } else { echo "ERROR: " . $conn->error . "\n"; } echo "--- DONE ---\n"; ?> === ФАЙЛ: fix_crash.php === query($sql1) === TRUE) { echo "✅ Колонка 'shares_count' УСПЕШНО ДОБАВЛЕНА!\n"; } else { echo "ℹ️ Информация: " . $conn->error . "\n"; } // 2. Добавляем favorites_count (на всякий случай, если таблицы нет) // (Обычно она есть, но проверим) $sqlCheck = "SHOW TABLES LIKE 'favorites'"; if ($conn->query($sqlCheck)->num_rows == 0) { echo "⚠️ Таблицы 'favorites' нет! Создаю...\n"; $sql2 = "CREATE TABLE favorites ( id INT AUTO_INCREMENT PRIMARY KEY, user_id VARCHAR(50), item_id VARCHAR(50), type VARCHAR(20), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )"; $conn->query($sql2); echo "✅ Таблица 'favorites' создана.\n"; } else { echo "✅ Таблица 'favorites' уже на месте.\n"; } echo "\n=== ЛЕЧЕНИЕ ЗАВЕРШЕНО. ТОВАРЫ ДОЛЖНЫ ПОЯВИТЬСЯ ===\n"; ?> === ФАЙЛ: fix_db.php === query($sql) === TRUE) { echo "УСПЕХ! Таблица 'reports' успешно создана! Можете обновить админ-панель."; } else { echo "ОШИБКА: " . $conn->error; } $conn->close(); ?> === ФАЙЛ: follow_user.php === "error", "message" => "Missing required fields"]); exit(); } // 3. Логика Подписки / Отписки if ($action === 'follow') { // Используем INSERT IGNORE, чтобы не было ошибок, если уже подписан // Важно: в базе колонка называется following_id, мы пишем туда нашу переменную $followed_id $sql = "INSERT IGNORE INTO follows (follower_id, following_id) VALUES (?, ?)"; $stmt = $conn->prepare($sql); $stmt->bind_param("ss", $follower_id, $followed_id); if ($stmt->execute()) { // ================================================================================= // [НОВОЕ] ХИРУРГИЧЕСКАЯ ВСТАВКА: СОЗДАНИЕ УВЕДОМЛЕНИЯ // ================================================================================= if ($follower_id != $followed_id) { // Проверка, чтобы не уведомлять самого себя // 1. Получаем имя и фото подписчика (того, кто нажал кнопку) // Используем username, как и везде $u_res = $conn->query("SELECT username, avatar_url FROM users WHERE id = '$follower_id'"); if ($u_res && $u_row = $u_res->fetch_assoc()) { $sender_name = !empty($u_row['username']) ? $u_row['username'] : "Пользователь"; $sender_ava = $u_row['avatar_url']; // Исправляем путь к аватару, если он относительный if (!empty($sender_ava) && strpos($sender_ava, 'http') === false) { $sender_ava = "http://216.250.14.77/api/v1/uploads/" . $sender_ava; } // 2. Добавляем запись в notifications // type='follow', action_id=ID подписчика (чтобы при клике открылся ЕГО профиль) // message: "подписался(-ась) на ваши обновления" $notif_sql = "INSERT INTO notifications (user_id, sender_id, type, action_id, title, message, user_avatar, created_at, is_read) VALUES (?, ?, 'follow', ?, ?, 'подписался(-ась) на ваши обновления', ?, NOW(), 0)"; $n_stmt = $conn->prepare($notif_sql); if ($n_stmt) { // sssss = 5 строк (user_id, sender_id, action_id, title, user_avatar) $n_stmt->bind_param("sssss", $followed_id, $follower_id, $follower_id, $sender_name, $sender_ava); $n_stmt->execute(); $n_stmt->close(); } } } // ================================================================================= echo json_encode(["status" => "success", "message" => "Followed successfully"]); } else { echo json_encode(["status" => "error", "message" => "Database error"]); } } elseif ($action === 'unfollow') { // Удаляем запись из таблицы $sql = "DELETE FROM follows WHERE follower_id = ? AND following_id = ?"; $stmt = $conn->prepare($sql); $stmt->bind_param("ss", $follower_id, $followed_id); if ($stmt->execute()) { // ================================================================================= // [НОВОЕ] ХИРУРГИЧЕСКАЯ ВСТАВКА: УДАЛЕНИЕ УВЕДОМЛЕНИЯ // Если человек отписался, убираем уведомление о подписке, чтобы не мусорить // ================================================================================= $conn->query("DELETE FROM notifications WHERE user_id='$followed_id' AND sender_id='$follower_id' AND type='follow'"); // ================================================================================= echo json_encode(["status" => "success", "message" => "Unfollowed successfully"]); } else { echo json_encode(["status" => "error", "message" => "Database error"]); } } else { echo json_encode(["status" => "error", "message" => "Invalid action"]); } $conn->close(); ?> === ФАЙЛ: generate_post.php === "error", "message" => "Введите пару слов для генерации"]); exit(); } // [НОВОЕ] Узнаем, что именно хочет пользователь: сгенерировать новый или изменить старый $action = $_POST['action'] ?? 'generate'; // ============================================================================== // [ИЗМЕНЕНО] Бронебойный контроль языка: правило ставится в самый конец промпта! // ============================================================================== $lang_code = $_POST['lang'] ?? 'ru'; $target_language = "на РУССКОМ языке"; if ($lang_code === 'tk') { $target_language = "строго на ТУРКМЕНСКОМ языке (Turkmen language)"; } elseif ($lang_code === 'en') { $target_language = "строго на АНГЛИЙСКОМ языке (English language)"; } // Жесткое правило, которое ИИ прочитает последним: $strict_lang_rule = "\n\n[КРИТИЧЕСКИ ВАЖНО]: Твой финальный ответ должен быть СТРОГО $target_language! Запрещено использовать другие языки. Если исходный текст на другом языке - переведи его $target_language. Не пиши приветствий, выдавай сразу готовый текст."; if ($action === 'rewrite') { $system_prompt = "Ты блогер. Перепиши этот текст по-новому: [ $user_input ]. Сделай свежо, добавь эмодзи." . $strict_lang_rule; } elseif ($action === 'funny') { $system_prompt = "Ты комик. Сделай этот текст смешнее: [ $user_input ]." . $strict_lang_rule; } elseif ($action === 'short') { $system_prompt = "Сделай текст короче: [ $user_input ]. Оставь суть и хештеги." . $strict_lang_rule; } else { $system_prompt = "Ты блогер. Напиши пост на основе этих слов: [ $user_input ]. Добавь интригу, 3-4 эмодзи и вопрос в конце. Подбери хештеги." . $strict_lang_rule; } // ============================================================================== // Упаковываем данные для Google Gemini API $data = [ "contents" => [ [ "parts" => [ ["text" => $system_prompt] ] ] ] ]; $json_data = json_encode($data); // ============================================================================== // [ИЗМЕНЕНО] Возвращаем вашу родную модель gemini-2.5-flash + Бронебойный Retry // ============================================================================== $url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=" . trim($api_key); $max_retries = 3; // [НОВОЕ] Количество попыток, если ИИ заглючил $attempt = 0; $success = false; $response = ""; $http_code = 0; while ($attempt < $max_retries && !$success) { $attempt++; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json' ]); // Увеличили таймаут до 45 секунд, чтобы ИИ успел подумать curl_setopt($ch, CURLOPT_TIMEOUT, 45); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // Проверяем, действительно ли ИИ отдал нормальный текст if ($http_code == 200 && $response) { $result_check = json_decode($response, true); if (isset($result_check['candidates'][0]['content']['parts'][0]['text'])) { $success = true; // Успех! Текст получен, прерываем цикл. break; } } // Если ИИ выдал ошибку (глюк, перегрузка), ждем 1 секунду и пробуем снова if (!$success && $attempt < $max_retries) { sleep(1); } } // ============================================================================== // Обрабатываем ответ от Google if ($http_code == 200 && $response) { $result = json_decode($response, true); // Вытаскиваем сам сгенерированный текст if (isset($result['candidates'][0]['content']['parts'][0]['text'])) { $ai_text = $result['candidates'][0]['content']['parts'][0]['text']; echo json_encode([ "status" => "success", "text" => trim($ai_text) ], JSON_UNESCAPED_UNICODE); } else { echo json_encode(["status" => "error", "message" => "ИИ вернул пустой ответ.", "debug" => $response]); } } else { // Ошибка от Google echo json_encode([ "status" => "error", "message" => "Ошибка соединения с ИИ. HTTP: $http_code", "debug" => $response ]); } ?> === ФАЙЛ: generate_product_desc.php === "error", "message" => "Пожалуйста, введите пару слов или характеристик товара"]); exit(); } // ============================================================================== // Умная локализация. ИИ будет отвечать на языке пользователя. // ============================================================================== if ($lang === 'tk') { $language_instruction = "\nВАЖНО: Итоговый текст должен быть СТРОГО на грамотном туркменском языке (Turkmen language, Latin alphabet)."; } else { $language_instruction = "\nВАЖНО: Итоговый текст должен быть на грамотном русском языке."; } // ============================================================================== // Динамические промпты для ТОВАРОВ (используем маркетинговую формулу AIDA) // ============================================================================== if ($action === 'rewrite') { $system_prompt = "Ты профессиональный копирайтер для маркетплейса. Перепиши это описание товара, сделай его более привлекательным, 'вкусным' и продающим: [ $user_input ]. Убери скучные формулировки, добавь абзацы и уместные эмодзи (✅, 💎, 🔥). Сделай так, чтобы товар хотелось купить немедленно. Выдавай сразу готовый текст." . $language_instruction; } elseif ($action === 'short') { $system_prompt = "Ты опытный маркетолог. Сократи это описание товара до 3-4 самых мощных и бьющих в цель предложений: [ $user_input ]. Оставь только главную суть, бренд/модель и самое важное преимущество. Используй 1-2 эмодзи. Текст должен быть коротким, чтобы клиент мог прочитать его за 5 секунд. Выдавай сразу готовый текст." . $language_instruction; } else { // Стандартная генерация с нуля (action = generate) $system_prompt = "Ты элитный копирайтер и продавец. Создай красивое продающее описание товара на основе этих данных: [ $user_input ].\n" . "Соблюдай эту структуру:\n" . "1. Краткое и цепляющее вступление.\n" . "2. Главные преимущества (используй маркеры-эмодзи ✅, 🌟, 💎).\n" . "3. Состояние или особенности (если это понятно из текста).\n" . "4. Призыв к действию (Call to Action) в конце: 'Пишите в сообщения' или 'Звоните прямо сейчас!'.\n" . "Не пиши 'воды'. Не пиши приветствия вроде 'Привет, я ИИ'. Выдавай сразу готовый текст для публикации." . $language_instruction; } // Упаковываем данные для Google Gemini API $data = [ "contents" => [ [ "parts" => [ ["text" => $system_prompt] ] ] ] ]; $json_data = json_encode($data); // ============================================================================== // [ИЗМЕНЕНО] Возвращаем вашу родную модель gemini-2.5-flash + Прямой доступ // ============================================================================== $url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=" . trim($api_key); $max_retries = 3; $attempt = 0; $success = false; $response = ""; $http_code = 0; $curl_system_error = ""; while ($attempt < $max_retries && !$success) { $attempt++; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json' ]); // Безопасные настройки + Таймаут curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_TIMEOUT, 45); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curl_system_error = curl_error($ch); curl_close($ch); if ($http_code == 200 && $response) { $result_check = json_decode($response, true); if (isset($result_check['candidates'][0]['content']['parts'][0]['text'])) { $success = true; break; } } if (!$success && $attempt < $max_retries) { sleep(1); } } // ============================================================================== // Обрабатываем ответ от Google if ($http_code == 200 && $response && $success) { $result = json_decode($response, true); $ai_text = $result['candidates'][0]['content']['parts'][0]['text']; echo json_encode([ "status" => "success", "text" => trim($ai_text) ], JSON_UNESCAPED_UNICODE); } else { // В случае ошибки отдаем полный лог echo json_encode([ "status" => "error", "message" => "HTTP: $http_code | cURL: $curl_system_error", "debug" => $response ]); } ?> === ФАЙЛ: get_analytics.php === connect_error) { throw new Exception("Ошибка БД: " . $conn->connect_error); } $conn->set_charset("utf8mb4"); // 2. ПОЛУЧЕНИЕ ПАРАМЕТРОВ $user_id = isset($_GET['user_id']) ? $conn->real_escape_string($_GET['user_id']) : ''; $category = isset($_GET['category']) ? $_GET['category'] : ''; if (empty($user_id)) { echo json_encode([]); // Просто пустой список, если нет ID exit; } $response_items = []; // 3. ЛОГИКА switch ($category) { case 'stories': // [ИСПРАВЛЕНИЕ] Ищем истории в таблице POSTS // Мы точно знаем, что они там есть (по вашим тестам) $sql = "SELECT `id`, `image_url`, `video_url`, `views`, `created_at` FROM `posts` WHERE `author_id` = '$user_id' AND (`type` = 'STORY' OR `type` = 'story') ORDER BY `created_at` DESC"; $result = $conn->query($sql); if (!$result) throw new Exception("Ошибка SQL Stories: " . $conn->error); while ($row = $result->fetch_assoc()) { // created_at - это число (BIGINT) $ts = isset($row['created_at']) ? (float)$row['created_at'] : 0; // Защита от деления на ноль if ($ts > 1000000000000) { // Если в миллисекундах $dateTitle = date("d M H:i", $ts / 1000); } else { $dateTitle = date("d M H:i", $ts); } $media = !empty($row['image_url']) ? $row['image_url'] : $row['video_url']; $response_items[] = [ "id" => $row['id'], "title" => $dateTitle, "imageUrl" => $media, "impressions" => (int)$row['views'], "views" => (int)$row['views'], "clicks" => 0, "type" => "STORY" ]; } break; case 'posts': $sql = "SELECT `id`, `title`, `image_url`, `views`, `likes_count`, `comments_count` FROM `posts` WHERE `author_id` = '$user_id' AND `type` = 'image' ORDER BY `created_at` DESC"; $result = $conn->query($sql); if (!$result) throw new Exception("Ошибка SQL Posts: " . $conn->error); while ($row = $result->fetch_assoc()) { $clicks = (int)$row['likes_count'] + (int)$row['comments_count']; $response_items[] = [ "id" => $row['id'], "title" => $row['title'] ?: "Пост", "imageUrl" => $row['image_url'], "impressions" => (int)$row['views'], "views" => (int)$row['views'], "clicks" => $clicks, "type" => "POST" ]; } break; case 'reels': $sql = "SELECT `id`, `title`, `image_url`, `video_url`, `views`, `likes_count`, `comments_count` FROM `posts` WHERE `author_id` = '$user_id' AND (`type` = 'video' OR `type` = 'reel') ORDER BY `created_at` DESC"; $result = $conn->query($sql); if (!$result) throw new Exception("Ошибка SQL Reels: " . $conn->error); while ($row = $result->fetch_assoc()) { $clicks = (int)$row['likes_count'] + (int)$row['comments_count']; $media = !empty($row['image_url']) ? $row['image_url'] : $row['video_url']; $response_items[] = [ "id" => $row['id'], "title" => $row['title'] ?: "Reel", "imageUrl" => $media, "impressions" => (int)$row['views'], "views" => (int)$row['views'], "clicks" => $clicks, "type" => "REEL" ]; } break; case 'ads': $sql = "SELECT `id`, `title`, `media_url`, `views_count`, `clicks_count`, `status` FROM `ad_campaigns` WHERE `owner_id` = '$user_id' ORDER BY `created_at` DESC"; $result = $conn->query($sql); if (!$result) throw new Exception("Ошибка SQL Ads: " . $conn->error); while ($row = $result->fetch_assoc()) { $statusIcon = ($row['status'] == 'ACTIVE') ? "🟢 " : "🔴 "; $response_items[] = [ "id" => (string)$row['id'], "title" => $statusIcon . ($row['title'] ?? "Реклама"), "imageUrl" => $row['media_url'], "impressions" => (int)$row['views_count'], "views" => (int)$row['views_count'], "clicks" => (int)$row['clicks_count'], "type" => "STORE" ]; } break; case 'store': $sql = "SELECT `id`, `name`, `image_urls`, `views`, `favorites_count` FROM `products` WHERE `owner_id` = '$user_id' ORDER BY `created_at` DESC"; $result = $conn->query($sql); if (!$result) throw new Exception("Ошибка SQL Store: " . $conn->error); while ($row = $result->fetch_assoc()) { $images = json_decode($row['image_urls'], true); $main_image = (!empty($images) && is_array($images)) ? $images[0] : ""; $response_items[] = [ "id" => $row['id'], "title" => $row['name'], "imageUrl" => $main_image, "impressions" => (int)$row['views'], "views" => (int)$row['views'], "clicks" => (int)$row['favorites_count'], "type" => "STORE" ]; } break; } echo json_encode($response_items); } catch (Exception $e) { // ВМЕСТО ОШИБКИ 500 МЫ ОТДАЕМ JSON С ТЕКСТОМ ОШИБКИ http_response_code(200); // Отдаем как "Успех", чтобы Андроид прочитал тело ответа echo json_encode([ ["id" => "error", "title" => "ОШИБКА СЕРВЕРА", "imageUrl" => "", "impressions" => 0, "views" => 0, "clicks" => 0, "type" => "POST"], ["id" => "error_msg", "title" => $e->getMessage(), "imageUrl" => "", "impressions" => 0, "views" => 0, "clicks" => 0, "type" => "POST"] ]); } ?> === ФАЙЛ: get_blocked_users.php === "error", "message" => "User ID required"]); exit; } // Получаем данные заблокированных пользователей (JOIN с таблицей users) $sql = "SELECT u.id, u.name, u.username, u.avatar_url FROM blocked_users b JOIN users u ON b.blocked_id = u.id WHERE b.user_id = ?"; $stmt = $conn->prepare($sql); $stmt->bind_param("s", $user_id); $stmt->execute(); $result = $stmt->get_result(); $blocked_users = []; while ($row = $result->fetch_assoc()) { // Исправляем ссылку на аватар if (!empty($row['avatar_url']) && strpos($row['avatar_url'], 'http') === false) { $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https://" : "http://"; $host = $_SERVER['HTTP_HOST']; $row['avatar_url'] = $protocol . $host . "/api/v1/" . $row['avatar_url']; } $blocked_users[] = $row; } echo json_encode(["status" => "success", "data" => $blocked_users]); $stmt->close(); $conn->close(); ?> === ФАЙЛ: get_comments.php === real_escape_string($post_id); $safe_viewer_id = $conn->real_escape_string($viewer_id); // 2. Умный запрос (ТОЧНО РАБОЧИЙ ВАРИАНТ) // Считаем лайки через подзапрос, так как колонки в базе нет $sql = "SELECT c.id, c.post_id, c.user_id, c.text, c.created_at, -- Считаем общее количество лайков (SELECT COUNT(*) FROM comment_likes WHERE comment_id = c.id) as likes_count, u.username, u.name, u.avatar_url, -- Проверяем лайк зрителя (SELECT COUNT(*) FROM comment_likes cl WHERE cl.comment_id = c.id AND cl.user_id = '$safe_viewer_id') as is_liked_by_me FROM comments c LEFT JOIN users u ON c.user_id = u.id WHERE c.post_id = '$safe_post_id' ORDER BY c.created_at ASC"; $result = $conn->query($sql); $comments = []; if ($result) { while ($row = $result->fetch_assoc()) { // --- [ЖУЧОК ЧАСТЬ 2: Проверяем, видит ли сервер лайк] --- if ($row['is_liked_by_me'] > 0) { $debugMsg = " -> НАЙДЕН ЛАЙК! Коммент ID: {$row['id']} лайкнут пользователем\n"; file_put_contents('debug_get.txt', $debugMsg, FILE_APPEND); } // -------------------------------------------------------- // Имена $displayName = !empty($row['name']) ? $row['name'] : (!empty($row['username']) ? $row['username'] : 'Пользователь'); // Время $timeStr = $row['created_at']; $timestamp = 0; if (!empty($timeStr)) { $timestamp = strtotime($timeStr) * 1000; } else { $timestamp = time() * 1000; } $item = [ "id" => (string)$row['id'], "post_id" => (string)$row['post_id'], "user_id" => (string)$row['user_id'], "username" => (string)$displayName, "author_name" => (string)$displayName, "avatar_url" => !empty($row['avatar_url']) ? $row['avatar_url'] : null, "text" => (string)$row['text'], "timestamp" => $timestamp, // Данные о лайках "likes" => (int)$row['likes_count'], "is_liked" => ($row['is_liked_by_me'] > 0) ]; $comments[] = $item; } } echo json_encode($comments); $conn->close(); ?> === ФАЙЛ: get_dashboard_stats.php === "error", "message" => "DB Connection Failed"]); exit(); } $user_id = $_GET['user_id'] ?? ''; if (empty($user_id)) { echo json_encode(["status" => "error", "message" => "No User ID"]); exit(); } // 1. Считаем РЕАЛЬНЫЙ РАСХОД (по логам) $total_spent = 0.00; // [ИЗМЕНЕНО] Добавлена проверка на ошибку prepare() $stmt = $conn->prepare("SELECT COUNT(*) as total_clicks FROM ad_billing_logs WHERE user_id = ?"); if ($stmt) { $stmt->bind_param("s", $user_id); $stmt->execute(); $resLogs = $stmt->get_result(); if ($row = $resLogs->fetch_assoc()) { $clicks = $row['total_clicks']; $total_spent = $clicks * 0.01; } $stmt->close(); } else { // Если таблицы логов нет или ошибка SQL $total_spent = 0.00; } // ============================================================================== // [ИЗМЕНЕНО] 2. ОБЪЕДИНЕННЫЙ КОШЕЛЕК (Основной баланс + Бонусы + Рекламный) // ============================================================================== $balance = 0.00; // А. Читаем ОСНОВНОЙ баланс (Бонусы, пополнения, остаток после ИИ-Студии) из таблицы users $stmt_users = $conn->prepare("SELECT balance FROM users WHERE id = ?"); if ($stmt_users) { $stmt_users->bind_param("s", $user_id); $stmt_users->execute(); $resUsers = $stmt_users->get_result(); if ($row = $resUsers->fetch_assoc()) { $balance += (float)$row['balance']; } $stmt_users->close(); } // Б. Читаем РЕКЛАМНЫЙ кошелек (из ad_wallets, на случай старых пополнений) $stmt_wallets = $conn->prepare("SELECT balance FROM ad_wallets WHERE owner_id = ?"); if ($stmt_wallets) { $stmt_wallets->bind_param("s", $user_id); $stmt_wallets->execute(); $resWallets = $stmt_wallets->get_result(); if ($row = $resWallets->fetch_assoc()) { $balance += (float)$row['balance']; } $stmt_wallets->close(); } // 3. Считаем СУММУ ЛИМИТОВ $active_limits = 0.00; $stmt3 = $conn->prepare("SELECT SUM(budget_limit) as total_limits FROM ad_campaigns WHERE owner_id = ? AND status != 'DELETED'"); if ($stmt3) { $stmt3->bind_param("s", $user_id); $stmt3->execute(); $resLimits = $stmt3->get_result(); if ($row = $resLimits->fetch_assoc()) { $active_limits = (float)$row['total_limits']; } $stmt3->close(); } // ============================================================================== // [ИЗМЕНЕНО] 4. Считаем РЕАЛЬНЫЙ РАСХОД НА МАГАЗИНЫ (store_spent) // Парсим файл логов биллинга для 100% точности списаний. // ============================================================================== $store_spent = 0.00; $log_file = 'debug_billing.txt'; // Тот самый файл, куда пишет renew_store_subscription.php if (file_exists($log_file)) { // Читаем файл построчно в массив $lines = file($log_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { // Ищем строки, которые начинаются с "SUCCESS:" if (strpos($line, 'SUCCESS:') === 0) { // Убеждаемся, что транзакция принадлежит именно этому пользователю if (strpos($line, "User $user_id paid") !== false) { // Извлекаем сумму платежа с помощью регулярного выражения // Ищем конструкцию "paid 500" или "paid 100.0" if (preg_match('/paid\s+([0-9\.]+)/', $line, $matches)) { $amount = (float)$matches[1]; $store_spent += $amount; } } } } } // ============================================================================== // [НОВОЕ] 4.5 Считаем расход на ИИ-Студию и плюсуем к общим расходам магазина // ============================================================================== $ai_log_file = 'debug_ai_billing.txt'; if (file_exists($ai_log_file)) { $ai_lines = file($ai_log_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($ai_lines as $line) { // Ищем строку старта для конкретного юзера if (strpos($line, "СТАРТ: Юзер $user_id") !== false) { // Извлекаем сумму (Пытаемся списать: 2 TMT) if (preg_match('/списать:\s*([0-9\.]+)\s*TMT/', $line, $matches)) { $store_spent += (float)$matches[1]; } else { $store_spent += 2.0; // Страховка, если регулярка не сработала } } } } // [ВАЖНО] Вывод JSON без лишних пробелов echo json_encode([ "status" => "success", "data" => [ "total_spent" => $total_spent, "current_balance" => $balance, "active_limits" => $active_limits, "store_spent" => round($store_spent, 2) ] ]); ?> === ФАЙЛ: get_favorites.php === prepare($sql); // ============================================================================== // [НОВОЕ] АБСОЛЮТНАЯ ЗАЩИТА ГЛАВНОГО ЗАПРОСА (Устраняем 500 ошибку) // ============================================================================== if (!$stmt) { echo json_encode([]); exit(); } $stmt->bind_param("s", $user_id); $stmt->execute(); $result = $stmt->get_result(); // [НОВОЕ] Вторая линия защиты if (!$result) { echo json_encode([]); exit(); } $favorites = []; while ($row = $result->fetch_assoc()) { $itemId = $row['item_id']; $type = $row['type']; // Изначально то, что записано в базе (например "PRODUCT") $title = "Loading..."; $imageUrl = null; $price = null; // ЛОГИКА РАЗДЕЛЕНИЯ // [НОВОЕ] Обернули в try-catch для абсолютной защиты от 500 ошибки try { // [ИЗМЕНЕНО] Сделали нечувствительным к регистру на случай "product" вместо "PRODUCT" if (strtoupper($type) === 'PRODUCT') { // ============================================================================== // [ИЗМЕНЕНО] 1. Ищем в актуальной таблице GRAND_PRODUCTS! // ============================================================================== $storeSql = "SELECT * FROM grand_products WHERE id = ?"; $storeStmt = $conn->prepare($storeSql); // [НОВОЕ] Защита от Fatal Error: проверяем, успешно ли подготовился запрос if ($storeStmt) { $storeStmt->bind_param("s", $itemId); $storeStmt->execute(); $storeRes = $storeStmt->get_result(); // ============================================================================== // [ИЗМЕНЕНО] Двойная защита: проверяем $storeRes на false перед fetch_assoc() // ============================================================================== if ($storeRes && $storeRow = $storeRes->fetch_assoc()) { // НАШЛИ В МАГАЗИНЕ! $title = isset($storeRow['name']) ? $storeRow['name'] : "Store Product"; $price = isset($storeRow['price']) ? (float)$storeRow['price'] : null; // ============================================================================== // [ИЗМЕНЕНО] Правильная колонка для фото: images_json // ============================================================================== $photoData = !empty($storeRow['images_json']) ? $storeRow['images_json'] : (!empty($storeRow['image_urls']) ? $storeRow['image_urls'] : null); if ($photoData) { $images = json_decode($photoData, true); if (is_array($images) && count($images) > 0) { $imageUrl = $images[0]; } } // [ВАЖНО] Подменяем тип, чтобы Android знал, что это магазин $type = "STORE_PRODUCT"; } else { // 2. Если не нашли, ищем в ЧАСТНЫХ ОБЪЯВЛЕНИЯХ (products) $prodSql = "SELECT * FROM products WHERE id = ?"; $prodStmt = $conn->prepare($prodSql); // [НОВОЕ] Защита от Fatal Error if ($prodStmt) { $prodStmt->bind_param("s", $itemId); $prodStmt->execute(); $prodRes = $prodStmt->get_result(); // ============================================================================== // [ИЗМЕНЕНО] Двойная защита от падения get_result() // ============================================================================== if ($prodRes && $prodRow = $prodRes->fetch_assoc()) { // Нашли в частных $title = isset($prodRow['name']) ? $prodRow['name'] : "Product"; $price = isset($prodRow['price']) ? (float)$prodRow['price'] : null; if (!empty($prodRow['image_urls'])) { $images = json_decode($prodRow['image_urls'], true); if (is_array($images) && count($images) > 0) { $imageUrl = $images[0]; } } } $prodStmt->close(); } } $storeStmt->close(); } } else { // Логика для постов, видео и сторис // [ИЗМЕНЕНО] SELECT * спасает от 500 ошибки, если колонки 'title' нет в таблице posts $postSql = "SELECT * FROM posts WHERE id = ?"; $postStmt = $conn->prepare($postSql); // [НОВОЕ] Защита от Fatal Error if ($postStmt) { $postStmt->bind_param("s", $itemId); $postStmt->execute(); $postRes = $postStmt->get_result(); // ============================================================================== // [ИЗМЕНЕНО] Двойная защита от падения get_result() // ============================================================================== if ($postRes && $postRow = $postRes->fetch_assoc()) { // Умный поиск заголовка (если title пуст, берем кусочек content) $title = !empty($postRow['title']) ? $postRow['title'] : (!empty($postRow['content']) ? mb_substr($postRow['content'], 0, 30) . "..." : "Post"); $imageUrl = !empty($postRow['image_url']) ? $postRow['image_url'] : (!empty($postRow['video_url']) ? $postRow['video_url'] : null); } $postStmt->close(); } } } catch (Throwable $e) { // [НОВОЕ] Логируем ошибку, но отдаем Android пустой/дефолтный объект, а не 500 ошибку error_log("Favorites Error for item $itemId: " . $e->getMessage()); $title = "Ошибка загрузки"; } // Собираем ответ $favorites[] = [ "item_id" => $itemId, "item_type" => $type, // Здесь будет или STORE_PRODUCT или PRODUCT "title" => $title, "image_url" => $imageUrl, "price" => $price, "timestamp" => strtotime($row['created_at']) * 1000 ]; } echo json_encode($favorites); ?> === ФАЙЛ: get_followers.php === real_escape_string($user_id); $safe_viewer_id = $conn->real_escape_string($viewer_id); // SQL для Подписчиков: // Ищем тех, кто подписан (follower_id) на нужного пользователя (following_id) $sql = " SELECT u.id, u.name, u.bio, u.avatar_url, (SELECT COUNT(*) FROM follows WHERE follower_id = '$safe_viewer_id' AND following_id = u.id) as is_following FROM follows f JOIN users u ON f.follower_id = u.id WHERE f.following_id = '$safe_user_id' LIMIT $limit OFFSET $offset "; $result = $conn->query($sql); if (!$result) { throw new Exception("SQL Error: " . $conn->error); } $profiles = []; while ($row = $result->fetch_assoc()) { $profiles[] = [ "id" => $row['id'], "name" => $row['name'], "bio" => $row['bio'] ?? "", "avatarUrl" => $row['avatar_url'], "avatar_url" => $row['avatar_url'], "isFollowing" => $row['is_following'] > 0, "is_following" => $row['is_following'] > 0, "followersCount" => 0, "followingCount" => 0, "postsCount" => 0, "isOnline" => false ]; } echo json_encode($profiles, JSON_UNESCAPED_UNICODE); } catch (Throwable $e) { http_response_code(500); echo "PHP_ERROR: " . $e->getMessage(); } ?> === ФАЙЛ: get_following.php === real_escape_string($user_id); $safe_viewer_id = $conn->real_escape_string($viewer_id); // SQL для Подписок: // Ищем тех, на кого подписан (following_id) нужный пользователь (follower_id) $sql = " SELECT u.id, u.name, u.bio, u.avatar_url, (SELECT COUNT(*) FROM follows WHERE follower_id = '$safe_viewer_id' AND following_id = u.id) as is_following FROM follows f JOIN users u ON f.following_id = u.id WHERE f.follower_id = '$safe_user_id' LIMIT $limit OFFSET $offset "; $result = $conn->query($sql); if (!$result) { throw new Exception("SQL Error: " . $conn->error); } $profiles = []; while ($row = $result->fetch_assoc()) { $profiles[] = [ "id" => $row['id'], "name" => $row['name'], "bio" => $row['bio'] ?? "", "avatarUrl" => $row['avatar_url'], "avatar_url" => $row['avatar_url'], "isFollowing" => $row['is_following'] > 0, "is_following" => $row['is_following'] > 0, "followersCount" => 0, "followingCount" => 0, "postsCount" => 0, "isOnline" => false ]; } echo json_encode($profiles, JSON_UNESCAPED_UNICODE); } catch (Throwable $e) { http_response_code(500); echo "PHP_ERROR: " . $e->getMessage(); } ?> === ФАЙЛ: get_following_ids.php === "error", "message" => "user_id is missing"]); exit(); } $user_id = $_GET['user_id']; // 5. ЗАПРОС (Строго под вашу структуру таблицы follows) // Мы ищем following_id (на кого подписан), где follower_id (это я) $sql = "SELECT following_id FROM follows WHERE follower_id = ?"; if ($stmt = $conn->prepare($sql)) { $stmt->bind_param("s", $user_id); if ($stmt->execute()) { $result = $stmt->get_result(); $following_ids = array(); // Собираем список ID while ($row = $result->fetch_assoc()) { $following_ids[] = $row['following_id']; } // 6. Успешный ответ echo json_encode([ "status" => "success", "data" => $following_ids ]); } else { echo json_encode(["status" => "error", "message" => "Execution failed"]); } $stmt->close(); } else { echo json_encode(["status" => "error", "message" => "Prepare failed"]); } $conn->close(); ?> === ФАЙЛ: get_main_feed.php === set_charset("utf8mb4"); // 1. ПАРАМЕТРЫ $page = isset($_GET['page']) ? (int)$_GET['page'] : 1; $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 20; $offset = ($page - 1) * $limit; $mode = isset($_GET['mode']) ? trim($_GET['mode']) : 'products'; if (!isset($conn) && isset($mysqli)) $conn = $mysqli; $finalFeed = []; $contentItems = []; // ============================================================================== // [ИЗМЕНЕНО] Бронебойный Shield-Фильтр (Защита от Fatal Error и SQL-крашей) // ============================================================================== $currentUserId = isset($_GET['user_id']) ? trim($_GET['user_id']) : ''; $safeUid = $conn->real_escape_string($currentUserId); $shieldFilter = ""; $adShieldFilter = ""; if (!empty($safeUid)) { // 1. БЕЗОПАСНО достаем скрытые посты (Ловим ошибки, если таблицы еще нет) $hidden_ids = []; try { $h_res = $conn->query("SELECT post_id FROM hidden_posts WHERE user_id = '$safeUid' AND post_id IS NOT NULL"); if ($h_res) { while ($row = $h_res->fetch_assoc()) { $hidden_ids[] = "'" . $conn->real_escape_string($row['post_id']) . "'"; } } } catch (Throwable $e) { // Таблицы нет - просто игнорируем, постов в черном списке 0 } $hidden_sql = count($hidden_ids) > 0 ? implode(",", $hidden_ids) : "'IMPOSSIBLE_POST_ID_999'"; // 2. БЕЗОПАСНО достаем заблокированных (Ловим ошибки) $blocked_ids = []; try { $b_res = $conn->query("SELECT blocked_id as uid FROM blocked_users WHERE user_id = '$safeUid' AND blocked_id IS NOT NULL UNION SELECT user_id as uid FROM blocked_users WHERE blocked_id = '$safeUid' AND user_id IS NOT NULL"); if ($b_res) { while ($row = $b_res->fetch_assoc()) { $blocked_ids[] = "'" . $conn->real_escape_string($row['uid']) . "'"; } } } catch (Throwable $e) { // Игнорируем } $blocked_sql = count($blocked_ids) > 0 ? implode(",", $blocked_ids) : "'IMPOSSIBLE_USER_ID_999'"; $shieldFilter = " AND p.id NOT IN ($hidden_sql) AND p.author_id NOT IN ($blocked_sql) "; $adShieldFilter = "AND id NOT IN ($hidden_sql)"; } // ============================================================================== // ================================================================================= // ШАГ 1: ЗАГРУЖАЕМ КОНТЕНТ // ================================================================================= // [НОВОЕ] 0. REELS (Мои + Друзья + Глобальные) if ($mode == 'reels') { $currentUserId = isset($_GET['user_id']) ? trim($_GET['user_id']) : ''; // [ИСПРАВЛЕНО] Добавлены поля p.music_track_name и p.audio_url // А) Основной запрос: Мои видео + Видео подписок (follows) $sqlReels = "SELECT p.id, p.author_id, p.content, p.video_url, p.created_at, p.likes_count, p.comments_count, p.music_track_name, p.audio_url, p.allow_comments, u.name as authorName, u.avatar_url as authorAvatar, 'post' as itemType, 'VIDEO' as type, (SELECT COUNT(*) FROM likes WHERE user_id = '$currentUserId' AND post_id = p.id) as is_liked FROM posts p JOIN users u ON p.author_id = u.id WHERE (p.type = 'VIDEO' OR p.type = 'REEL' OR p.video_url != '') AND p.type != 'STORY' AND ( p.author_id = '$currentUserId' OR p.author_id IN (SELECT following_id FROM follows WHERE follower_id = '$currentUserId') ) $shieldFilter ORDER BY p.created_at DESC LIMIT $limit OFFSET $offset"; $result = $conn->query($sqlReels); // [НОВОЕ] ЖУЧОК 5: Ловим ошибки основного запроса Reels if (!$result) { file_put_contents('debug_feed_sql.txt', "❌ ОШИБКА REELS (MAIN): " . $conn->error . "\nSQL: $sqlReels\n", FILE_APPEND); } // [ИСПРАВЛЕНО] Добавлены поля p.music_track_name и p.audio_url // Б) Запасной план: Случайные видео if (!$result || $result->num_rows < 10) { $sqlReels = "SELECT p.id, p.author_id, p.content, p.video_url, p.created_at, p.likes_count, p.comments_count, p.music_track_name, p.audio_url, p.allow_comments, u.name as authorName, u.avatar_url as authorAvatar, 'post' as itemType, 'VIDEO' as type, (SELECT COUNT(*) FROM likes WHERE user_id = '$currentUserId' AND post_id = p.id) as is_liked FROM posts p JOIN users u ON p.author_id = u.id WHERE (p.type = 'VIDEO' OR p.type = 'REEL' OR p.video_url != '') AND p.type != 'STORY' $shieldFilter ORDER BY RAND() LIMIT $limit OFFSET $offset"; $result = $conn->query($sqlReels); if (!$result) { file_put_contents('debug_feed_sql.txt', "❌ ОШИБКА REELS (RANDOM): " . $conn->error . "\nSQL: $sqlReels\n", FILE_APPEND); } } if ($result) { while ($row = $result->fetch_assoc()) { $row['mediaUrls'] = [$row['video_url']]; $row['isLiked'] = ((int)$row['is_liked'] > 0); $row['musicTrackName'] = !empty($row['music_track_name']) ? $row['music_track_name'] : null; $row['audioUrl'] = !empty($row['audio_url']) ? $row['audio_url'] : null; $ac = isset($row['allow_comments']) ? $row['allow_comments'] : 1; $row['allowComments'] = ($ac == 1); unset($row['allow_comments']); $contentItems[] = $row; } } } // 1. STORIES elseif ($mode == 'stories') { $currentUserId = isset($_GET['user_id']) ? trim($_GET['user_id']) : ''; // [ИЗМЕНЕНО] Теперь берем истории из универсальной таблицы posts (где type = 'STORY') // [НОВОЕ] Условие: показываем СВОИ истории + истории тех, на кого подписан $sqlStories = "SELECT p.id, p.author_id as authorId, p.video_url, p.image_url, p.media_urls, p.created_at, u.name as authorName, u.avatar_url as authorAvatar, 'story' as itemType FROM posts p JOIN users u ON p.author_id = u.id WHERE p.type = 'STORY' AND ( p.author_id = '$currentUserId' OR p.author_id IN (SELECT following_id FROM follows WHERE follower_id = '$currentUserId') ) $shieldFilter ORDER BY p.created_at DESC LIMIT 50"; $result = $conn->query($sqlStories); if ($result) { while ($row = $result->fetch_assoc()) { // [НОВОЕ] Умное извлечение медиафайла (видео или фото) $mediaUrl = ""; if (!empty($row['video_url'])) { $mediaUrl = $row['video_url']; } elseif (!empty($row['image_url'])) { $mediaUrl = $row['image_url']; } else { $imgs = json_decode($row['media_urls'], true); if (is_string($imgs)) $imgs = json_decode($imgs, true); // Защита от двойного кодирования if (is_array($imgs) && count($imgs) > 0) { $mediaUrl = $imgs[0]; } } $row['mediaUrl'] = $mediaUrl; // Android-клиент (StoryViewModel) ожидает поле mediaUrls как массив $row['mediaUrls'] = [$mediaUrl]; $row['isViewed'] = false; $row['duration'] = 5000; // Очищаем технические поля unset($row['video_url']); unset($row['image_url']); unset($row['media_urls']); $contentItems[] = $row; } } } // 2. РЕЖИМ ЛЕНТЫ (POSTS) elseif ($mode == 'posts') { $currentUserId = isset($_GET['user_id']) ? trim($_GET['user_id']) : ''; $sqlPosts = "SELECT p.id, p.author_id, p.content, p.title, p.image_url, p.video_url, p.media_urls, p.type, p.created_at, p.likes_count, p.comments_count, p.location, p.music_track_name, p.music_artist, p.audio_url, p.allow_comments, (SELECT COUNT(*) FROM likes WHERE user_id = '$currentUserId' AND post_id = p.id) as is_liked, u.name as authorName, u.avatar_url as authorAvatar, u.phone as authorPhone FROM posts p LEFT JOIN users u ON p.author_id = u.id WHERE (p.video_url IS NULL OR p.video_url = '') AND p.type != 'STORY' $shieldFilter ORDER BY p.created_at DESC LIMIT ?, ?"; $stmt = $conn->prepare($sqlPosts); // Безопасное выполнение запроса if ($stmt) { $stmt->bind_param("ii", $offset, $limit); $stmt->execute(); $res = $stmt->get_result(); while ($row = $res->fetch_assoc()) { $rawMedia = $row['media_urls']; $media = json_decode($rawMedia, true); if (is_string($media)) $media = json_decode($media, true); if (is_array($media)) { $row['mediaUrls'] = $media; } else { $row['mediaUrls'] = []; if (!empty($row['video_url'])) $row['mediaUrls'][] = $row['video_url']; elseif (!empty($row['image_url'])) $row['mediaUrls'][] = $row['image_url']; } $row['phone'] = !empty($row['authorPhone']) ? $row['authorPhone'] : ""; if (empty($row['authorName'])) { $row['authorName'] = "Пользователь"; $row['authorAvatar'] = ""; } $row['authorId'] = trim($row['author_id']); $row['timestamp'] = (int)$row['created_at']; $row['likes'] = (int)$row['likes_count']; $row['commentsCount'] = (int)$row['comments_count']; $ac = isset($row['allow_comments']) ? $row['allow_comments'] : 1; $row['allowComments'] = ($ac == 1); $row['musicTrackName'] = !empty($row['music_track_name']) ? $row['music_track_name'] : null; $row['musicArtist'] = $row['music_artist']; $row['musicPreviewUrl'] = ""; $row['audioUrl'] = !empty($row['audio_url']) ? $row['audio_url'] : null; $row['isLiked'] = ((int)$row['is_liked'] > 0); $row['isSaved'] = false; $row['isAd'] = false; $row['itemType'] = 'post'; unset($row['media_urls']); unset($row['is_liked']); unset($row['likes_count']); unset($row['comments_count']); unset($row['allow_comments']); unset($row['music_track_name']); $contentItems[] = $row; } $stmt->close(); } } // 3. PRODUCTS else { $sqlProducts = "SELECT id, name, price, old_price, images_json as imageUrls, category, store_id, owner_id, created_at, 'product' as itemType FROM grand_products WHERE status = 'active' AND (store_id IS NULL OR store_id = '' OR store_id = '0' OR store_id IN (SELECT id FROM stores WHERE subscription_expires_at > NOW())) ORDER BY created_at DESC LIMIT ?, ?"; $stmt = $conn->prepare($sqlProducts); $stmt->bind_param("ii", $offset, $limit); $stmt->execute(); $res = $stmt->get_result(); while ($row = $res->fetch_assoc()) { $imgs = json_decode($row['imageUrls']); $row['imageUrls'] = is_array($imgs) ? $imgs : []; $row['dateCreated'] = strtotime($row['created_at']); $row['rating'] = 0.0; $contentItems[] = $row; } $stmt->close(); } // ================================================================================= // ШАГ 2: ПОДМЕШИВАЕМ РЕКЛАМУ // ================================================================================= $ads = []; header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); // ============================================================================== // [НОВОЕ] ЖУЧОК-СНАЙПЕР 1: Проверяем, заходит ли вообще сервер в блок рекламы // ============================================================================== $countItems = count($contentItems); file_put_contents('debug_ads_blocker.txt', date('Y-m-d H:i:s') . " | РЕЖИМ: $mode | Обычных товаров (contentItems): $countItems шт.\n", FILE_APPEND); if (($mode == 'posts' || $mode == 'products' || $mode == 'reels') && count($contentItems) > 0) { $adsLimit = 4; $rawAds = []; $viewIds = []; $targetPlacement = ($mode == 'reels') ? 'REELS' : 'FEED'; // ============================================================================== // [ИЗМЕНЕНО] ИНЪЕКЦИЯ АУКЦИОНА: eCPM + Vickrey + Historical CTR + Targeting // ============================================================================== if (defined('ENABLE_VICKREY_AUCTION') && ENABLE_VICKREY_AUCTION === true) { file_put_contents('debug_ads_blocker.txt', " -> ⚡ АКТИВИРОВАН АУКЦИОН eCPM (VICKREY)\n", FILE_APPEND); // ============================================================================== // [ИЗМЕНЕНО] 1. Получаем контекст пользователя (Бронебойно, через city + try/catch) // ============================================================================== $userAge = 25; $userGender = 'Any'; $userLocation = 'Ashgabat'; if (!empty($safeUid)) { try { // Безопасно достаем возраст, пол и город (city) из базы $uRes = $conn->query("SELECT gender, city, IFNULL(TIMESTAMPDIFF(YEAR, birthdate, CURDATE()), 25) as age FROM users WHERE id = '$safeUid'"); if ($uRes && $uRow = $uRes->fetch_assoc()) { $userGender = !empty($uRow['gender']) ? $uRow['gender'] : 'Any'; $userLocation = !empty($uRow['city']) ? $uRow['city'] : 'Ashgabat'; $userAge = (int)$uRow['age']; } } catch (Throwable $e) { // Если таблицы или колонок нет, просто глотаем ошибку и используем дефолтные значения } } // [ИЗМЕНЕНО] 2. Выборка с учетом Полного Таргетинга (добавили 'Туркменистан') $sqlAds = "SELECT id, campaign_uuid, target_type, target_id, title, description, media_url, content_type, 'ad' as itemType, bid, bid_type, daily_budget, daily_spent, quality_score, status FROM ad_campaigns WHERE status = 'ACTIVE' AND placements LIKE CONCAT('%', ?, '%') AND spent < budget_limit AND daily_spent < daily_budget AND (gender = 'Any' OR gender = 'Любой' OR gender = ?) AND (age_from = 0 OR age_from <= ?) AND (age_to = 0 OR age_to >= ?) AND (location = 'All' OR location = 'Любой' OR location = 'Туркменистан' OR location = 'Весь Туркменистан' OR location = ?) -- [ИЗМЕНЕНО] $adShieldFilter"; $stmtAds = $conn->prepare($sqlAds); // [ИЗМЕНЕНО] Привязываем 5 переменных вместо 1 $stmtAds->bind_param("ssiis", $targetPlacement, $userGender, $userAge, $userAge, $userLocation); $stmtAds->execute(); $resAds = $stmtAds->get_result(); $candidates = []; $auction_id = uniqid('auc_'); // [НОВОЕ] Генерируем ID аукциона для этой выдачи (Traceability) while ($row = $resAds->fetch_assoc()) { if ($row['status'] === 'PAUSED' || $row['status'] === 'DELETED') continue; // Защита от удаленных // ============================================================================== // [ИЗМЕНЕНО] 3. Бронебойный расчет CTR (Защита от Fatal Error при отсутствии таблиц) // ============================================================================== $campId = (int)$row['id']; $historical_ctr = 0.02; // Fallback-значение (2%) try { $ctrQuery = $conn->query(" SELECT (SELECT COUNT(*) FROM ad_clicks WHERE campaign_id = $campId) as clicks, (SELECT COUNT(*) FROM ad_impressions WHERE campaign_id = $campId) as impressions "); if ($ctrQuery && $ctrRow = $ctrQuery->fetch_assoc()) { if ((int)$ctrRow['impressions'] > 50) { // Считаем CTR только если есть статистика (от 50 показов) $historical_ctr = (float)$ctrRow['clicks'] / (float)$ctrRow['impressions']; $historical_ctr = max(0.001, min(0.15, $historical_ctr)); // Лимиты от 0.1% до 15% (Антифрод) } } } catch (Throwable $e) { } // [НОВОЕ] 4. Считаем eCPM по формуле из ТЗ $eCPM = ($row['bid_type'] === 'CPC') ? ((float)$row['bid'] * $historical_ctr * 1000) : (float)$row['bid']; // [НОВОЕ] 5. Budget Pacing (равномерное распределение бюджета) $d_budget = max(0.0001, (float)$row['daily_budget']); $remaining = max(0, $d_budget - (float)$row['daily_spent']); $pacing = min(1.5, max(0.3, $remaining / ($d_budget * 0.7))); // Итоговый скор аукциона $row['final_score'] = $eCPM * $pacing * (float)$row['quality_score']; $row['auction_id'] = $auction_id; $candidates[] = $row; } $stmtAds->close(); // [НОВОЕ] 6. Ранжируем аукцион (Топ eCPM сверху) usort($candidates, function($a, $b) { return $b['final_score'] <=> $a['final_score']; }); // [НОВОЕ] ЖУЧОК-СНАЙПЕР: Диагностика аукциона file_put_contents('debug_ads_blocker.txt', " -> 🔍 КАНДИДАТЫ (после фильтрации): " . count($candidates) . " шт.\n", FILE_APPEND); if (count($candidates) > 0) { file_put_contents('debug_ads_blocker.txt', " -> 🥇 ТОП eCPM (final_score): " . $candidates[0]['final_score'] . "\n", FILE_APPEND); } else { // [ИЗМЕНЕНО] Сюда мы зайдем, если таргетинг отсек всю рекламу file_put_contents('debug_ads_blocker.txt', " -> 🛑 ВНИМАНИЕ: Кандидатов нет! Проверьте таргетинг (возраст/пол/локацию) или активность кампаний.\n", FILE_APPEND); } // Отрезаем нужное количество победителей $rawAds = array_slice($candidates, 0, $adsLimit); $adFound = count($rawAds); file_put_contents('debug_ads_blocker.txt', " -> 🏆 Победителей аукциона: $adFound шт.\n", FILE_APPEND); } else { // ============================================================================== // [ИЗМЕНЕНО] СТАРАЯ ЛОГИКА (FLAT-RATE) ОСТАЕТСЯ ДЛЯ ОБЕСПЕЧЕНИЯ СОВМЕСТИМОСТИ // ============================================================================== $sqlAds = "SELECT id, campaign_uuid, target_type, target_id, title, description, media_url, content_type, 'ad' as itemType FROM ad_campaigns WHERE status = 'ACTIVE' AND placements LIKE CONCAT('%', ?, '%') AND spent < budget_limit $adShieldFilter ORDER BY (CAST(budget_limit AS DECIMAL) * RAND()) DESC LIMIT ?"; $stmtAds = $conn->prepare($sqlAds); $stmtAds->bind_param("si", $targetPlacement, $adsLimit); $stmtAds->execute(); $resAds = $stmtAds->get_result(); $adFound = $resAds->num_rows; file_put_contents('debug_ads_blocker.txt', " -> 🎲 SQL RAND (Placement: $targetPlacement) нашел: $adFound шт.\n", FILE_APPEND); while ($row = $resAds->fetch_assoc()) { // Двойная проверка, чтобы исключить удаленные if ($row['status'] === 'PAUSED' || $row['status'] === 'DELETED') continue; $rawAds[] = $row; } $stmtAds->close(); } // ============================================================================== // [ИЗМЕНЕНО] ФОРМИРОВАНИЕ МАССИВА $ads (ОБЩИЙ ДЛЯ ОБОИХ АЛГОРИТМОВ) // ============================================================================== foreach ($rawAds as $adRow) { $viewIds[] = $adRow['id']; $adRow['itemType'] = 'ad'; $adRow['authorId'] = 'SPONSOR'; $adRow['authorName'] = 'Рекламное предложение'; $adRow['authorAvatar'] = 'https://cdn-icons-png.flaticon.com/512/1170/1170667.png'; $adRow['likes'] = 0; $adRow['comments'] = []; $adRow['shares'] = 0; $adRow['views'] = 0; $adRow['isLiked'] = false; $adRow['isSaved'] = false; $adRow['location'] = 'Туркменистан'; $adRow['content'] = !empty($adRow['description']) ? $adRow['description'] : ""; // [НОВОЕ] Умное распознавание видео (защита кроссплатформенных форматов iOS: .mov) $mediaLower = strtolower($adRow['media_url'] ?? ''); $isVideo = !empty($adRow['media_url']) && (strpos($mediaLower, '.mp4') !== false || strpos($mediaLower, '.mov') !== false); $adRow['type'] = $isVideo ? 'VIDEO' : 'IMAGE'; // [НОВОЕ] ЖЕСТКИЙ ФИЛЬТР: В массив рекламы для Reels пускаем ТОЛЬКО видео! // Это предотвращает "сгорание слота" в миксере. if ($mode == 'reels' && !$isVideo) { continue; } $adRow['videoUrl'] = !empty($adRow['media_url']) ? $adRow['media_url'] : ""; $adRow['created_at'] = time(); $adRow['dateCreated'] = time(); $adRow['timestamp'] = time(); if (!empty($adRow['target_id'])) $adRow['id'] = $adRow['target_id']; $realPrice = 0; $realBrand = ""; $realCategory = ""; $realStoreId = null; // ===================================================================== // [ХИРУРГИЯ 6.0 FINAL] ПОИСК ДАННЫХ (ОСНОВАНО НА УСПЕШНОМ ТЕСТЕ) // ===================================================================== $realPrice = 0; $realBrand = ""; $realCategory = ""; $realStoreId = null; $adRow['phone'] = ""; // Сброс перед поиском $adRow['owner_id'] = ""; if (!empty($adRow['target_id'])) { $rawTid = trim($adRow['target_id']); // Очищаем ID от 'prod_' для поиска по чистому ID, но оставляем для SKU $cleanId = str_replace('prod_', '', $rawTid); $safeTid = $conn->real_escape_string($rawTid); // ----------------------------------------------------------- // 1. ИЩЕМ В МАГАЗИНАХ (GRAND_PRODUCTS) // Используем LIKE '$safeTid', чтобы обойти конфликт кодировок! // ----------------------------------------------------------- $sqlGrand = "SELECT p.*, u.phone as owner_phone, u.id as real_owner_id, u.avatar_url as user_avatar, s.name as shop_name, s.avatar_url as shop_avatar FROM grand_products p LEFT JOIN users u ON p.owner_id = u.id LEFT JOIN shops s ON p.store_id = s.id WHERE p.id = '$safeTid' OR p.sku LIKE '$safeTid' OR p.id LIKE '%$cleanId%' LIMIT 1"; $q1 = $conn->query($sqlGrand); if ($q1 && $q1->num_rows > 0) { // -> НАШЛИ В МАГАЗИНЕ $d = $q1->fetch_assoc(); $realPrice = (float)$d['price']; $realBrand = $d['brand']; $realCategory = $d['category']; $realStoreId = $d['store_id']; // [ВАЖНО] Записываем Телефон и Владельца if (!empty($d['owner_phone'])) $adRow['phone'] = $d['owner_phone']; $adRow['owner_id'] = !empty($d['real_owner_id']) ? $d['real_owner_id'] : $d['owner_id']; // Визуал (Имя магазина) if (!empty($d['shop_name'])) $adRow['authorName'] = $d['shop_name']; $finalAvatar = !empty($d['shop_avatar']) ? $d['shop_avatar'] : $d['user_avatar']; if (!empty($finalAvatar)) { $adRow['authorAvatar'] = (strpos($finalAvatar, 'http') === 0) ? $finalAvatar : "http://" . $_SERVER['HTTP_HOST'] . "/api/v1/uploads/" . $finalAvatar; } // Картинка (если в рекламе нет своей) if (empty($adRow['media_url'])) { $imgs = json_decode($d['images_json']); $imgUrl = is_array($imgs) ? $imgs[0] : null; if ($imgUrl) $adRow['media_url'] = (strpos($imgUrl, 'http') === 0) ? $imgUrl : "http://" . $_SERVER['HTTP_HOST'] . "/api/v1/uploads/" . $imgUrl; } } else { // ----------------------------------------------------------- // 2. ИЩЕМ В ЧАСТНЫХ (PRODUCTS) // Тоже используем LIKE для SKU // ----------------------------------------------------------- $sqlSimple = "SELECT p.*, u.phone as owner_phone, u.id as real_owner_id, u.name as user_name, u.avatar_url as user_avatar FROM products p LEFT JOIN users u ON p.owner_id = u.id WHERE p.id = '$safeTid' OR p.sku LIKE '$safeTid' OR p.id LIKE '%$cleanId%' LIMIT 1"; $q2 = $conn->query($sqlSimple); if ($q2 && $q2->num_rows > 0) { // -> НАШЛИ В ЧАСТНЫХ $d = $q2->fetch_assoc(); $realPrice = (float)$d['price']; $realBrand = !empty($d['brand']) ? $d['brand'] : "Частное"; $realCategory = !empty($d['category']) ? $d['category'] : "Разное"; $realStoreId = null; // [ВАЖНО] Записываем Телефон и Владельца if (!empty($d['owner_phone'])) $adRow['phone'] = $d['owner_phone']; $adRow['owner_id'] = !empty($d['real_owner_id']) ? $d['real_owner_id'] : $d['owner_id']; $adRow['authorName'] = !empty($d['user_name']) ? $d['user_name'] : "Пользователь"; if (!empty($d['user_avatar'])) { $av = $d['user_avatar']; $adRow['authorAvatar'] = (strpos($av, 'http') === 0) ? $av : "http://" . $_SERVER['HTTP_HOST'] . "/api/v1/uploads/" . $av; } } } } // Применяем данные $adRow['price'] = $realPrice; $adRow['brand'] = !empty($realBrand) ? $realBrand : ""; $adRow['category'] = !empty($realCategory) ? $realCategory : ""; $adRow['storeId'] = $realStoreId; $adRow['store_id'] = $realStoreId; if (!empty($adRow['owner_id'])) { $adRow['authorId'] = $adRow['owner_id']; } // ===================================================================== // [ХИРУРГИЯ 6.0] ОЖИВЛЕНИЕ СТАТИСТИКИ (Лайки, Комменты) // Получаем ID текущего пользователя для проверки "Лайкнул ли я?" $currentUid = isset($_GET['user_id']) ? trim($_GET['user_id']) : ''; // Берем ID товара (мы его уже положили в id ранее: $adRow['id'] = $adRow['target_id']) $prodIdForStats = $conn->real_escape_string($adRow['id']); if (!empty($prodIdForStats)) { // Делаем 3 быстрых запроса в одном: // 1. Общее кол-во лайков // 2. Общее кол-во комментариев // 3. Лайкнул ли ЭТОТ пользователь ЭТОТ товар? $sqlStats = "SELECT (SELECT COUNT(*) FROM likes WHERE post_id = '$prodIdForStats') as l_cnt, (SELECT COUNT(*) FROM comments WHERE post_id = '$prodIdForStats') as c_cnt, (SELECT COUNT(*) FROM likes WHERE post_id = '$prodIdForStats' AND user_id = '$currentUid') as i_liked"; $qStats = $conn->query($sqlStats); if ($qStats && $rowStats = $qStats->fetch_assoc()) { // Записываем реальные цифры $adRow['likes'] = (int)$rowStats['l_cnt']; $adRow['commentsCount'] = (int)$rowStats['c_cnt']; // Важно: Android ждет commentsCount // Если i_liked > 0, значит лайк стоит (сердечко будет красным) $adRow['isLiked'] = ((int)$rowStats['i_liked'] > 0); } } $isVideo = !empty($adRow['media_url']) && strpos($adRow['media_url'], '.mp4') !== false; $adRow['type'] = $isVideo ? 'VIDEO' : 'IMAGE'; $adRow['mediaUrls'] = []; $adRow['tags'] = []; $adRow['currency'] = "TMT"; $adRow['musicTrackName'] = ""; $adRow['musicArtist'] = ""; $adRow['musicPreviewUrl'] = ""; $adRow['adUrl'] = "product://" . $adRow['id']; $adRow['actionButtonText'] = "Подробнее"; $img = !empty($adRow['media_url']) ? $adRow['media_url'] : ""; $adRow['imageUrl'] = $img; $adRow['image_url'] = $img; $adRow['id'] = (string)$adRow['id']; $adRow['isAd'] = true; $ads[] = $adRow; } if (!empty($viewIds)) { $idsString = implode(',', $viewIds); $conn->query("UPDATE ad_campaigns SET views_count = views_count + 1 WHERE id IN ($idsString)"); } } if ($mode == 'stories') { echo json_encode($contentItems); } else { $finalFeed = []; $adIndex = 0; $adInterval = 5; foreach ($contentItems as $index => $item) { $finalFeed[] = $item; // Подмешиваем рекламу каждые $adInterval постов if (($index + 1) % $adInterval == 0) { if (isset($ads[$adIndex])) { // --- ЖУЧОК НАЧАЛО --- $debugMsg = "Mode: [" . $mode . "] | AdType: [" . $ads[$adIndex]['type'] . "] | Is Video? " . ($ads[$adIndex]['type'] == 'VIDEO' ? 'YES' : 'NO') . "\n"; file_put_contents('debug_ad_logic.txt', $debugMsg, FILE_APPEND); // --- ЖУЧОК КОНЕЦ --- $adCandidate = $ads[$adIndex]; // [ХИРУРГИЯ] Если это Reels, пропускаем всё, что НЕ видео if ($mode == 'reels' && $adCandidate['type'] != 'VIDEO') { $adIndex++; // Пропуск картинки } else { $finalFeed[] = $adCandidate; $adIndex++; } } } } echo json_encode($finalFeed); } ?> === ФАЙЛ: get_messages.php === "error", "message" => "Missing parameters"]); exit(); } // --- [НОВОЕ] ОТМЕЧАЕМ КАК "ДОСТАВЛЕНО" --- // Если Я ($user_id) запрашиваю сообщения, значит, все сообщения, // отправленные МНЕ ($receiver_id = $user_id) от НЕГО ($sender_id = $target_id), // считаются ДОСТАВЛЕННЫМИ. $update_sql = "UPDATE messages SET is_delivered = 1 WHERE receiver_id = ? AND sender_id = ? AND is_delivered = 0"; $upd_stmt = $conn->prepare($update_sql); $upd_stmt->bind_param("ss", $user_id, $target_id); $upd_stmt->execute(); $upd_stmt->close(); // ------------------------------------------ // [ИЗМЕНЕНО] Добавлено поле m.order_json в выборку из базы $sql = "SELECT m.id, m.sender_id, m.receiver_id, m.message_text, m.image_url, m.audio_url, m.audio_duration, m.is_read, m.is_delivered, m.created_at, m.reply_to_id, m.type, m.offer_price, m.offer_conditions, m.offer_status, m.order_json, p.message_text AS reply_to_text, u.username AS reply_to_sender_name, (SELECT GROUP_CONCAT(emoji SEPARATOR ',') FROM message_reactions WHERE message_id = m.id) AS reactions_list FROM messages m LEFT JOIN messages p ON m.reply_to_id = p.id LEFT JOIN users u ON p.sender_id = u.id WHERE (m.sender_id = ? AND m.receiver_id = ?) OR (m.sender_id = ? AND m.receiver_id = ?) ORDER BY m.created_at ASC"; $stmt = $conn->prepare($sql); $stmt->bind_param("ssss", $user_id, $target_id, $target_id, $user_id); $stmt->execute(); $result = $stmt->get_result(); $messages = []; while ($row = $result->fetch_assoc()) { $timestamp = strtotime($row['created_at']) * 1000; $messages[] = [ "id" => (string)$row['id'], "senderId" => $row['sender_id'], "receiverId" => $row['receiver_id'], "text" => $row['message_text'] ?? "", "imageUrl" => $row['image_url'], "isRead" => (bool)$row['is_read'], "isDelivered" => (bool)$row['is_delivered'], // <-- Передаем новый статус "timestamp" => $timestamp, "audio_url" => $row['audio_url'] ?? null, "audio_duration" => isset($row['audio_duration']) ? (int)$row['audio_duration'] : 0, "reply_to_id" => $row['reply_to_id'] ?? null, "reply_to_text" => $row['reply_to_text'] ?? null, "reply_to_sender_name" => $row['reply_to_sender_name'] ?? null, "reactions" => !empty($row['reactions_list']) ? explode(',', $row['reactions_list']) : [], "type" => $row['type'] ?? 'text', "offer_price" => isset($row['offer_price']) ? (float)$row['offer_price'] : null, "offer_conditions" => $row['offer_conditions'] ?? null, "offer_status" => $row['offer_status'] ?? null, "order_json" => $row['order_json'] ?? null ]; } echo json_encode(["status" => "success", "data" => $messages]); $stmt->close(); $conn->close(); ?> === ФАЙЛ: get_my_ads.php === owner_id header("Content-Type: application/json; charset=UTF-8"); ini_set('display_errors', 0); // В продакшене ошибки скрываем error_reporting(E_ALL); include 'db.php'; // Проверка соединения if (!isset($conn) && isset($mysqli)) { $conn = $mysqli; } if (!$conn) { echo json_encode([]); exit(); } // Получаем ID пользователя из Android $userId = isset($_GET['user_id']) ? $_GET['user_id'] : ''; if (empty($userId)) { echo json_encode([]); exit(); } // [ГЛАВНОЕ ИСПРАВЛЕНИЕ] // Используем owner_id вместо user_id $sql = "SELECT id, owner_id, target_id, title, media_url, status, budget_limit, views_count, clicks_count, spent, placements, location, created_at, gender, age_from, age_to, interests FROM ad_campaigns WHERE owner_id = ? ORDER BY id DESC"; $stmt = $conn->prepare($sql); if (!$stmt) { // Если ошибка в SQL - вернем пустой список, чтобы приложение не падало echo json_encode([]); exit(); } $stmt->bind_param("s", $userId); $stmt->execute(); $result = $stmt->get_result(); $campaigns = []; while ($row = $result->fetch_assoc()) { // --- 1. ПЕРЕВОДЧИК ИМЕН (DB -> Android) --- // База: owner_id -> Android ждет: user_id $row['user_id'] = $row['owner_id']; // База: location -> Android ждет: target_location $row['target_location'] = isset($row['location']) ? $row['location'] : ""; // База: budget_limit -> Android ждет: budget $row['budget'] = isset($row['budget_limit']) ? (string)$row['budget_limit'] : "0"; $row['spent'] = isset($row['spent']) ? (float)$row['spent'] : 0.00; $row['gender'] = isset($row['gender']) ? $row['gender'] : "Любой"; $row['age_from'] = isset($row['age_from']) ? (string)$row['age_from'] : "18"; $row['age_to'] = isset($row['age_to']) ? (string)$row['age_to'] : "65"; $row['interests'] = isset($row['interests']) ? $row['interests'] : ""; // --- 2. ОБРАБОТКА ДАННЫХ --- // [ИСПРАВЛЕНИЕ ОШИБКИ NULL] 🛡 // 1. Декодируем JSON $decodedPlacements = isset($row['placements']) ? json_decode($row['placements']) : []; // 2. ЖЕСТКАЯ ПРОВЕРКА: Если json_decode вернул NULL или это не массив -> ставим [] if (!is_array($decodedPlacements)) { $decodedPlacements = []; } // 3. Отправляем в Android гарантированный массив $row['placements'] = $decodedPlacements; // Приводим счетчики к числам $row['views_count'] = (int)$row['views_count']; $row['clicks_count'] = (int)$row['clicks_count']; // Приводим счетчики к числам $row['views_count'] = (int)$row['views_count']; $row['clicks_count'] = (int)$row['clicks_count']; // Гарантируем наличие полей для Android заглушек if (!isset($row['title'])) $row['title'] = "Без названия"; if (!isset($row['media_url'])) $row['media_url'] = null; $campaigns[] = $row; } echo json_encode($campaigns); ?> === ФАЙЛ: get_my_stores.php === "error", "message" => "Missing User ID"]); exit(); } $safe_id = $conn->real_escape_string($user_id); // Выбираем только те магазины, где owner_id совпадает с ID пользователя $sql = "SELECT id, name, description, category, address, phone, logo_url, cover_url, rating, is_verified,subscription_expires_at FROM stores WHERE owner_id = '$safe_id' ORDER BY created_at DESC"; $result = $conn->query($sql); $stores = []; if ($result) { while ($row = $result->fetch_assoc()) { // ============================================================================== // [НОВОЕ] Математика подписки: Считаем оставшиеся дни // ============================================================================== $days_left = 0; $is_frozen = true; // По умолчанию заморожен, если даты нет if (!empty($row['subscription_expires_at'])) { $expire_timestamp = strtotime($row['subscription_expires_at']); $current_timestamp = time(); // Считаем разницу в секундах и переводим в дни (округляем в меньшую сторону) $diff_seconds = $expire_timestamp - $current_timestamp; $days_left = floor($diff_seconds / (60 * 60 * 24)); // Если осталось меньше 0 дней, ставим 0 if ($days_left <= 0) { $days_left = 0; $is_frozen = true; } else { $is_frozen = false; } } // ============================================================================== $stores[] = [ "id" => (string)$row['id'], "owner_id" => $safe_id, "name" => (string)$row['name'], "description" => (string)$row['description'], "category" => (string)$row['category'], "address" => (string)$row['address'], "phone" => (string)$row['phone'], "logo_url" => !empty($row['logo_url']) ? $row['logo_url'] : null, "cover_url" => !empty($row['cover_url']) ? $row['cover_url'] : null, "rating" => (float)$row['rating'], "is_verified" => ($row['is_verified'] == 1), "whatsapp" => "", "followersCount" => 0, "days_left" => (int)$days_left, "is_frozen" => (bool)$is_frozen ]; } // 2. ЛОГИРОВАНИЕ РЕЗУЛЬТАТА (ЖУЧОК) file_put_contents('debug_stores.txt', " -> FOUND: " . count($stores) . " stores for user '$safe_id'\n", FILE_APPEND); } else { // Логируем ошибку базы данных file_put_contents('debug_stores.txt', " -> DB ERROR: " . $conn->error . "\n", FILE_APPEND); } echo json_encode(["status" => "success", "data" => $stores], JSON_UNESCAPED_UNICODE); $conn->close(); ?> === ФАЙЛ: get_notification_settings.php === "error", "message" => "User ID required"]); exit; } // 1. Пытаемся найти настройки пользователя $sql = "SELECT * FROM notification_settings WHERE user_id = ?"; $stmt = $conn->prepare($sql); $stmt->bind_param("s", $user_id); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { // Если настройки уже есть — отдаем их $settings = $result->fetch_assoc(); echo json_encode(["status" => "success", "data" => $settings]); } else { // 2. Если настроек НЕТ — создаем "По умолчанию" (Всё включено) $insert_sql = "INSERT INTO notification_settings (user_id, pause_all, messages, likes, comments, follows, new_ads, sound_enabled) VALUES (?, 0, 1, 1, 1, 1, 1, 1)"; $insert_stmt = $conn->prepare($insert_sql); $insert_stmt->bind_param("s", $user_id); if ($insert_stmt->execute()) { // Отдаем дефолтные настройки echo json_encode([ "status" => "success", "data" => [ "user_id" => $user_id, "pause_all" => 0, "messages" => 1, "likes" => 1, "comments" => 1, "follows" => 1, "new_ads" => 1, "sound_enabled" => 1 ] ]); } else { echo json_encode(["status" => "error", "message" => "Failed to create default settings"]); } } $stmt->close(); $conn->close(); ?> === ФАЙЛ: get_notifications.php === set_charset("utf8mb4"); $user_id = $_GET['user_id'] ?? ''; if (empty($user_id)) { echo json_encode(["status" => "error", "message" => "No user ID"]); exit(); } $safe_id = $conn->real_escape_string($user_id); // [НОВОЕ] Для запроса скрытых // ================================================================================= // [НОВОЕ] 0. ЗАГРУЖАЕМ "ЧЕРНЫЙ СПИСОК" (HIDDEN ITEMS) // ================================================================================= $hidden_map = []; $sql_hidden = "SELECT notification_id FROM hidden_notifications WHERE user_id = '$safe_id'"; if ($h_res = $conn->query($sql_hidden)) { while ($h_row = $h_res->fetch_assoc()) { $hidden_map[$h_row['notification_id']] = true; } } // ================================================================================= // ФУНКЦИЯ ПРОВЕРКИ ПОДПИСКИ function checkIsFollowing($conn, $me, $target) { if ($me == $target) return false; $sql = "SELECT 1 FROM follows WHERE follower_id = '$me' AND following_id = '$target' LIMIT 1"; $res = $conn->query($sql); return ($res && $res->num_rows > 0); } $notifications = []; // ================================================================================= // 1. СООБЩЕНИЯ (Таблица messages) // ================================================================================= $sql_msgs = " SELECT MAX(m.created_at) as timestamp, u.id as actor_id, u.name as actor_name, u.avatar_url as actor_avatar, COUNT(*) as count_msgs, MAX(m.message_text) as last_msg, MAX(m.is_read) as is_read -- Берем статус FROM messages m JOIN users u ON m.sender_id = u.id WHERE m.receiver_id = ? AND m.is_read = 0 -- Показываем непрочитанные GROUP BY m.sender_id, u.id, u.name, u.avatar_url ORDER BY timestamp DESC "; if ($stmt = $conn->prepare($sql_msgs)) { $stmt->bind_param("s", $user_id); $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // [НОВОЕ] Генерируем ID и проверяем фильтр $gen_id = "msg_" . $row['actor_id'] . "_" . $row['timestamp']; if (isset($hidden_map[$gen_id])) continue; // Если в корзине - пропускаем $msgText = $row['count_msgs'] > 1 ? "написал(а) вам " . $row['count_msgs'] . " сообщений." : "написал(а): " . mb_substr($row['last_msg'], 0, 30) . "..."; $notifications[] = [ "id" => $gen_id, // [ИЗМЕНЕНО] Используем переменную "title" => $row['actor_name'] ?? 'Пользователь', "message" => $msgText, "timestamp" => (string)$row['timestamp'], "is_read" => (bool)$row['is_read'], // Реальный статус "type" => "message", "user_avatar" => $row['actor_avatar'], "post_preview" => null, "action_id" => $row['actor_id'], // ID собеседника для клика "sender_id" => null, "is_following" => false ]; } $stmt->close(); } // ================================================================================= // 2. ПОДПИСКИ (Таблица follows) // ================================================================================= $sql_follows = " SELECT f.created_at as timestamp, f.is_read, -- Читаем статус из БД u.id as actor_id, u.name as actor_name, u.avatar_url as actor_avatar FROM follows f JOIN users u ON f.follower_id = u.id WHERE f.following_id = ? ORDER BY f.created_at DESC LIMIT 20 "; if ($stmt = $conn->prepare($sql_follows)) { $stmt->bind_param("s", $user_id); $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { $ts = $row['timestamp'] ?? date('Y-m-d H:i:s'); // [НОВОЕ] Генерируем ID и проверяем фильтр $gen_id = "follow_" . $row['actor_id'] . "_" . $ts; if (isset($hidden_map[$gen_id])) continue; // Если в корзине - пропускаем $is_sub = checkIsFollowing($conn, $user_id, $row['actor_id']); $notifications[] = [ "id" => $gen_id, // [ИЗМЕНЕНО] Используем переменную "title" => $row['actor_name'] ?? 'Пользователь', "message" => "подписался(-ась) на ваши обновления.", "timestamp" => (string)$ts, "is_read" => (bool)$row['is_read'], // Реальный статус "type" => "follow", "user_avatar" => $row['actor_avatar'], "post_preview" => null, "action_id" => $row['actor_id'], "sender_id" => $row['actor_id'], "is_following" => $is_sub ]; } $stmt->close(); } // ================================================================================= // 3. ЛАЙКИ ПОСТОВ (Таблица likes) // ================================================================================= $sql_likes = " SELECT l.created_at as timestamp, l.is_read, -- Читаем статус из БД u.id as actor_id, u.name as actor_name, u.avatar_url as actor_avatar, p.id as post_id, p.image_url as post_image, p.type as post_type FROM likes l JOIN users u ON l.user_id = u.id JOIN posts p ON l.post_id = p.id WHERE p.author_id = ? AND l.user_id != ? ORDER BY l.created_at DESC LIMIT 20 "; if ($stmt = $conn->prepare($sql_likes)) { $stmt->bind_param("ss", $user_id, $user_id); $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // [НОВОЕ] Генерируем ID и проверяем фильтр $gen_id = "like_" . $row['post_id'] . "_" . $row['actor_id']; if (isset($hidden_map[$gen_id])) continue; // Если в корзине - пропускаем $preview = $row['post_image']; if (strpos($preview, '[') === 0) { $decoded = json_decode($preview, true); $preview = !empty($decoded) ? $decoded[0] : null; } $is_sub = checkIsFollowing($conn, $user_id, $row['actor_id']); $notifications[] = [ "id" => $gen_id, // [ИЗМЕНЕНО] Используем переменную "title" => $row['actor_name'] ?? 'Пользователь', "message" => "оценил(а) вашу публикацию.", "timestamp" => (string)$row['timestamp'], "is_read" => (bool)$row['is_read'], // Реальный статус "type" => "like", "user_avatar" => $row['actor_avatar'], "post_preview" => $preview, "action_id" => $row['post_id'], "post_type" => $row['post_type'], "sender_id" => $row['actor_id'], "is_following" => $is_sub ]; } $stmt->close(); } // ================================================================================= // 4. КОММЕНТАРИИ (Таблица comments) // ================================================================================= $sql_comments = " SELECT c.created_at as timestamp, c.text as comment_text, c.is_read, -- Читаем статус из БД u.id as actor_id, u.name as actor_name, u.avatar_url as actor_avatar, p.id as post_id, p.image_url as post_image, p.type as post_type FROM comments c JOIN users u ON c.user_id = u.id JOIN posts p ON c.post_id = p.id WHERE p.author_id = ? AND c.user_id != ? ORDER BY c.created_at DESC LIMIT 20 "; if ($stmt = $conn->prepare($sql_comments)) { $stmt->bind_param("ss", $user_id, $user_id); $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // [НОВОЕ] Генерируем ID и проверяем фильтр $gen_id = "comm_" . $row['timestamp']; if (isset($hidden_map[$gen_id])) continue; // Если в корзине - пропускаем $preview = $row['post_image']; if (strpos($preview, '[') === 0) { $decoded = json_decode($preview, true); $preview = !empty($decoded) ? $decoded[0] : null; } $shortText = mb_substr($row['comment_text'], 0, 30) . "..."; $is_sub = checkIsFollowing($conn, $user_id, $row['actor_id']); $notifications[] = [ "id" => $gen_id, // [ИЗМЕНЕНО] Используем переменную "title" => $row['actor_name'] ?? 'Пользователь', "message" => "прокомментировал(а): «" . $shortText . "»", "timestamp" => (string)$row['timestamp'], "is_read" => (bool)$row['is_read'], // Реальный статус "type" => "comment", "user_avatar" => $row['actor_avatar'], "post_preview" => $preview, "action_id" => $row['post_id'], "post_type" => $row['post_type'], "sender_id" => $row['actor_id'], "is_following" => $is_sub ]; } $stmt->close(); } // ================================================================================= // 5. СИСТЕМНЫЕ УВЕДОМЛЕНИЯ И ЛАЙКИ ИСТОРИЙ (Таблица notifications) // ================================================================================= $sql_notif = "SELECT * FROM notifications WHERE user_id = ? ORDER BY created_at DESC LIMIT 20"; if ($stmt = $conn->prepare($sql_notif)) { $stmt->bind_param("s", $user_id); $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // [НОВОЕ] Генерируем ID и проверяем фильтр $gen_id = "notif_" . $row['id']; // [ХИРУРГИЧЕСКАЯ ВСТАВКА] ФИЛЬТР ДУБЛИКАТОВ // Мы игнорируем типы, которые уже прочитали в блоках 1-4, // чтобы не показывать их дважды в Колокольчике. // Но Пуш-уведомления (check_notifications.php) их увидят! if (in_array($row['type'], ['message', 'like', 'follow', 'comment'])) { continue; } if (isset($hidden_map[$gen_id])) continue; // Если в корзине - пропускаем $is_sub = checkIsFollowing($conn, $user_id, $row['sender_id']); $postType = null; if ($row['type'] === 'like' && strpos($row['message'], 'история') !== false) { $postType = 'STORY'; } $notifications[] = [ "id" => $gen_id, // [ИЗМЕНЕНО] Используем переменную "title" => $row['title'], "message" => $row['message'], "timestamp" => $row['created_at'], "is_read" => (bool)$row['is_read'], "type" => $row['type'], "user_avatar" => $row['user_avatar'], "post_preview" => !empty($row['post_preview']) ? $row['post_preview'] : null, "action_id" => $row['action_id'], "post_type" => $postType, "is_following" => $is_sub, "sender_id" => $row['sender_id'] ]; } $stmt->close(); } // ================================================================================= // ФИНАЛ: СОРТИРОВКА И ВЫВОД // ================================================================================= usort($notifications, function($a, $b) { return $b['timestamp'] <=> $a['timestamp']; }); ob_end_clean(); echo json_encode([ "status" => "success", "data" => $notifications ], JSON_UNESCAPED_UNICODE); ?> === ФАЙЛ: get_post_details.php === "error", "message" => "No ID"]); exit(); } // Запрос, который вытягивает всё нужное для FullScreen $sql = "SELECT p.*, u.name as author_name, u.avatar_url as author_avatar, (SELECT COUNT(*) FROM likes WHERE post_id = p.id AND user_id = ?) as is_viewer_liked FROM posts p LEFT JOIN users u ON p.author_id = u.id WHERE p.id = ? LIMIT 1"; $stmt = $conn->prepare($sql); $stmt->bind_param("ss", $viewerId, $postId); $stmt->execute(); $result = $stmt->get_result(); $row = $result->fetch_assoc(); if ($row) { echo json_encode([ "id" => $row['id'], "authorId" => $row['author_id'], "authorName" => $row['author_name'] ?? "Пользователь", "authorAvatar" => $row['author_avatar'], "content" => $row['content'], "imageUrl" => $row['image_url'], "videoUrl" => $row['video_url'], "type" => $row['type'], "likes" => (int)$row['likes_count'], "commentsCount" => (int)$row['comments_count'], "isLiked" => (bool)$row['is_viewer_liked'], "timestamp" => (float)$row['created_at'], "mediaUrls" => !empty($row['media_urls']) ? json_decode($row['media_urls']) : [] ]); } else { http_response_code(404); echo json_encode(["status" => "error", "message" => "Post not found"]); } $conn->close(); ?> === ФАЙЛ: get_posts.php === prepare($sql); $stmt->bind_param("s", $viewer_id); $stmt->execute(); $result = $stmt->get_result(); $posts = []; if ($result && $result->num_rows > 0) { while($row = $result->fetch_assoc()) { $post = []; $post['id'] = $row['id']; $post['authorId'] = $row['author_id']; $post['authorName'] = $row['author_name'] ?? 'Неизвестный'; $post['authorAvatar'] = $row['author_avatar']; $post['title'] = $row['title']; $post['content'] = $row['content']; $post['imageUrl'] = $row['image_url']; $post['videoUrl'] = $row['video_url']; $post['type'] = $row['type']; $post['mediaUrls'] = !empty($row['media_urls']) ? json_decode($row['media_urls']) : []; $post['tags'] = !empty($row['tags']) ? json_decode($row['tags']) : []; $post['likesCount'] = (int)$row['likes_count']; $post['commentsCount'] = (int)$row['comments_count']; // [ИСПРАВЛЕНИЕ] Проверка лайка через LEFT JOIN // Если liked_id не пустой, значит лайк стоит. $post['isLiked'] = !empty($row['liked_id']); $post['timestamp'] = (float)$row['created_at']; $post['isAd'] = (bool)$row['is_ad']; $post['location'] = $row['location']; $post['musicTrackName'] = $row['music_track']; $posts[] = $post; } } echo json_encode($posts); $conn->close(); ?> === ФАЙЛ: get_posts_by_user.php === prepare($sql); $stmt->bind_param("ss", $viewerId, $userId); $stmt->execute(); $result = $stmt->get_result(); $posts = []; while ($row = $result->fetch_assoc()) { $post = []; // Мапинг полей строго под модель Android (Post.kt) $post['id'] = (string)$row['id']; $post['authorId'] = $row['author_id']; $post['authorName'] = $row['authorName'] ?? 'Пользователь'; $post['authorAvatar'] = $row['authorAvatar'] ?? ''; $post['allowComments'] = isset($row['allow_comments']) ? (bool)$row['allow_comments'] : true; $post['musicTrackName'] = $row['music_track_name'] ?? null; $post['audioUrl'] = $row['audio_url'] ?? null; $post['content'] = $row['content']; $post['title'] = $row['title'] ?? ""; $post['imageUrl'] = $row['image_url']; $post['videoUrl'] = $row['video_url']; $post['type'] = $row['type']; $post['location'] = $row['location']; // Синхронизация счетчиков $post['likes'] = (int)$row['likes_count']; // Основное имя для Post.kt $post['likesCount'] = (int)$row['likes_count']; // Для страховки $post['commentsCount'] = (int)$row['comments_count']; // [ВАЖНО] Статус лайка (чтобы сердечко не гасло) $post['isLiked'] = (bool)$row['is_viewer_liked']; // Декодирование JSON $post['mediaUrls'] = !empty($row['media_urls']) ? json_decode($row['media_urls']) : []; $post['tags'] = !empty($row['tags']) ? json_decode($row['tags']) : []; $post['timestamp'] = (float)$row['created_at']; $posts[] = $post; } echo json_encode($posts, JSON_UNESCAPED_UNICODE); } catch (Exception $e) { echo json_encode([]); } ?> === ФАЙЛ: get_product.php === set_charset("utf8mb4"); // Настройка путей к картинкам $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http"; $baseUrl = "$protocol://" . $_SERVER['HTTP_HOST'] . "/api/v1/uploads/"; function sendJson($data) { ob_end_clean(); echo json_encode($data, JSON_UNESCAPED_UNICODE); exit(); } if (!isset($_GET['product_id'])) { sendJson(null); } $prod_id = $_GET['product_id']; // [1] УВЕЛИЧИВАЕМ СЧЕТЧИК ПРОСМОТРОВ (+1) $sqlView = "UPDATE products SET views = views + 1 WHERE id = ?"; $stmtView = $conn->prepare($sqlView); if ($stmtView) { $stmtView->bind_param("s", $prod_id); $stmtView->execute(); $stmtView->close(); } // [2] ПОЛУЧАЕМ ДАННЫЕ (ВКЛЮЧАЯ ТЕЛЕФОН u.phone) $sql = "SELECT p.*, u.phone, COALESCE(s.name, u.name) as store_name, COALESCE(s.avatar_url, u.avatar_url) as store_avatar, COALESCE(s.is_verified, 0) as is_verified FROM products p LEFT JOIN shops s ON p.store_id = s.id LEFT JOIN users u ON p.owner_id = u.id WHERE p.id = ? LIMIT 1"; $stmt = $conn->prepare($sql); if (!$stmt) sendJson(null); $stmt->bind_param("s", $prod_id); $stmt->execute(); $result = $stmt->get_result(); if ($row = $result->fetch_assoc()) { // Обработка картинок $images = []; if (!empty($row['image_urls'])) { $decoded = json_decode($row['image_urls'], true); if (json_last_error() === JSON_ERROR_NONE) { $rawImages = is_array($decoded) ? $decoded : [$row['image_urls']]; foreach ($rawImages as $img) { if (!empty($img)) { $images[] = (strpos($img, 'http') === 0) ? $img : $baseUrl . $img; } } } else { $images[] = (strpos($row['image_urls'], 'http') === 0) ? $row['image_urls'] : $baseUrl . $row['image_urls']; } } $row['image_urls'] = $images; // Аватар магазина if (!empty($row['store_avatar']) && strpos($row['store_avatar'], 'http') === false) { $row['store_avatar'] = $baseUrl . $row['store_avatar']; } // Приведение типов для Android $row['price'] = (double)$row['price']; $row['old_price'] = $row['old_price'] ? (double)$row['old_price'] : null; $row['is_sale'] = (bool)$row['is_sale']; $row['views'] = (int)$row['views']; // Возвращаем обновленные просмотры sendJson($row); } else { sendJson(null); } ?> === ФАЙЛ: get_profile_visitors.php === real_escape_string($user_id); // 1. Простой и надежный SQL запрос // Мы убрали фильтр hidden_notifications, так как он вызывал краш (Error 500) $sql = "SELECT pv.visited_at, u.id, u.name, u.avatar_url, u.bio, -- Проверяем, подписан ли Я (кто смотрит список) на этого гостя (SELECT COUNT(*) FROM follows f WHERE f.follower_id = '$safe_id' AND f.following_id = u.id) as is_following FROM profile_visits pv JOIN users u ON pv.visitor_id = u.id WHERE pv.target_id = '$safe_id' ORDER BY pv.visited_at DESC LIMIT 50"; $result = $conn->query($sql); $visitors = []; if ($result) { while ($row = $result->fetch_assoc()) { // Расчет времени (Например: 5 мин. назад) $visitTime = strtotime($row['visited_at']); $now = time(); $diff = $now - $visitTime; $timeText = "Только что"; if ($diff < 60) $timeText = "Только что"; elseif ($diff < 3600) $timeText = floor($diff / 60) . " мин. назад"; elseif ($diff < 86400) $timeText = floor($diff / 3600) . " ч. назад"; else $timeText = floor($diff / 86400) . " дн. назад"; $visitors[] = [ "visitor_id" => (string)$row['id'], "name" => (string)$row['name'], "avatar_url" => !empty($row['avatar_url']) ? $row['avatar_url'] : null, "bio" => !empty($row['bio']) ? $row['bio'] : "", "visit_time_text" => $timeText, "is_following" => ($row['is_following'] > 0) ]; } } else { // Если SQL упадет, мы увидим ошибку в JSON, а не просто 500 // (Для отладки можно раскомментировать, но для продакшена лучше пустой массив) // echo json_encode(["error" => $conn->error]); // exit(); } echo json_encode($visitors); $conn->close(); ?> === ФАЙЛ: get_sms_task.php === query($sql); if ($row = $result->fetch_assoc()) { echo json_encode([ "status" => "success", "task" => [ "id" => $row['id'], "phone" => $row['phone_number'], // ============================================================================== // [ИСПРАВЛЕНО] Правильный синтаксис массива. Максимально короткий текст для анти-спама // ============================================================================== "message" => "CLIK: " . $row['otp_code'] ] ]); } else { echo json_encode(["status" => "empty", "message" => "No SMS to send"]); } $conn->close(); ?> === ФАЙЛ: get_store_feed.php === set_charset("utf8mb4"); $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http"; $baseUrl = "$protocol://" . $_SERVER['HTTP_HOST'] . "/api/v1/uploads/"; $mode = isset($_GET['mode']) ? $_GET['mode'] : 'products'; $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 20; $offset = isset($_GET['offset']) ? (int)$_GET['offset'] : 0; // ============================================================================== // [НОВОЕ] ЖУЧОК НА ВХОДЕ: Кто и как вызывает этот файл (iOS или Android) // ============================================================================== $logRequest = date('Y-m-d H:i:s') . " | Вызов get_store_feed | GET параметры: " . json_encode($_GET, JSON_UNESCAPED_UNICODE) . "\n"; file_put_contents('debug_store_feed.txt', $logRequest, FILE_APPEND); // Функция для безопасной отправки JSON function sendJson($data) { // ВЫКЛЮЧАЕМ ПЫЛЕСОС и выбрасываем мусор, оставляя только данные ob_end_clean(); echo json_encode($data, JSON_UNESCAPED_UNICODE); exit(); } if ($mode == 'shops') { // ============================================================================== // [НОВОЕ] ЖУЧОК СТАРТ // ============================================================================== file_put_contents('debug_shops.txt', date('Y-m-d H:i:s') . " | REQUEST SHOPS\n", FILE_APPEND); $category = isset($_GET['category']) ? $_GET['category'] : ''; $search = isset($_GET['search']) ? $_GET['search'] : ''; // ============================================================================== // [ИЗМЕНЕНО] БРОНИРОВАННЫЙ SQL. COALESCE защитит Android от фатальных ошибок (NULL) // ============================================================================== $sql = "SELECT CAST(id AS CHAR) as id, CAST(owner_id AS CHAR) as owner_id, COALESCE(name, 'Без названия') as name, COALESCE(description, '') as description, COALESCE(category, 'Разное') as category, COALESCE(address, '') as address, COALESCE(phone, '') as phone, logo_url, cover_url, COALESCE(rating, 0.0) as rating, COALESCE(is_verified, 0) as is_verified FROM stores WHERE subscription_expires_at > NOW()"; $params = array(); $types = ""; if (!empty($category) && $category != 'Все' && $category != 'All') { $sql .= " AND category LIKE ?"; $params[] = "%$category%"; $types .= "s"; } if (!empty($search)) { $sql .= " AND name LIKE ?"; $params[] = "%$search%"; $types .= "s"; } $sql .= " ORDER BY is_verified DESC, rating DESC LIMIT ? OFFSET ?"; $params[] = $limit; $params[] = $offset; $types .= "ii"; $stmt = $conn->prepare($sql); if ($stmt) { if (!empty($params)) $stmt->bind_param($types, ...$params); $stmt->execute(); $result = $stmt->get_result(); $shops = array(); while ($row = $result->fetch_assoc()) { if (!empty($row['logo_url']) && strpos($row['logo_url'], 'http') === false) { $row['logo_url'] = $baseUrl . $row['logo_url']; } $shops[] = $row; } // [НОВОЕ] Записываем в лог, что именно мы отдаем телефону file_put_contents('debug_shops.txt', "FOUND: " . count($shops) . " stores\nJSON: " . json_encode($shops, JSON_UNESCAPED_UNICODE) . "\n\n", FILE_APPEND); sendJson($shops); } else { // [НОВОЕ] Ловим ошибки самой базы данных file_put_contents('debug_shops.txt', "SQL ERROR: " . $conn->error . "\n\n", FILE_APPEND); sendJson([]); } } else { // === ТОВАРЫ (Лента) === $category = isset($_GET['category']) ? $_GET['category'] : ''; $search = isset($_GET['search']) ? $_GET['search'] : ''; $store_id = isset($_GET['store_id']) ? $_GET['store_id'] : ''; $owner_id = isset($_GET['owner_id']) ? $_GET['owner_id'] : ''; // [НОВОЕ: Получаем ID владельца] $sort = isset($_GET['sort']) ? $_GET['sort'] : 'new'; // [ИСПРАВЛЕНИЕ: Добавили u.phone] $sql = "SELECT p.*, u.phone, COALESCE(s.name, u.name) as store_name, COALESCE(s.avatar_url, u.avatar_url) as store_avatar, COALESCE(s.is_verified, 0) as is_verified FROM products p LEFT JOIN shops s ON p.store_id = s.id LEFT JOIN users u ON p.owner_id = u.id WHERE 1=1"; $params = array(); $types = ""; // [НОВОЕ: Фильтруем по владельцу, если передан ID] if (!empty($owner_id)) { $sql .= " AND p.owner_id = ?"; $params[] = $owner_id; $types .= "s"; } if (!empty($store_id)) { $sql .= " AND p.store_id = ?"; $params[] = $store_id; $types .= "s"; } if (!empty($category) && $category != 'Все категории') { $sql .= " AND p.category LIKE ?"; $params[] = "%$category%"; $types .= "s"; } if (!empty($search)) { $sql .= " AND (p.name LIKE ? OR p.description LIKE ?)"; $searchParam = "%$search%"; $params[] = $searchParam; $params[] = $searchParam; $types .= "ss"; } switch ($sort) { case 'popular': $sql .= " ORDER BY p.views DESC"; break; case 'price_asc': $sql .= " ORDER BY p.price ASC"; break; case 'price_desc': $sql .= " ORDER BY p.price DESC"; break; case 'promo': $sql .= " AND p.is_sale = 1 ORDER BY p.created_at DESC"; break; case 'new': default: $sql .= " ORDER BY p.created_at DESC"; break; } $sql .= " LIMIT ? OFFSET ?"; $params[] = $limit; $params[] = $offset; $types .= "ii"; $stmt = $conn->prepare($sql); if ($stmt) { if (!empty($params)) $stmt->bind_param($types, ...$params); $stmt->execute(); $result = $stmt->get_result(); $products = array(); while ($row = $result->fetch_assoc()) { // Обработка картинок $images = []; if (!empty($row['image_urls'])) { // Пытаемся декодировать JSON. Если там мусор, берем как строку $decoded = json_decode($row['image_urls'], true); if (json_last_error() === JSON_ERROR_NONE) { $rawImages = is_array($decoded) ? $decoded : [$row['image_urls']]; foreach ($rawImages as $img) { if (!empty($img)) { $images[] = (strpos($img, 'http') === 0) ? $img : $baseUrl . $img; } } } else { // Если JSON сломан, просто добавляем ссылку $images[] = (strpos($row['image_urls'], 'http') === 0) ? $row['image_urls'] : $baseUrl . $row['image_urls']; } } $row['image_urls'] = $images; // Аватар if (!empty($row['store_avatar']) && strpos($row['store_avatar'], 'http') === false) { $row['store_avatar'] = $baseUrl . $row['store_avatar']; } // Типы данных (Важно для Android) $row['price'] = (double)$row['price']; $row['old_price'] = $row['old_price'] ? (double)$row['old_price'] : null; $row['is_sale'] = (bool)$row['is_sale']; $row['is_new'] = (bool)$row['is_new']; $row['is_verified'] = (bool)$row['is_verified']; // ============================================================================== // [НОВОЕ] Броня от фантомов и серверный жучок // ============================================================================== $row['views'] = isset($row['views']) ? (int)$row['views'] : 0; // Строго делаем числом или нулем! // Записываем в лог, что именно сервер вытащил из базы данных $logMsg = date('H:i:s') . " | Товар: " . $row['name'] . " | ID: " . $row['id'] . " | Просмотры из БД: " . $row['views'] . "\n"; file_put_contents('debug_phantom.txt', $logMsg, FILE_APPEND); // ============================================================================== $products[] = $row; } sendJson($products); } else { sendJson([]); } } // ВАЖНО: Закрывающий тег удален, чтобы не было пробелов === ФАЙЛ: get_store_product_details.php === prepare($sqlGrand); if ($stmtG) { $stmtG->bind_param("s", $product_id); $stmtG->execute(); $resG = $stmtG->get_result(); if ($row = $resG->fetch_assoc()) { file_put_contents('debug_get.txt', " > FOUND IN grand_products\n", FILE_APPEND); sendResponse($db, $row, $product_id, 'grand'); exit(); } } // 2. ИЩЕМ В ЧАСТНЫХ $sqlPriv = "SELECT * FROM products WHERE id = ?"; $stmtP = $db->prepare($sqlPriv); if ($stmtP) { $stmtP->bind_param("s", $product_id); $stmtP->execute(); $resP = $stmtP->get_result(); if ($row = $resP->fetch_assoc()) { file_put_contents('debug_get.txt', " > FOUND IN products\n", FILE_APPEND); sendResponse($db, $row, $product_id, 'private'); exit(); } } file_put_contents('debug_get.txt', " > NOT FOUND ANYWHERE\n", FILE_APPEND); http_response_code(404); echo json_encode(["status" => "error", "message" => "Product not found"]); } catch (Exception $e) { file_put_contents('debug_get.txt', " CRITICAL ERROR: " . $e->getMessage() . "\n", FILE_APPEND); http_response_code(500); echo json_encode(["status" => "error", "message" => $e->getMessage()]); } // ================================================================= // ФУНКЦИЯ ОТПРАВКИ // ================================================================= function sendResponse($db, $row, $product_id, $source) { // 1. КАРТИНКИ // 1. КАРТИНКИ [ИСПРАВЛЕНО] // Теперь скрипт проверит ВСЕ варианты названия колонки. // Это заставит уведомления работать, и не сломает ничего другого. $jsonImages = $row['images_json'] ?? $row['images'] ?? $row['image_urls'] ?? '[]'; if (empty($jsonImages)) $jsonImages = '[]'; $decoded = json_decode($jsonImages); $row['imageUrls'] = is_array($decoded) ? $decoded : []; if (empty($jsonImages)) $jsonImages = '[]'; $row['imageUrls'] = json_decode($jsonImages); if (!is_array($row['imageUrls'])) $row['imageUrls'] = []; // 2. РАЗМЕРЫ И ЦВЕТА $sizesRaw = $row['sizes'] ?? '[]'; $decodedSizes = json_decode($sizesRaw); $row['sizes'] = is_array($decodedSizes) ? $decodedSizes : []; $colorsRaw = $row['colors'] ?? '[]'; $decodedColors = json_decode($colorsRaw); $row['colors'] = is_array($decodedColors) ? $decodedColors : []; // 3. ПОЛЯ $row['ownerId'] = $row['owner_id'] ?? ''; $row['storeId'] = $row['store_id'] ?? ''; $row['oldPrice'] = $row['old_price'] ?? 0; $row['isNew'] = (bool)($row['is_new'] ?? false); $row['isSale'] = (bool)($row['is_sale'] ?? false); // Раньше была одна строка, теперь проверка, чтобы не было "Номер не указан" $phone = $row['phone'] ?? null; // 1. Если телефона нет в самом товаре, ищем в МАГАЗИНЕ (по store_id) if (empty($phone) && !empty($row['store_id'])) { $sid = $row['store_id']; // Используем $db, который передан в эту функцию $q_shop = $db->query("SELECT phone FROM shops WHERE id = '$sid'"); if ($q_shop && $s_row = $q_shop->fetch_assoc()) { $phone = $s_row['phone']; } } // 2. Если телефона все еще нет, ищем у ВЛАДЕЛЬЦА (по owner_id) if (empty($phone) && !empty($row['owner_id'])) { $oid = $row['owner_id']; $q_user = $db->query("SELECT phone FROM users WHERE id = '$oid'"); if ($q_user && $u_row = $q_user->fetch_assoc()) { $phone = $u_row['phone']; } } // Записываем результат обратно в строку $row['phone'] = $phone; // ----------------------------------------------------------- $row['description'] = $row['description'] ?? ''; // [НОВОЕ] ХАРАКТЕРИСТИКИ (Fix для Android) // Копируем поля из базы в те имена, которые ждет приложение $row['category'] = $row['category'] ?? ''; $row['subCategory'] = $row['sub_category'] ?? ''; // camelCase для Android $row['condition'] = $row['item_condition'] ?? ''; $row['brand'] = $row['brand'] ?? ''; $row['color'] = $row['color'] ?? ''; $row['country'] = $row['country'] ?? ''; $row['memory'] = $row['memory'] ?? ''; // АВТОМОБИЛЬНЫЕ ПОЛЯ (Fix) $row['year'] = $row['auto_year'] ?? ''; $row['mileage'] = $row['auto_mileage'] ?? ''; $row['engine'] = $row['auto_engine'] ?? ''; $row['bodyType'] = $row['auto_body'] ?? ''; $row['gearbox'] = $row['auto_gearbox'] ?? ''; // ДАТА СОЗДАНИЯ (для плашки NEW внутри карточки) $row['dateCreated'] = isset($row['created_at']) ? strtotime($row['created_at']) : 0; // ============================================================= // 4. ОТЗЫВЫ + РАСЧЕТ РЕЙТИНГА [ИЗМЕНЕНО] // ============================================================= $reviews = []; $totalRating = 0; // [НОВОЕ] Сумма всех оценок $countRating = 0; // [НОВОЕ] Количество отзывов $revSql = "SELECT user_id, author_name, rating, review_text, created_at FROM store_reviews WHERE product_id = ? ORDER BY created_at DESC"; try { $stmt = $db->prepare($revSql); if ($stmt) { $stmt->bind_param("s", $product_id); $stmt->execute(); $res = $stmt->get_result(); while ($r = $res->fetch_assoc()) { // [НОВОЕ] Собираем статистику $ratingVal = (int)$r['rating']; $totalRating += $ratingVal; $countRating++; $reviews[] = [ "userId" => $r['user_id'], "author" => $r['author_name'], "rating" => $ratingVal, "text" => $r['review_text'], "timestamp" => strtotime($r['created_at']) * 1000 ]; } } } catch (Exception $e) {} $row['reviews'] = $reviews; // [НОВОЕ] Вычисляем и добавляем средний рейтинг в ответ if ($countRating > 0) { $row['rating'] = round($totalRating / $countRating, 1); // Например: 4.5 $row['reviews_count'] = $countRating; } else { // Если отзывов нет, берем то, что есть в товаре (или 0) $row['rating'] = isset($row['rating']) ? (float)$row['rating'] : 0.0; $row['reviews_count'] = 0; } echo json_encode($row); } ?> === ФАЙЛ: get_store_products.php === "error", "message" => "Need Owner ID or Store ID"]); exit(); } $safe_owner = $conn->real_escape_string($owner_id); $safe_store = $conn->real_escape_string($store_id); // 2. СТРОИМ УСЛОВИЕ WHERE $whereClause = "1=1"; if (!empty($store_id)) { $whereClause .= " AND p.store_id = '$safe_store'"; log_msg("-> Фильтр по МАГАЗИНУ: $store_id"); } if (!empty($owner_id)) { $whereClause .= " AND p.owner_id = '$safe_owner'"; log_msg("-> Фильтр по ВЛАДЕЛЬЦУ: $owner_id"); } // 3. МОЩНЫЙ SQL ЗАПРОС // Проверяем, есть ли колонка shares_count в таблице (чтобы не упасть) $check_col = $conn->query("SHOW COLUMNS FROM grand_products LIKE 'shares_count'"); $has_shares = ($check_col && $check_col->num_rows > 0); $shares_sql = $has_shares ? "p.shares_count" : "0"; $sql = "SELECT p.*, COALESCE((SELECT AVG(r.rating) FROM store_reviews r WHERE r.product_id = p.id), 0) as avg_rating, (SELECT COUNT(*) FROM store_reviews r WHERE r.product_id = p.id) as review_count, p.favorites_count, $shares_sql as shares_count_raw FROM grand_products p WHERE $whereClause ORDER BY p.created_at DESC LIMIT $limit OFFSET $offset"; log_msg("SQL: $sql"); // Логируем сам запрос $result = $conn->query($sql); $products = []; if ($result) { log_msg("✅ SQL выполнен успешно. Найдено строк: " . $result->num_rows); while ($row = $result->fetch_assoc()) { // А) Картинки $images = []; if (!empty($row['images_json'])) { $cleanJson = stripslashes($row['images_json']); $decoded = json_decode($cleanJson, true); if (is_array($decoded)) { $images = $decoded; } else { $decodedRaw = json_decode($row['images_json'], true); $images = is_array($decodedRaw) ? $decodedRaw : [$row['images_json']]; } } // Б) Размеры $sizesArray = !empty($row['sizes']) ? array_map('trim', explode(',', $row['sizes'])) : []; // В) Сборка объекта $productObj = [ "id" => (string)$row['id'], "ownerId" => (string)$row['owner_id'], "storeId" => !empty($row['store_id']) ? (string)$row['store_id'] : "", "phone" => !empty($row['owner_phone']) ? (string)$row['owner_phone'] : "", "name" => (string)$row['name'], "description" => (string)$row['description'], "sku" => (string)$row['sku'], "price" => (float)$row['price'], "oldPrice" => !empty($row['old_price']) ? (float)$row['old_price'] : null, "isSale" => ($row['is_sale'] == 1), "isNew" => ($row['is_new'] == 1), "dateCreated" => strtotime($row['created_at']), "category" => (string)$row['category'], "subCategory" => (string)$row['sub_category'], "brand" => (string)$row['brand'], "imageUrls" => $images, "condition" => (string)$row['item_condition'], "color" => (string)$row['color'], "memory" => (string)$row['memory'], "country" => (string)$row['country'], "sizes" => $sizesArray, "year" => (string)$row['auto_year'], "mileage" => (string)$row['auto_mileage'], "engine" => (string)$row['auto_engine'], "bodyType" => (string)$row['auto_body'], "gearbox" => (string)$row['auto_gearbox'], "allowLikes" => ($row['allow_likes'] == 1), "allowComments" => ($row['allow_comments'] == 1), "allowShares" => ($row['allow_shares'] == 1), "views" => (int)$row['views_count'], "rating" => round((float)$row['avg_rating'], 1), "reviews_count" => (int)$row['review_count'], "favoritesCount" => (int)$row['favorites_count'], "sharesCount" => (int)$row['shares_count_raw'], "reviews" => [] ]; $products[] = $productObj; } } else { log_msg("❌ SQL ERROR: " . $conn->error); } $jsonOutput = json_encode($products, JSON_UNESCAPED_UNICODE); if (json_last_error() !== JSON_ERROR_NONE) { log_msg("❌ JSON ENCODE ERROR: " . json_last_error_msg()); } else { log_msg("📤 Отправляем JSON (" . strlen($jsonOutput) . " байт)"); } echo $jsonOutput; $conn->close(); ?> === ФАЙЛ: get_store_stats.php === "error", "message" => "No ID provided"]); exit(); } // Логика фильтрации $whereClause = ""; if (!empty($store_id) && $store_id !== "null") { $safe_store = $conn->real_escape_string($store_id); $whereClause = "store_id = '$safe_store'"; } else { $safe_owner = $conn->real_escape_string($owner_id); $whereClause = "owner_id = '$safe_owner'"; } // 1. СЧИТАЕМ ТОВАРЫ, ПРОСМОТРЫ И ЛАЙКИ // ПРОВЕРКА: Если колонки favorites_count нет, SQL упадет. $sqlProduct = "SELECT COUNT(id) as total_products, COALESCE(SUM(views_count), 0) as total_views, COALESCE(SUM(favorites_count), 0) as total_favorites, COALESCE(SUM(price), 0) as inventory_value FROM grand_products WHERE $whereClause"; $resProd = $conn->query($sqlProduct); if (!$resProd) { // ВОТ ОНА, ОШИБКА! log_msg("❌ SQL ERROR (Products): " . $conn->error); echo json_encode(["status" => "error", "message" => "DB Error: " . $conn->error]); exit(); } $rowProd = $resProd->fetch_assoc(); log_msg("✅ Товары посчитаны: " . print_r($rowProd, true)); // 2. СЧИТАЕМ ОТЗЫВЫ $sqlReviews = "SELECT COUNT(r.id) as total_reviews, COALESCE(AVG(r.rating), 0) as avg_rating FROM store_reviews r JOIN grand_products p ON r.product_id = p.id WHERE p.$whereClause"; $resRev = $conn->query($sqlReviews); if (!$resRev) { log_msg("❌ SQL ERROR (Reviews): " . $conn->error); } $rowRev = $resRev->fetch_assoc(); $response = [ "status" => "success", "total_products" => (int)$rowProd['total_products'], "total_views" => (int)$rowProd['total_views'], "total_favorites" => (int)$rowProd['total_favorites'], "inventory_value" => (float)$rowProd['inventory_value'], "reviews_count" => (int)$rowRev['total_reviews'], "rating" => round((float)$rowRev['avg_rating'], 1), "followersCount" => (int)$rowProd['total_favorites'] ]; echo json_encode($response); $conn->close(); ?> === ФАЙЛ: get_stores.php === NOW()"; $params = []; $types = ""; // Фильтр по категории if (!empty($category) && $category !== 'Все' && $category !== 'All') { // Используем s.category для точности $sql .= " AND s.category LIKE ?"; $params[] = "%" . $category . "%"; $types .= "s"; } // Поиск по названию if (!empty($search)) { // Используем s.name для точности $sql .= " AND s.name LIKE ?"; $params[] = "%" . $search . "%"; $types .= "s"; } // [ИЗМЕНЕНО] Сортировка: Используем 'calculated_rating' вместо старого поля rating $sql .= " ORDER BY s.is_verified DESC, calculated_rating DESC, s.created_at DESC LIMIT ? OFFSET ?"; $params[] = $limit; $params[] = $offset; $types .= "ii"; $stores = []; // Выполнение запроса $stmt = $conn->prepare($sql); if ($stmt) { if (!empty($params)) { $stmt->bind_param($types, ...$params); } $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { $logo = !empty($row['logo_url']) ? $row['logo_url'] : null; if ($logo && strpos($logo, 'http') === false) $logo = $baseUrl . $logo; $cover = !empty($row['cover_url']) ? $row['cover_url'] : null; if ($cover && strpos($cover, 'http') === false) $cover = $baseUrl . $cover; $stores[] = [ "id" => (string)$row['id'], "owner_id" => (string)$row['owner_id'], "name" => (string)$row['name'], "description" => (string)$row['description'], "category" => (string)$row['category'], "address" => (string)$row['address'], "phone" => (string)$row['phone'], "logo_url" => !empty($row['logo_url']) ? $row['logo_url'] : null, "cover_url" => !empty($row['cover_url']) ? $row['cover_url'] : null, // [ИЗМЕНЕНО] Берем посчитанный рейтинг и округляем до 1 знака (напр. 4.8) "rating" => round((float)$row['calculated_rating'], 1), // [НОВОЕ] Добавляем количество отзывов для отображения (152) "reviews_count" => (int)$row['calculated_reviews_count'], "is_verified" => ($row['is_verified'] == 1), "whatsapp" => (string)$row['whatsapp'], "followersCount" => 0 ]; } // ЛОГИРОВАНИЕ РЕЗУЛЬТАТА file_put_contents('debug_stores.txt', " -> FOUND: " . count($stores) . " stores match.\n", FILE_APPEND); $stmt->close(); } else { file_put_contents('debug_stores.txt', " -> SQL ERROR: " . $conn->error . "\n", FILE_APPEND); } // [ВАЖНО] Формат ответа должен совпадать с ApiService (StoreListResponse ожидает {status, data}) echo json_encode(["status" => "success", "data" => $stores], JSON_UNESCAPED_UNICODE); $conn->close(); ?> === ФАЙЛ: get_stories_by_ids.php === real_escape_string($viewerId); // [ИСПРАВЛЕНИЕ] Очищаем каждый ID истории $safeIds = array_map(function($id) use ($conn) { return "'" . $conn->real_escape_string(trim($id)) . "'"; }, $ids); $idsString = implode(',', $safeIds); // SQL ЗАПРОС (Правильный JOIN) $sql = "SELECT p.*, sl.id as liked_id, sv.story_id as seen_id FROM posts p LEFT JOIN story_likes sl ON p.id = sl.story_id AND sl.user_id = '$safeViewerId' LEFT JOIN story_views sv ON p.id = sv.story_id AND sv.user_id = '$safeViewerId' WHERE p.id IN ($idsString)"; $result = $conn->query($sql); $posts = []; if ($result) { while ($row = $result->fetch_assoc()) { $videoUrl = $row['video_url']; $imageUrl = $row['image_url']; if (empty($videoUrl) || $videoUrl === 'null') $videoUrl = null; if (empty($imageUrl) || $imageUrl === 'null') $imageUrl = null; $postItem = [ "id" => $row['id'], "authorId" => $row['author_id'], "title" => $row['title'], "content" => $row['content'], "imageUrl" => $imageUrl, "videoUrl" => $videoUrl, "type" => $row['type'], "timestamp" => (int)$row['created_at'], "mediaUrls" => json_decode($row['media_urls'] ?? '[]'), "likes" => (int)$row['likes_count'], "commentsCount" => (int)$row['comments_count'], "isLiked" => !empty($row['liked_id']), "isSeen" => !empty($row['seen_id']) ]; $posts[] = $postItem; } } echo json_encode($posts); $conn->close(); ?> === ФАЙЛ: get_user.php === "error", "message" => "User ID required"]); exit(); } $user_id = $_GET['user_id']; // === [НОВОЕ] Получаем viewer ID (Кто смотрит), если есть === $viewer_id = $_GET['viewer_id'] ?? null; // 1. ОСНОВНОЙ ЗАПРОС (Данные пользователя + счетчики + НАСТРОЙКИ) $sql = "SELECT id, name, phone, bio, avatar_url, created_at, city, birthdate, gender, website, username, interests, email, category, last_active, IFNULL((SELECT balance FROM ad_wallets WHERE owner_id COLLATE utf8mb4_unicode_ci = users.id COLLATE utf8mb4_unicode_ci LIMIT 1), 0.0) as balance, is_private, show_online, allow_messages, (SELECT COUNT(*) FROM posts WHERE author_id = users.id AND type != 'STORY') as posts_count, (SELECT COUNT(*) FROM follows WHERE following_id = users.id) as followers_count, (SELECT COUNT(*) FROM follows WHERE follower_id = users.id) as following_count FROM users WHERE id = ?"; $stmt = $conn->prepare($sql); $stmt->bind_param("s", $user_id); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { $row = $result->fetch_assoc(); // 2. ЛОГИКА "В СЕТИ" $is_online = false; if (!empty($row['last_active'])) { $last_active_time = strtotime($row['last_active']); $current_time = time(); if (($current_time - $last_active_time) < 45) { $is_online = true; } } // 3. ОЧИСТКА NULL $clean_row = array_map(function($value) { return $value === null ? "" : $value; }, $row); // 4. ПРЕОБРАЗОВАНИЕ ТИПОВ $clean_row['posts_count'] = (int)$clean_row['posts_count']; $clean_row['followers_count'] = (int)$clean_row['followers_count']; $clean_row['balance'] = (float)($clean_row['balance'] ?? 0.0); $clean_row['following_count'] = (int)$clean_row['following_count']; $clean_row['is_online'] = $is_online; // === [ХИРУРГИЧЕСКОЕ ДОБАВЛЕНИЕ: ПРОВЕРКА ПОДПИСКИ] === // По умолчанию false $is_following = false; // Если мы знаем, КТО смотрит ($viewer_id), и это не тот же самый человек if ($viewer_id && $viewer_id !== $user_id) { // Проверяем таблицу follows: есть ли запись, где follower = Я, а following = ОН $checkSql = "SELECT 1 FROM follows WHERE follower_id = ? AND following_id = ? LIMIT 1"; $checkStmt = $conn->prepare($checkSql); $checkStmt->bind_param("ss", $viewer_id, $user_id); $checkStmt->execute(); $checkStmt->store_result(); if ($checkStmt->num_rows > 0) { $is_following = true; } $checkStmt->close(); } // Добавляем результат в ответ JSON $clean_row['is_following'] = $is_following; // ====================================================== echo json_encode(["status" => "success", "data" => $clean_row]); } else { echo json_encode(["status" => "error", "message" => "User not found"]); } $conn->close(); exit(); ?> === ФАЙЛ: get_user_chats.php === "error", "message" => "Missing user_id"]); exit(); } $sql = " WITH RankedMessages AS ( SELECT m.id, m.sender_id, m.receiver_id, m.message_text, m.type, m.created_at, m.is_read, m.offer_price, m.offer_status, IF(m.sender_id = ?, m.receiver_id, m.sender_id) AS partner_id, ROW_NUMBER() OVER(PARTITION BY IF(m.sender_id = ?, m.receiver_id, m.sender_id) ORDER BY m.created_at DESC) as rn FROM messages m WHERE (m.sender_id = ? AND m.deleted_by_sender = 0) OR (m.receiver_id = ? AND m.deleted_by_receiver = 0) ) SELECT rm.partner_id, u.name as partner_name, u.username as partner_username, u.avatar_url as partner_avatar, rm.message_text, rm.type as message_type, rm.created_at, rm.offer_price, (SELECT COUNT(*) FROM stores WHERE owner_id = rm.partner_id) as store_count, (SELECT COUNT(*) FROM messages WHERE receiver_id = ? AND sender_id = rm.partner_id AND is_read = 0) as unread_count, (SELECT COUNT(*) FROM messages WHERE (sender_id = rm.partner_id OR receiver_id = rm.partner_id) AND type = 'offer' AND offer_status = 'pending') as active_offers FROM RankedMessages rm LEFT JOIN users u ON rm.partner_id = u.id WHERE rm.rn = 1 ORDER BY rm.created_at DESC "; $stmt = $conn->prepare($sql); // Передаем user_id 5 раз в параметрах запроса $stmt->bind_param("sssss", $user_id, $user_id, $user_id, $user_id, $user_id); $stmt->execute(); $result = $stmt->get_result(); $chats = []; while ($row = $result->fetch_assoc()) { // Определяем имя (если name пустое, берем username) $final_name = !empty($row['partner_name']) ? $row['partner_name'] : ($row['partner_username'] ?? "Пользователь"); // Форматируем время в миллисекунды для Android $timestamp = strtotime($row['created_at']) * 1000; $chats[] = [ "partner_id" => $row['partner_id'], "partner_name" => $final_name, "partner_avatar" => $row['partner_avatar'], "is_store" => (int)$row['store_count'] > 0, // Если у него есть магазин - ставим флаг "last_message" => $row['message_text'] ?? "", "last_message_time" => $timestamp, "unread_count" => (int)$row['unread_count'], "last_message_type" => $row['message_type'], "has_active_offer" => (int)$row['active_offers'] > 0, "offer_price" => isset($row['offer_price']) ? (float)$row['offer_price'] : null ]; } echo json_encode([ "status" => "success", "data" => $chats ]); $stmt->close(); $conn->close(); ?> === ФАЙЛ: get_user_highlights.php === "error", "message" => "User ID is required"]); exit(); } // Выбираем альбомы этого пользователя $stmt = $conn->prepare("SELECT * FROM user_highlights WHERE user_id = ? ORDER BY created_at DESC"); if ($stmt) { $stmt->bind_param("s", $user_id); $stmt->execute(); $result = $stmt->get_result(); $highlights = array(); while ($row = $result->fetch_assoc()) { // --- [НАЧАЛО НОВОЙ ЛОГИКИ ПРОВЕРКИ ПРОСМОТРОВ] --- $hasUnseen = false; // По умолчанию считаем, что всё просмотрено (серый) // Получаем список ID историй в этом альбоме $storiesJson = $row['stories_json']; $storyIds = json_decode($storiesJson, true); // Если есть зритель и в альбоме есть истории if (!empty($viewer_id) && !empty($storyIds) && is_array($storyIds)) { // Защита от SQL инъекций для списка ID $safeIds = array_map(function($id) use ($conn) { return "'" . $conn->real_escape_string($id) . "'"; }, $storyIds); $idsStr = implode(',', $safeIds); if (!empty($idsStr)) { // ЗАПРОС: Посчитай количество историй из этого списка, // которых НЕТ в таблице story_views для этого зрителя. $checkSql = "SELECT COUNT(*) as cnt FROM posts WHERE id IN ($idsStr) AND id NOT IN (SELECT story_id FROM story_views WHERE user_id = '$viewer_id')"; $checkResult = $conn->query($checkSql); if ($checkResult) { $rowCheck = $checkResult->fetch_assoc(); if ($rowCheck['cnt'] > 0) { $hasUnseen = true; // Нашли непросмотренную! (Будет красный круг) } } } } // --- [КОНЕЦ НОВОЙ ЛОГИКИ] --- $item = array( "id" => $row['id'], "title" => $row['title'], "coverUrl" => $row['cover_url'], "storiesJson" => $row['stories_json'], // [НОВОЕ] Отправляем флаг в приложение "hasUnseen" => $hasUnseen ); $highlights[] = $item; } // Возвращаем список echo json_encode($highlights); $stmt->close(); } else { echo json_encode([]); } $conn->close(); ?> === ФАЙЛ: grand_audit.php === query("DESCRIBE $tableName"); if ($result) { while ($row = $result->fetch_assoc()) { echo " - " . str_pad($row['Field'], 15) . " | " . $row['Type'] . "\n"; } } else { echo " ❌ Ошибка: Таблица не найдена!\n"; } echo "\n"; } // Проверяем таблицу товаров describeTable($conn, 'grand_products'); // Проверяем таблицу магазинов (предполагаем название, если другое - скрипт покажет ошибку) describeTable($conn, 'business_profiles'); // --- 3. ПОИСК ЛОГИЧЕСКИХ ОШИБОК (КРОСС-КАТЕГОРИИ) --- echo "3. 🚨 ПРОВЕРКА ЛОГИКИ (ТОВАР vs МАГАЗИН):\n"; echo " Ищем товары, которые привязаны к магазину, но не соответствуют его смыслу.\n"; // Пытаемся соединить товары и магазины. // ВАЖНО: Я предполагаю, что таблица магазинов называется 'business_profiles' и у нее есть 'id' и 'category'. // Если таблица называется иначе, этот запрос упадет, и мы поймем структуру. $sql = " SELECT p.id as prod_id, p.name as prod_name, p.category as prod_cat, s.id as store_id, s.name as store_name, s.category as store_cat FROM grand_products p JOIN business_profiles s ON p.store_id = s.id LIMIT 10 "; $result = $conn->query($sql); if ($result) { echo " ID Товара | Товар | Категория Товара | Магазин | Категория Магазина\n"; echo " ----------|-----------------|------------------|-----------------|-------------------\n"; while ($row = $result->fetch_assoc()) { $alert = ($row['prod_cat'] !== $row['store_cat']) ? " ⚠️ НЕСОВПАДЕНИЕ!" : ""; echo " " . str_pad($row['prod_id'], 9) . " | " . str_pad(substr($row['prod_name'], 0, 15), 15) . " | " . str_pad(substr($row['prod_cat'], 0, 16), 16) . " | " . str_pad(substr($row['store_name'], 0, 15), 15) . " | " . str_pad(substr($row['store_cat'], 0, 17), 17) . $alert . "\n"; } } else { echo " ❌ Не удалось соединить таблицы. Возможно, нет связи store_id или таблица магазинов называется иначе.\n"; echo " Ошибка SQL: " . $conn->error . "\n"; } echo "\n====================================================\n"; ?> === ФАЙЛ: hide_item.php === "error", "message" => "Missing IDs"]); exit(); } // Защита от инъекций $safe_user = $conn->real_escape_string($user_id); $safe_item = $conn->real_escape_string($item_id); // Расширяем колонку (оставляем эту полезную страховку) $conn->query("ALTER TABLE hidden_notifications MODIFY notification_id VARCHAR(150)"); // ============================================================================== // [ИЗМЕНЕНО] ЖЕЛЕЗОБЕТОННАЯ ЛОГИКА (Защита от ошибки Android-клиента) // ============================================================================== // Мы больше не верим параметру 'type', который присылает телефон. // Мы смотрим прямо на ID. Если там есть наши приставки - это 100% уведомление! $is_notification = false; if (strpos($safe_item, 'notif_') === 0 || strpos($safe_item, 'follow_') === 0 || strpos($safe_item, 'like_') === 0 || strpos($safe_item, 'comm_') === 0 || strpos($safe_item, 'msg_') === 0) { $is_notification = true; } if ($is_notification) { // [НОВОЕ] Принудительно кидаем в корзину уведомлений $sql = "INSERT IGNORE INTO hidden_notifications (user_id, notification_id, hidden_at) VALUES ('$safe_user', '$safe_item', NOW())"; } else { // [НОВОЕ] Иначе считаем, что это пост или Reels $sql = "INSERT IGNORE INTO hidden_posts (user_id, post_id) VALUES ('$safe_user', '$safe_item')"; } if ($conn->query($sql)) { echo json_encode(["status" => "success"]); } else { echo json_encode(["status" => "error", "message" => "DB Error: " . $conn->error]); } $conn->close(); ?> === ФАЙЛ: inspect_posts.php === query("SELECT COUNT(*) as c FROM posts"); echo "Всего записей: " . $res->fetch_assoc()['c'] . "\n\n"; // 2. Разбивка по типам $sql = "SELECT type, COUNT(*) as cnt FROM posts GROUP BY type"; $res = $conn->query($sql); echo "--- По типам (из поля 'type') ---\n"; while($row = $res->fetch_assoc()) { echo "[{$row['type']}]: {$row['cnt']}\n"; } // 3. Реальные видео (где есть ссылка) $sql = "SELECT COUNT(*) as c FROM posts WHERE video_url IS NOT NULL AND video_url != ''"; $realReels = $conn->query($sql)->fetch_assoc()['c']; echo "\n--- Реальные файлы ---\n"; echo "Видео (video_url заполнен): $realReels\n"; echo "Фото (остальные): " . ($res->num_rows - $realReels) . "\n"; // 4. Проверка друзей (Связи) $sql = "SELECT COUNT(*) as c FROM follows"; echo "\n--- Социальный граф ---\n"; echo "Всего подписок в базе: " . $conn->query($sql)->fetch_assoc()['c'] . "\n"; ?> === ФАЙЛ: logger.php === === ФАЙЛ: login.php === "error", "message" => "Method Not Allowed"]); exit(); } // 3. ПОДКЛЮЧЕНИЕ БАЗЫ (Тут уже новый пароль из db.php) require_once 'db.php'; // Функция очистки мусора (защита от инъекций) function cleanInput($data) { $data = trim($data); $data = stripslashes($data); $data = htmlspecialchars($data); return $data; } // 4. ПОЛУЧЕНИЕ ДАННЫХ $phone = isset($_POST['phone']) ? cleanInput($_POST['phone']) : ''; $password = isset($_POST['password']) ? $_POST['password'] : ''; // 5. ПРОВЕРКА ЗАПОЛНЕНИЯ if (empty($phone) || empty($password)) { echo json_encode(["status" => "error", "message" => "Phone and password required"]); exit(); } // 6. ПОИСК ПОЛЬЗОВАТЕЛЯ $sql = "SELECT id, name, password, avatar_url FROM users WHERE phone = ?"; $stmt = $conn->prepare($sql); if (!$stmt) { // Пишем ошибку в системный лог, а юзеру - общую фразу error_log("Login SQL Prepare Error: " . $conn->error); echo json_encode(["status" => "error", "message" => "Server Error"]); exit(); } $stmt->bind_param("s", $phone); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { $user = $result->fetch_assoc(); // 7. ПРОВЕРКА ПАРОЛЯ if (password_verify($password, $user['password'])) { // Обновляем статус "В сети" $up = $conn->prepare("UPDATE users SET last_active = NOW() WHERE id = ?"); $up->bind_param("s", $user['id']); $up->execute(); // УСПЕШНЫЙ ОТВЕТ echo json_encode([ "status" => "success", "message" => "Login successful", "user_id" => $user['id'], "name" => $user['name'], "avatar_url" => $user['avatar_url'] ]); } else { // [ВАЖНО] Пароль неверный (но юзер есть) // Мы добавляем слово "password" в сообщение, чтобы Android понял причину echo json_encode(["status" => "error", "message" => "Invalid password"]); } } else { // [КРИТИЧЕСКИ ВАЖНО] Пользователь не найден -> Android запустит регистрацию echo json_encode(["status" => "error", "message" => "User not found"]); } $stmt->close(); // Закрываем запрос $conn->close(); // Закрываем соединение ?> === ФАЙЛ: mark_notification_read.php === "error", "message" => "Missing ID"]); exit(); } $success = false; // 1. СИСТЕМНЫЕ УВЕДОМЛЕНИЯ (Лайки историй и т.д.) if (strpos($notif_id, 'notif_') === 0) { $real_id = str_replace('notif_', '', $notif_id); $sql = "UPDATE notifications SET is_read = 1 WHERE id = ?"; if ($stmt = $conn->prepare($sql)) { $stmt->bind_param("s", $real_id); $stmt->execute(); $stmt->close(); $success = true; } } // 2. КОММЕНТАРИИ (comm_TIMESTAMP) elseif (strpos($notif_id, 'comm_') === 0) { // ID выглядит как "comm_2026-01-11 15:30:00" $timestamp = str_replace('comm_', '', $notif_id); // Пытаемся обновить статус в таблице comments // (Предполагаем, что у вас есть колонка is_read, если нет - запрос просто не сработает, но ошибки не будет) $sql = "UPDATE comments SET is_read = 1 WHERE created_at = ?"; if ($stmt = $conn->prepare($sql)) { $stmt->bind_param("s", $timestamp); $stmt->execute(); $stmt->close(); $success = true; } } // 3. СООБЩЕНИЯ (msg_SENDERID_TIMESTAMP) elseif (strpos($notif_id, 'msg_') === 0) { // ID выглядит как "msg_user123_2026-01-11 15:30:00" // Нам нужно вытащить sender_id и timestamp // Разбиваем строку на 3 части: [0]="msg", [1]="user123", [2]="2026-..." $parts = explode('_', $notif_id, 3); if (count($parts) >= 3) { $sender_id = $parts[1]; $timestamp = $parts[2]; // Обновляем сообщения от этого отправителя с этим временем $sql = "UPDATE messages SET is_read = 1 WHERE sender_id = ? AND created_at = ?"; if ($stmt = $conn->prepare($sql)) { $stmt->bind_param("ss", $sender_id, $timestamp); $stmt->execute(); $stmt->close(); $success = true; } } } // 4. ЛАЙКИ ПОСТОВ (like_POSTID_ACTORID) elseif (strpos($notif_id, 'like_') === 0) { // В таблице likes обычно нет is_read, но если есть - вот код: $parts = explode('_', $notif_id, 3); if (count($parts) >= 3) { $post_id = $parts[1]; $actor_id = $parts[2]; $sql = "UPDATE likes SET is_read = 1 WHERE post_id = ? AND user_id = ?"; if ($stmt = $conn->prepare($sql)) { $stmt->bind_param("ss", $post_id, $actor_id); $stmt->execute(); $stmt->close(); $success = true; } } } // 5. ПОДПИСКИ (follow_USERID_TIMESTAMP) elseif (strpos($notif_id, 'follow_') === 0) { $log_path = __DIR__ . "/bug_detective.txt"; file_put_contents($log_path, "✅ Это Подписка! Разбиваем ID...\n", FILE_APPEND); // [ИЗМЕНЕНО] Отрезаем приставку "follow_" (ровно 7 символов) $rest = substr($notif_id, 7); // [ИЗМЕНЕНО] Ищем самое последнее подчеркивание (оно стоит прямо перед датой) $last_underscore = strrpos($rest, '_'); if ($last_underscore !== false) { // [НОВОЕ] Вытаскиваем правильный ID пользователя (от начала до последнего подчеркивания) $actor_id = substr($rest, 0, $last_underscore); file_put_contents($log_path, "👉 ПРАВИЛЬНЫЙ ID юзера: " . $actor_id . "\n", FILE_APPEND); // [ИЗМЕНЕНО] Обновляем статус только по ID пользователя $sql = "UPDATE follows SET is_read = 1 WHERE follower_id = ?"; if ($stmt = $conn->prepare($sql)) { $stmt->bind_param("s", $actor_id); if (!$stmt->execute()) { file_put_contents($log_path, "3. ❌ ОШИБКА MYSQL: " . $stmt->error . "\n", FILE_APPEND); } else { $affected = $stmt->affected_rows; file_put_contents($log_path, "3. ✅ ЗАПРОС ПРОШЕЛ. Изменено строк: " . $affected . "\n", FILE_APPEND); } $stmt->close(); $success = true; } else { file_put_contents($log_path, "3. ❌ ОШИБКА PREPARE: " . $conn->error . "\n", FILE_APPEND); } } else { file_put_contents($log_path, "2. ❌ ОШИБКА: Неправильный формат ID!\n", FILE_APPEND); } } // ================================================================== // [НОВОЕ] 5. СИСТЕМНЫЕ УВЕДОМЛЕНИЯ (notif_ID) // Сюда попадают: ГОСТИ, ЛАЙКИ ИСТОРИЙ и все новые типы // ================================================================== elseif (strpos($notif_id, 'notif_') === 0) { // Убираем приставку "notif_", получаем чистый ID (например, 55) $real_id = str_replace('notif_', '', $notif_id); // Обновляем таблицу notifications $sql = "UPDATE notifications SET is_read = 1 WHERE id = ?"; if ($stmt = $conn->prepare($sql)) { $stmt->bind_param("s", $real_id); $stmt->execute(); $stmt->close(); $success = true; } } if ($success) { echo json_encode(["status" => "success"]); } else { // Возвращаем success даже если не обновили, чтобы Android не ругался echo json_encode(["status" => "success", "message" => "No matching logic or update failed"]); } ?> === ФАЙЛ: mark_read.php === "error", "message" => "Missing parameters"]); exit(); } // Обновляем статус: "Пометить прочитанными все сообщения, // которые адресованы МНЕ ($user_id) от НЕГО ($sender_id) и еще не прочитаны" $sql = "UPDATE messages SET is_read = 1 WHERE receiver_id = ? AND sender_id = ? AND is_read = 0"; $stmt = $conn->prepare($sql); $stmt->bind_param("ss", $user_id, $sender_id); if ($stmt->execute()) { // Возвращаем успех (даже если 0 строк обновлено, это не ошибка) echo json_encode(["status" => "success", "updated_count" => $stmt->affected_rows]); } else { echo json_encode(["status" => "error", "message" => "Database error"]); } $stmt->close(); $conn->close(); ?> === ФАЙЛ: mark_story_seen.php === $user_id, "story_id" => $story_id]); if (empty($user_id) || empty($story_id)) { logDebug("SEEN_ERROR", "Missing data"); echo json_encode(["status" => "error", "message" => "Missing data"]); exit(); } // [ИСПРАВЛЕНИЕ] // Используем NOW() вместо time() * 1000. // MySQL сам подставит правильное текущее время. $sql = "INSERT IGNORE INTO story_views (user_id, story_id, created_at) VALUES (?, ?, NOW())"; $stmt = $conn->prepare($sql); if (!$stmt) { logDebug("SEEN_SQL_ERROR", $conn->error); echo json_encode(["status" => "error", "message" => "SQL Error"]); exit(); } // Bind только двух строк (время MySQL берет сам) $stmt->bind_param("ss", $user_id, $story_id); if ($stmt->execute()) { // affected_rows > 0 значит записали новую. 0 значит уже было. // В обоих случаях для клиента это успех. logDebug("SEEN_SUCCESS", "View recorded (Rows: " . $stmt->affected_rows . ")"); echo json_encode(["status" => "success"]); } else { logDebug("SEEN_DB_ERROR", $stmt->error); echo json_encode(["status" => "error", "message" => $stmt->error]); } $stmt->close(); $conn->close(); ?> === ФАЙЛ: moderation_dict.php === prepare("SELECT is_immune FROM users WHERE id = ?"); $stmt->bind_param("s", $user_id); $stmt->execute(); $res = $stmt->get_result(); $immune = false; if ($row = $res->fetch_assoc()) { $immune = (int)$row['is_immune'] === 1; } $stmt->close(); return $immune; } function contains_bad_words($text) { if (empty($text)) return false; // 1. Переводим в нижний регистр для удобства поиска $text = mb_strtolower($text, 'UTF-8'); // 2. АНТИ-ХИТРЕЦ: Перехватываем замену кириллицы на латиницу и символы $replacements = [ 'a'=>'а', 'o'=>'о', 'e'=>'е', 'c'=>'с', 'p'=>'р', 'x'=>'х', 'y'=>'у', '0'=>'о', '@'=>'а', '*'=>'и', '$'=>'с', '1'=>'и', '3'=>'з' ]; $text = strtr($text, $replacements); // 3. СТРОГАЯ БАЗА ЗАПРЕЩЕННЫХ СЛОВ (Regex) // Модификатор /u - для поддержки UTF-8 (кириллицы/туркменских букв), /i - игнор регистра $bad_patterns = [ // ================================================================== // 🇹🇲 ТУРКМЕНСКИЙ ЯЗЫК (Специфика менталитета) // ================================================================== // Аморальность и оскорбления: '/j+[a@]+l+[a@]+p+/ui', // Проститутка '/f+a+h+y+ş+a+/ui', // Проститутка (лит.) '/g+[oö0]+t+w+e+r+e+n+/ui', // Оскорбление чести '/h+a+r+a+m+z+a+d+a+/ui', // Ублюдок '/s+i+k+e+y+i+n+/ui', // Мат '/a+m+c+y+k+/ui', // Мат '/g+o+t+u+n+e+/ui', // Мат // Наркотики и криминал: '/n+e+ş+e+k+e+ş+/ui', // Наркоман '/t+i+r+ý+e+k+/ui', // Опиум/Наркотики // ================================================================== // 🇷🇺 РУССКИЙ ЯЗЫК (Традиционный мат и теневой бизнес) // ================================================================== // Агрессивный мат (корни слов, ловит любые склонения): '/п[иеё]зд/ui', // пиздец, пизда, спиздил и т.д. '/х[уy][йеёия]/ui', // хуй, охуенно, нахуй '/бл[яеё][дт]/ui', // блядь, ублюдок '/еб[аоуеёы]/ui', // ебать, уебок, выебоны '/шлюх[аиу]/ui', // шлюха '/п[ие]д[оа]р/ui', // пидор, педараст '/г[ао]нд[оа]н/ui', // гондон '/м[уy]д[аa]к/ui', // мудак // Незаконная деятельность, наркотики, аморалка (Очень важно для TM!): '/з[аa]кл[аa]дк/ui', // закладки (наркотики) '/м[еe]ф[еe]др[оo]н/ui', // мефедрон '/г[аa]ш[иu]ш/ui', // гашиш '/эск[оo]рт/ui', // эскорт услуги '/инд[иu]в[иu]д[уy]алк/ui', // индивидуалки (проституция) '/п[оo]рн[оo]/ui', // порно '/к[аa]зин[оo]/ui', // казино '/1x\s*bet/ui', // нелегальные ставки '/ст[аa]вк[иu]\s+н[аa]\s+сп[оo]рт/ui', // ставки на спорт '/п[оo]рн[оo]гр[аa]ф/ui', // порнография '/ф[иu]ш[иu]нг/ui', // фишинг (мошенничество) '/м[о0]шенник/ui', // мошенник // ================================================================== // 🇬🇧 АНГЛИЙСКИЙ ЯЗЫК (Глобальный спам и боты) // ================================================================== '/f+u+c+k+/ui', '/b+i+t+c+h+/ui', '/s+h+i+t+/ui', '/a+s+s+h+o+l+e+/ui', '/c+u+n+t+/ui', '/n+i+g+g+e+r+/ui', '/p+o+r+n+/ui', // Porn '/e+s+c+o+r+t+s+/ui', // Escorts '/o+n+l+y+f+a+n+s+/ui', // OnlyFans (часто спамят боты) '/w+e+e+d+/ui', // Наркотики '/m+e+t+h+/ui' // Наркотики ]; foreach ($bad_patterns as $pattern) { if (preg_match($pattern, $text)) { return true; // Найдено запрещенное слово! } } return false; } // Проверка: Находится ли пользователь в бане прямо сейчас? function is_user_banned($user_id, $conn) { $stmt = $conn->prepare("SELECT banned_until FROM users WHERE id = ? AND banned_until > NOW()"); $stmt->bind_param("s", $user_id); $stmt->execute(); $res = $stmt->get_result(); $is_banned = $res->num_rows > 0; $stmt->close(); return $is_banned; } // Применение наказания (Система Страйков) function apply_strike($user_id, $conn) { // [НОВОЕ] Если у пользователя иммунитет — просто выходим, не считая страйки if (has_moderation_immunity($user_id, $conn)) { return; } // Узнаем текущее количество страйков $stmt = $conn->prepare("SELECT strikes_count FROM users WHERE id = ?"); $stmt->bind_param("s", $user_id); $stmt->execute(); $res = $stmt->get_result(); $strikes = 0; if ($row = $res->fetch_assoc()) { $strikes = (int)$row['strikes_count']; } $stmt->close(); $strikes++; // Увеличиваем страйк $banned_until = null; $notif_title = "⚠️ Предупреждение о нарушении"; $notif_msg = ""; if ($strikes == 1) { // Strike 1: Просто предупреждение $notif_msg = "Ваше сообщение было удалено. Оно содержит запрещенные материалы (нецензурная лексика, аморальный контент или незаконная реклама). Мы уважаем культуру и законы Туркменистана. Пожалуйста, соблюдайте правила общения."; $conn->query("UPDATE users SET strikes_count = 1 WHERE id = '$user_id'"); } elseif ($strikes == 2) { // Strike 2: Бан на 24 часа $notif_title = "🚫 Блокировка аккаунта (24 часа)"; $notif_msg = "Вы повторно нарушили правила сообщества. В целях поддержания порядка, ваша возможность писать сообщения и комментарии заблокирована на 24 часа."; $conn->query("UPDATE users SET strikes_count = 2, banned_until = DATE_ADD(NOW(), INTERVAL 24 HOUR) WHERE id = '$user_id'"); } else { // Strike 3: Бан на 10 лет (Перманентный) $notif_title = "🏴‍☠️ АККАУНТ ЗАБЛОКИРОВАН"; $notif_msg = "Вы получили 3 предупреждения за грубые нарушения правил платформы. Ваш аккаунт навсегда лишен права публиковать контент."; $conn->query("UPDATE users SET strikes_count = $strikes, banned_until = DATE_ADD(NOW(), INTERVAL 10 YEAR) WHERE id = '$user_id'"); } // ============================================================================== // [ИЗМЕНЕНО] Отправляем красный колокольчик нарушителю (Исправлена ошибка action_id) // ============================================================================== $sysAvatar = 'https://cdn-icons-png.flaticon.com/512/7486/7486747.png'; // Иконка системы $action_id = 'auto_mod'; // ВАЖНО: Обязательное поле для вашей базы данных! $n_stmt = $conn->prepare("INSERT INTO notifications (user_id, sender_id, type, action_id, title, message, user_avatar, created_at, is_read) VALUES (?, 'system', 'SYSTEM_ALERT', ?, ?, ?, ?, NOW(), 0)"); // Передаем 5 строк (sssss): user_id, action_id, title, message, user_avatar $n_stmt->bind_param("sssss", $user_id, $action_id, $notif_title, $notif_msg, $sysAvatar); $n_stmt->execute(); $n_stmt->close(); } ?> === ФАЙЛ: process_moderation.php === 'error', 'message' => 'Доступ запрещен. Вы не авторизованы.']); exit(); } require_once 'db.php'; // Получаем общие данные $action = $_POST['action'] ?? ''; // ============================================================================== // [НОВОЕ] ДЕЙСТВИЕ 1: ОТКЛОНИТЬ ЖАЛОБУ (Вынесено НАВЕРХ, до проверок таблиц) // ============================================================================== if ($action === 'dismiss_report') { $report_id = $conn->real_escape_string($_POST['report_id'] ?? ''); if (empty($report_id)) { echo json_encode(['status' => 'error', 'message' => 'Не указан ID жалобы.']); exit(); } // Просто меняем статус жалобы, чтобы она исчезла из панели модератора $sql = "UPDATE reports SET status = 'dismissed' WHERE id = '$report_id'"; if ($conn->query($sql)) { echo json_encode(['status' => 'success']); } else { echo json_encode(['status' => 'error', 'message' => 'Ошибка при отклонении жалобы.']); } exit(); } // ============================================================================== // ДЕЙСТВИЯ 2, 3 и 4: ОДОБРЕНИЕ, УДАЛЕНИЕ КОНТЕНТА И СТРАЙКИ // ============================================================================== $item_id = $_POST['item_id'] ?? ''; $source_table = $_POST['source_table'] ?? ''; // post, grand, product, user $reason = $_POST['reason'] ?? 'Нарушение правил сообщества'; if (empty($item_id) || empty($source_table) || empty($action)) { echo json_encode(['status' => 'error', 'message' => 'Недостаточно данных для модерации.']); exit(); } // Определяем правильное имя таблицы и колонок $tableName = ''; $textColumn = ''; $authorColumn = ''; if ($source_table === 'post' || $source_table === 'ad') { $tableName = 'posts'; $textColumn = 'content'; $authorColumn = 'author_id'; } elseif ($source_table === 'grand') { $tableName = 'grand_products'; $textColumn = 'description'; $authorColumn = 'owner_id'; } elseif ($source_table === 'product') { $tableName = 'products'; $textColumn = 'description'; $authorColumn = 'owner_id'; } elseif ($source_table === 'user') { // ============================================================================== // [НОВОЕ] Поддержка профиля для страйков // ============================================================================== $tableName = 'users'; $textColumn = 'bio'; // У юзера нет поста, берем описание (просто для логов) $authorColumn = 'id'; } elseif ($source_table === 'message') { $tableName = 'messages'; $textColumn = 'message_text'; $authorColumn = 'sender_id'; } else { echo json_encode(['status' => 'error', 'message' => "Неизвестный тип контента: $source_table"]); exit(); } $safe_id = $conn->real_escape_string($item_id); if ($action === 'approve') { // ДЕЙСТВИЕ: ОДОБРИТЬ $sql = "UPDATE $tableName SET is_moderated = 1 WHERE id = '$safe_id'"; if ($conn->query($sql)) { echo json_encode(['status' => 'success']); } else { echo json_encode(['status' => 'error', 'message' => 'Ошибка при одобрении.']); } } elseif ($action === 'delete') { // ДЕЙСТВИЕ: УДАЛИТЬ (С сохранением текстового следа и УВЕДОМЛЕНИЕМ) // Шаг А: Читаем данные перед удалением $sqlInfo = "SELECT t.$textColumn as content_text, u.name as user_name, u.phone as user_phone, t.$authorColumn as user_id FROM $tableName t LEFT JOIN users u ON t.$authorColumn = u.id WHERE t.id = '$safe_id'"; $resInfo = $conn->query($sqlInfo); if ($resInfo && $resInfo->num_rows > 0) { $info = $resInfo->fetch_assoc(); $safe_name = $conn->real_escape_string($info['user_name'] ?? 'Неизвестно'); $safe_phone = $conn->real_escape_string($info['user_phone'] ?? ''); $safe_text = $conn->real_escape_string($info['content_text'] ?? ''); $target_user_id = $conn->real_escape_string($info['user_id'] ?? ''); $safe_reason = $conn->real_escape_string($reason); // Шаг Б: Сохраняем текстовый след в Корзину $sqlLog = "INSERT INTO moderation_trash (original_id, source_table, user_name, user_phone, content_text) VALUES ('$safe_id', '$source_table', '$safe_name', '$safe_phone', '$safe_text')"; $conn->query($sqlLog); // Шаг В: ОТПРАВЛЯЕМ УВЕДОМЛЕНИЕ В КОЛОКОЛЬЧИК if (!empty($target_user_id)) { $notifTitle = 'Контент удален'; $notifMessage = 'Ваша публикация была удалена модератором. Причина: ' . $safe_reason; $sysAvatar = 'https://cdn-icons-png.flaticon.com/512/7486/7486747.png'; $preview = mb_substr($safe_text, 0, 50) . (mb_strlen($safe_text) > 50 ? '...' : ''); $sqlNotif = "INSERT INTO notifications (user_id, sender_id, type, action_id, title, message, user_avatar, post_preview) VALUES ('$target_user_id', 'system', 'SYSTEM_ALERT', '$safe_id', '$notifTitle', '$notifMessage', '$sysAvatar', '$preview')"; $conn->query($sqlNotif); } } // Шаг Г: БЕЗВОЗВРАТНО СТИРАЕМ ОРИГИНАЛ $sqlDelete = "DELETE FROM $tableName WHERE id = '$safe_id'"; if ($conn->query($sqlDelete)) { // Если модератор удалил контент по жалобе, закрываем эту жалобу $sqlCloseReports = "UPDATE reports SET status = 'resolved' WHERE reported_item_id = '$safe_id'"; $conn->query($sqlCloseReports); echo json_encode(['status' => 'success']); } else { echo json_encode(['status' => 'error', 'message' => 'Ошибка базы данных при удалении.']); } } elseif ($action === 'warn') { // ============================================================================== // [НОВОЕ] ДЕЙСТВИЕ: ВЫДАТЬ СТРАЙК ПОЛЬЗОВАТЕЛЮ // ============================================================================== if ($source_table !== 'user') { echo json_encode(['status' => 'error', 'message' => 'Страйк можно выдать только пользователю.']); exit(); } $safe_reason = $conn->real_escape_string($reason); // 1. Увеличиваем счетчик страйков $sqlStrike = "UPDATE users SET strikes_count = strikes_count + 1 WHERE id = '$safe_id'"; if ($conn->query($sqlStrike)) { // 2. Отправляем системное уведомление пользователю $notifTitle = '⚠️ Предупреждение от модератора'; $notifMessage = 'Вы получили страйк. Причина: ' . $safe_reason . '. При получении 3 страйков аккаунт будет заблокирован.'; $sysAvatar = 'https://cdn-icons-png.flaticon.com/512/7486/7486747.png'; $sqlNotif = "INSERT INTO notifications (user_id, sender_id, type, action_id, title, message, user_avatar, post_preview) VALUES ('$safe_id', 'system', 'SYSTEM_ALERT', '$safe_id', '$notifTitle', '$notifMessage', '$sysAvatar', '')"; $conn->query($sqlNotif); // 3. Закрываем активную жалобу $sqlCloseReports = "UPDATE reports SET status = 'resolved' WHERE reported_item_id = '$safe_id'"; $conn->query($sqlCloseReports); echo json_encode(['status' => 'success']); } else { echo json_encode(['status' => 'error', 'message' => 'Ошибка при выдаче страйка: ' . $conn->error]); } } else { echo json_encode(['status' => 'error', 'message' => 'Неизвестная команда.']); } $conn->close(); ?> === ФАЙЛ: process_unblock.php === 'error', 'message' => 'Доступ запрещен.']); exit(); } require_once 'db.php'; $user_id = $_POST['user_id'] ?? ''; if (empty($user_id)) { echo json_encode(['status' => 'error', 'message' => 'ID пользователя не указан.']); exit(); } $safe_id = $conn->real_escape_string($user_id); // [ИЗМЕНЕНО] Обнуляем страйки и время бана $sql = "UPDATE users SET strikes_count = 0, banned_until = NULL WHERE id = '$safe_id'"; if ($conn->query($sql)) { echo json_encode(['status' => 'success']); } else { echo json_encode(['status' => 'error', 'message' => 'Ошибка БД: ' . $conn->error]); } $conn->close(); ?> === ФАЙЛ: register.php === "error", "message" => "Method Not Allowed"]); exit(); } // --- 2. ПОДКЛЮЧЕНИЕ К БАЗЕ --- require_once 'db.php'; // --- 3. ПОЛУЧЕНИЕ И ЗАЩИТА ДАННЫХ --- // Функция для глубокой очистки function cleanInput($data) { $data = trim($data); // Убираем пробелы по краям $data = stripslashes($data); // Убираем экранирование $data = htmlspecialchars($data);// Превращаем теги