Files
VH_posting_system/api.php
T
zuevav 68f4560bd5 Add digital badge tab for round-display photo prep
Adds a new "Цифровой бейдж" tab for preparing 240×240 PNG circles for
round digital displays. Photos come from device upload or the existing
Flickr selection. Each photo can be drag-cropped and zoomed (mouse,
wheel, or pinch on touch), have a yellow price tag (arc band or
rectangular plate, with auto thousands separator and ₽), and a curved
nickname signature — which auto-moves to the top of the circle when a
price tag occupies the bottom. Saves are routed through the Web Share
API on phones so the badge lands in the iOS/Android Photos app
directly; history of saved badges is kept under data/badges/. Default
nickname is stored in Settings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:16:37 +03:00

1120 lines
42 KiB
PHP

<?php
/**
* API endpoints for AJAX requests
* VK, Telegram, Flickr integration
*/
// Set timezone to Moscow
date_default_timezone_set('Europe/Moscow');
session_start();
header('Content-Type: application/json');
// Load configuration
$configFile = __DIR__ . '/config.php';
if (!file_exists($configFile)) {
echo json_encode(['error' => 'Configuration not found']);
exit;
}
$config = require $configFile;
// Autoload classes
spl_autoload_register(function ($class) {
$file = __DIR__ . '/classes/' . $class . '.php';
if (file_exists($file)) {
require_once $file;
}
});
/**
* Create FlickrAPI instance with OAuth if available
*/
function createFlickrAPI($config) {
$flickr = new FlickrAPI(
$config['flickr']['api_key'],
$config['flickr']['api_secret'] ?? '',
$config['flickr_user_id'] ?? ''
);
// Add OAuth if tokens exist
if (!empty($config['flickr']['api_secret'])) {
$oauth = new FlickrOAuth(
$config['flickr']['api_key'],
$config['flickr']['api_secret']
);
if ($oauth->isAuthorized()) {
$flickr->setOAuth($oauth);
}
}
return $flickr;
}
// Check authentication
$auth = new Auth();
if (!$auth->isAuthenticated()) {
http_response_code(401);
echo json_encode(['error' => 'Not authenticated']);
exit;
}
// Get action
$action = $_GET['action'] ?? $_POST['action'] ?? '';
try {
switch ($action) {
// ============ CONVERTER ============
case 'convert':
$urls = $_POST['urls'] ?? '';
$size = $_POST['size'] ?? 'Large';
$format = $_POST['format'] ?? 'bbcode';
$parser = new FlickrParser();
$generator = new FormatGenerator();
$parsed = $parser->parseMultiple($urls);
if (empty($parsed)) {
echo json_encode(['error' => 'No valid Flickr URLs found']);
exit;
}
$images = [];
$flickr = null;
// Check if we need API to get full URLs
$needApi = false;
foreach ($parsed as $item) {
if ($item['type'] === 'page' || $item['type'] === 'short') {
$needApi = true;
break;
}
}
if ($needApi && !empty($config['flickr']['api_key'])) {
$flickr = createFlickrAPI($config);
}
foreach ($parsed as $item) {
$imageData = [
'photo_id' => $item['photo_id'],
'title' => 'Image',
];
if ($item['type'] === 'direct') {
// Direct URL - modify size suffix
$imageData['url'] = $parser->buildImageUrl($item, $size);
$imageData['original'] = $parser->buildImageUrl($item, 'Original');
} elseif ($flickr) {
// Need to fetch from API
try {
$info = $flickr->getPhotoInfo($item['photo_id']);
$sizes = $flickr->getPhotoSizes($item['photo_id']);
$imageData['title'] = $info['title']['_content'] ?? 'Image';
// Get requested size
$sizeMap = [
'Large' => 'Large',
'Large1600' => 'Large 1600',
'Large2048' => 'Large 2048',
'Original' => 'Original',
'Medium640' => 'Medium 640',
'Medium' => 'Medium',
];
$sizeName = $sizeMap[$size] ?? 'Large';
$imageData['url'] = $sizes[$sizeName]['url'] ?? $sizes['Large']['url'] ?? '';
$imageData['original'] = $sizes['Original']['url'] ?? $sizes['Large']['url'] ?? '';
} catch (Exception $e) {
$imageData['url'] = $item['original_url'];
$imageData['original'] = $item['original_url'];
}
} else {
// No API - use original URL
$imageData['url'] = $item['original_url'];
$imageData['original'] = $item['original_url'];
}
$images[] = $imageData;
}
$output = $generator->generateMultiple($format, $images);
echo json_encode([
'success' => true,
'output' => $output,
'count' => count($images),
]);
break;
// ============ FLICKR GALLERY ============
case 'flickr_albums':
if (empty($config['flickr']['api_key'])) {
echo json_encode(['error' => 'Flickr API not configured']);
exit;
}
$flickr = createFlickrAPI($config);
$page = (int)($_GET['page'] ?? 1);
$perPage = (int)($_GET['per_page'] ?? 50);
$result = $flickr->getPhotosets($page, $perPage);
echo json_encode([
'success' => true,
'albums' => $result['albums'],
'page' => $result['page'],
'pages' => $result['pages'],
'total' => $result['total'],
]);
break;
case 'flickr_photos':
if (empty($config['flickr']['api_key'])) {
echo json_encode(['error' => 'Flickr API not configured']);
exit;
}
$flickr = createFlickrAPI($config);
$page = (int)($_GET['page'] ?? 1);
$perPage = (int)($_GET['per_page'] ?? 50);
$albumId = $_GET['album_id'] ?? '';
$search = $_GET['search'] ?? '';
if ($search) {
$result = $flickr->searchPhotos($search, $page, $perPage);
} elseif ($albumId) {
$result = $flickr->getPhotosetPhotos($albumId, $page, $perPage);
} else {
$result = $flickr->getPhotos($page, $perPage);
}
echo json_encode([
'success' => true,
'photos' => $result['photos'],
'pagination' => [
'page' => $result['page'],
'pages' => $result['pages'],
'total' => $result['total'],
],
]);
break;
case 'flickr_photo_sizes':
if (empty($config['flickr']['api_key'])) {
echo json_encode(['error' => 'Flickr API not configured']);
exit;
}
$photoId = $_GET['photo_id'] ?? '';
if (!$photoId) {
echo json_encode(['error' => 'Photo ID required']);
exit;
}
$flickr = createFlickrAPI($config);
$sizes = $flickr->getPhotoSizes($photoId);
echo json_encode([
'success' => true,
'sizes' => $sizes,
]);
break;
case 'flickr_oauth_status':
if (empty($config['flickr']['api_secret'])) {
echo json_encode([
'success' => true,
'authorized' => false,
'message' => 'API secret not configured',
]);
exit;
}
$oauth = new FlickrOAuth(
$config['flickr']['api_key'],
$config['flickr']['api_secret']
);
echo json_encode([
'success' => true,
'authorized' => $oauth->isAuthorized(),
'auth_url' => 'flickr_auth.php',
]);
break;
case 'flickr_original_url':
if (empty($config['flickr']['api_key'])) {
echo json_encode(['error' => 'Flickr API not configured']);
exit;
}
$photoId = $_GET['photo_id'] ?? '';
if (!$photoId) {
echo json_encode(['error' => 'Photo ID required']);
exit;
}
$flickr = createFlickrAPI($config);
$originalUrl = $flickr->getOriginalUrl($photoId);
echo json_encode([
'success' => true,
'original_url' => $originalUrl,
'has_oauth' => $flickr->hasOAuth(),
]);
break;
// ============ TELEGRAM ============
case 'telegram_status':
if (empty($config['telegram']['bot_token'])) {
echo json_encode([
'success' => true,
'connected' => false,
'message' => 'Bot token not configured',
]);
exit;
}
$telegram = new TelegramBot($config['telegram']['bot_token']);
try {
$me = $telegram->getMe();
echo json_encode([
'success' => true,
'connected' => true,
'bot_name' => $me['first_name'] ?? '',
'bot_username' => $me['username'] ?? '',
]);
} catch (Exception $e) {
echo json_encode([
'success' => true,
'connected' => false,
'message' => $e->getMessage(),
]);
}
break;
case 'telegram_channels':
echo json_encode([
'success' => true,
'channels' => $config['telegram']['channels'] ?? [],
]);
break;
case 'telegram_post':
if (empty($config['telegram']['bot_token'])) {
echo json_encode(['error' => 'Telegram bot not configured']);
exit;
}
$channelId = $_POST['channel_id'] ?? '';
$text = $_POST['text'] ?? '';
$photos = json_decode($_POST['photos'] ?? '[]', true);
$parseMode = $_POST['parse_mode'] ?? 'HTML';
if (!$channelId) {
echo json_encode(['error' => 'Channel ID required']);
exit;
}
$telegram = new TelegramBot($config['telegram']['bot_token']);
$result = $telegram->post($channelId, $photos, $text, $parseMode);
echo json_encode([
'success' => true,
'result' => $result,
]);
break;
// ============ VK ============
case 'vk_status':
if (empty($config['vk']['access_token'])) {
echo json_encode([
'success' => true,
'connected' => false,
'message' => 'VK access token not configured',
]);
exit;
}
$vk = new VKAPI($config['vk']['access_token']);
try {
$validation = $vk->validateToken();
if ($validation['valid']) {
echo json_encode([
'success' => true,
'connected' => true,
'user_name' => $validation['user_name'] ?? '',
'user_id' => $validation['user_id'] ?? '',
'type' => $validation['type'] ?? 'user',
'screen_name' => $validation['screen_name'] ?? '',
]);
} else {
echo json_encode([
'success' => true,
'connected' => false,
'message' => $validation['error'] ?? 'Invalid token',
]);
}
} catch (Exception $e) {
echo json_encode([
'success' => true,
'connected' => false,
'message' => $e->getMessage(),
]);
}
break;
case 'vk_groups':
if (empty($config['vk']['access_token'])) {
echo json_encode([
'success' => true,
'groups' => [],
]);
exit;
}
$vk = new VKAPI($config['vk']['access_token']);
// First check if this is a community token
try {
$validation = $vk->validateToken();
if ($validation['valid'] && ($validation['type'] ?? '') === 'community') {
// Community token - return the community itself
echo json_encode([
'success' => true,
'groups' => [[
'id' => $validation['user_id'],
'name' => $validation['user_name'] ?? 'Сообщество',
'screen_name' => $validation['screen_name'] ?? '',
]],
'type' => 'community',
]);
exit;
}
} catch (Exception $e) {
// Continue to try groups.get
}
try {
$groups = $vk->getGroups();
echo json_encode([
'success' => true,
'groups' => $groups,
]);
} catch (Exception $e) {
echo json_encode([
'success' => true,
'groups' => [],
'error' => $e->getMessage(),
]);
}
break;
case 'vk_post':
if (empty($config['vk']['access_token'])) {
echo json_encode(['error' => 'VK not configured']);
exit;
}
$groupId = $_POST['group_id'] ?? '';
$text = $_POST['text'] ?? '';
$photos = json_decode($_POST['photos'] ?? '[]', true);
if (!$groupId) {
echo json_encode(['error' => 'Group ID required']);
exit;
}
$vk = new VKAPI($config['vk']['access_token']);
$result = $vk->post($groupId, $photos, $text);
echo json_encode([
'success' => true,
'result' => $result,
]);
break;
// ============ FILE UPLOAD ============
case 'upload_file':
if (empty($_FILES['file'])) {
echo json_encode(['error' => 'No file uploaded']);
exit;
}
$file = $_FILES['file'];
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'video/mp4', 'video/quicktime', 'video/webm'];
if (!in_array($file['type'], $allowedTypes)) {
echo json_encode(['error' => 'Неподдерживаемый тип файла: ' . $file['type']]);
exit;
}
if ($file['size'] > 50 * 1024 * 1024) {
echo json_encode(['error' => 'Файл слишком большой (макс 50MB)']);
exit;
}
// Create uploads directory
$uploadsDir = __DIR__ . '/uploads';
if (!is_dir($uploadsDir)) {
mkdir($uploadsDir, 0755, true);
}
// Generate unique filename
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$filename = uniqid('upload_') . '_' . time() . '.' . $ext;
$filepath = $uploadsDir . '/' . $filename;
if (move_uploaded_file($file['tmp_name'], $filepath)) {
// Get the URL
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'];
$path = dirname($_SERVER['REQUEST_URI']);
$url = $protocol . '://' . $host . $path . '/uploads/' . $filename;
echo json_encode([
'success' => true,
'url' => $url,
'filename' => $filename,
'type' => $file['type'],
'size' => $file['size']
]);
} else {
echo json_encode(['error' => 'Не удалось сохранить файл']);
}
break;
// ============ MULTI-PLATFORM POSTING ============
case 'multi_post':
$platforms = json_decode($_POST['platforms'] ?? '[]', true);
$text = $_POST['text'] ?? '';
$photos = json_decode($_POST['photos'] ?? '[]', true);
$uploadedFiles = json_decode($_POST['uploaded_files'] ?? '[]', true);
$parseMode = $_POST['parse_mode'] ?? 'HTML';
// Merge Flickr photos and uploaded files
if (!empty($uploadedFiles)) {
foreach ($uploadedFiles as $uploadedFile) {
if (!empty($uploadedFile['url'])) {
$photos[] = $uploadedFile['url'];
}
}
}
if (empty($platforms)) {
echo json_encode(['error' => 'No platforms selected']);
exit;
}
$results = [];
foreach ($platforms as $platform) {
$type = $platform['type'] ?? '';
$target = $platform['target'] ?? '';
switch ($type) {
case 'telegram':
if (empty($config['telegram']['bot_token'])) {
$results['telegram'] = [
'success' => false,
'error' => 'Telegram not configured',
];
break;
}
try {
$telegram = new TelegramBot($config['telegram']['bot_token']);
$result = $telegram->post($target, $photos, $text, $parseMode);
$results['telegram'] = [
'success' => true,
'result' => $result,
];
} catch (Exception $e) {
$results['telegram'] = [
'success' => false,
'error' => $e->getMessage(),
];
}
break;
case 'vk':
if (empty($config['vk']['access_token'])) {
$results['vk'] = [
'success' => false,
'error' => 'VK not configured',
];
break;
}
try {
$vk = new VKAPI($config['vk']['access_token']);
// VK uses plain text, remove HTML/Markdown formatting
$vkText = strip_tags($text);
$result = $vk->post($target, $photos, $vkText);
$vkResult = [
'success' => true,
'result' => $result,
];
// Include warning if photos were posted as links
if (isset($result['warning'])) {
$vkResult['warning'] = $result['warning'];
}
$results['vk'] = $vkResult;
} catch (Exception $e) {
$results['vk'] = [
'success' => false,
'error' => $e->getMessage(),
];
}
break;
case 'instagram':
// Instagram requires Facebook Business API
$results['instagram'] = [
'success' => false,
'error' => 'Instagram posting requires Facebook Business API setup',
];
break;
default:
$results[$type] = [
'success' => false,
'error' => 'Unknown platform',
];
}
}
echo json_encode([
'success' => true,
'results' => $results,
]);
break;
// ============ TAG PRESETS ============
case 'get_presets':
$presetsFile = __DIR__ . '/data/tag_presets.json';
if (file_exists($presetsFile)) {
$presets = json_decode(file_get_contents($presetsFile), true);
echo json_encode(['success' => true, 'presets' => $presets ?: []]);
} else {
// Return default presets
$defaultPresets = [
['id' => 1, 'name' => 'BJD', 'tags' => ['bjd', 'doll', 'куклы']],
['id' => 2, 'name' => 'Фото', 'tags' => ['фото', 'photo', 'photography']],
['id' => 3, 'name' => 'Арт', 'tags' => ['art', 'artwork', 'творчество']],
['id' => 4, 'name' => 'Handmade', 'tags' => ['handmade', 'ручнаяработа']],
['id' => 5, 'name' => 'Faceup', 'tags' => ['faceup', 'мейк']],
['id' => 6, 'name' => 'Outfit', 'tags' => ['outfit', 'одежда']],
];
echo json_encode(['success' => true, 'presets' => $defaultPresets]);
}
break;
case 'save_presets':
$presetsFile = __DIR__ . '/data/tag_presets.json';
$input = json_decode(file_get_contents('php://input'), true);
$presets = $input['presets'] ?? [];
// Validate presets structure
if (!is_array($presets)) {
echo json_encode(['error' => 'Invalid presets format']);
exit;
}
// Ensure data directory exists
$dataDir = __DIR__ . '/data';
if (!is_dir($dataDir)) {
mkdir($dataDir, 0755, true);
}
// Save presets
if (file_put_contents($presetsFile, json_encode($presets, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
echo json_encode(['success' => true, 'message' => 'Presets saved']);
} else {
echo json_encode(['error' => 'Failed to save presets']);
}
break;
// ============ SCHEDULED POSTS ============
case 'get_scheduled_posts':
$scheduledFile = __DIR__ . '/data/scheduled_posts.json';
if (file_exists($scheduledFile)) {
$posts = json_decode(file_get_contents($scheduledFile), true) ?: [];
// Sort by scheduled time
usort($posts, function($a, $b) {
return strtotime($a['scheduled_time']) - strtotime($b['scheduled_time']);
});
echo json_encode(['success' => true, 'posts' => $posts]);
} else {
echo json_encode(['success' => true, 'posts' => []]);
}
break;
case 'create_scheduled_post':
$scheduledFile = __DIR__ . '/data/scheduled_posts.json';
$dataDir = __DIR__ . '/data';
if (!is_dir($dataDir)) {
mkdir($dataDir, 0755, true);
}
$posts = file_exists($scheduledFile) ? json_decode(file_get_contents($scheduledFile), true) ?: [] : [];
$newPost = [
'id' => uniqid('sched_'),
'text' => $_POST['text'] ?? '',
'tags' => json_decode($_POST['tags'] ?? '[]', true),
'photos' => json_decode($_POST['photos'] ?? '[]', true),
'uploaded_files' => json_decode($_POST['uploaded_files'] ?? '[]', true),
'platforms' => json_decode($_POST['platforms'] ?? '[]', true),
'scheduled_time' => $_POST['scheduled_time'] ?? '',
'cross_promo' => ($_POST['cross_promo'] ?? '0') === '1',
'created_at' => date('Y-m-d H:i:s'),
'status' => 'pending'
];
if (empty($newPost['scheduled_time'])) {
echo json_encode(['error' => 'Укажите дату и время публикации']);
exit;
}
if (strtotime($newPost['scheduled_time']) < time()) {
echo json_encode(['error' => 'Нельзя запланировать на прошедшее время']);
exit;
}
$posts[] = $newPost;
if (file_put_contents($scheduledFile, json_encode($posts, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
echo json_encode(['success' => true, 'post' => $newPost]);
} else {
echo json_encode(['error' => 'Не удалось сохранить']);
}
break;
case 'update_scheduled_post':
$scheduledFile = __DIR__ . '/data/scheduled_posts.json';
$postId = $_POST['id'] ?? '';
if (!$postId) {
echo json_encode(['error' => 'ID поста не указан']);
exit;
}
$posts = file_exists($scheduledFile) ? json_decode(file_get_contents($scheduledFile), true) ?: [] : [];
$found = false;
foreach ($posts as &$post) {
if ($post['id'] === $postId && $post['status'] === 'pending') {
$post['text'] = $_POST['text'] ?? $post['text'];
$post['tags'] = isset($_POST['tags']) ? json_decode($_POST['tags'], true) : $post['tags'];
$post['photos'] = isset($_POST['photos']) ? json_decode($_POST['photos'], true) : $post['photos'];
$post['uploaded_files'] = isset($_POST['uploaded_files']) ? json_decode($_POST['uploaded_files'], true) : $post['uploaded_files'];
$post['platforms'] = isset($_POST['platforms']) ? json_decode($_POST['platforms'], true) : $post['platforms'];
$post['scheduled_time'] = $_POST['scheduled_time'] ?? $post['scheduled_time'];
$post['cross_promo'] = isset($_POST['cross_promo']) ? ($_POST['cross_promo'] === '1') : ($post['cross_promo'] ?? false);
$post['updated_at'] = date('Y-m-d H:i:s');
$found = true;
break;
}
}
if (!$found) {
echo json_encode(['error' => 'Пост не найден или уже опубликован']);
exit;
}
if (file_put_contents($scheduledFile, json_encode($posts, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
echo json_encode(['success' => true, 'message' => 'Пост обновлён']);
} else {
echo json_encode(['error' => 'Не удалось сохранить']);
}
break;
case 'delete_scheduled_post':
$scheduledFile = __DIR__ . '/data/scheduled_posts.json';
$postId = $_POST['id'] ?? '';
if (!$postId) {
echo json_encode(['error' => 'ID поста не указан']);
exit;
}
$posts = file_exists($scheduledFile) ? json_decode(file_get_contents($scheduledFile), true) ?: [] : [];
$initialCount = count($posts);
$posts = array_filter($posts, function($post) use ($postId) {
return $post['id'] !== $postId || $post['status'] !== 'pending';
});
if (count($posts) === $initialCount) {
echo json_encode(['error' => 'Пост не найден или уже опубликован']);
exit;
}
$posts = array_values($posts); // Re-index
if (file_put_contents($scheduledFile, json_encode($posts, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
echo json_encode(['success' => true, 'message' => 'Пост удалён']);
} else {
echo json_encode(['error' => 'Не удалось сохранить']);
}
break;
case 'mark_post_published':
$scheduledFile = __DIR__ . '/data/scheduled_posts.json';
$postId = $_POST['id'] ?? '';
$resultsJson = $_POST['results'] ?? '{}';
$results = json_decode($resultsJson, true) ?: [];
if (!$postId) {
echo json_encode(['error' => 'ID поста не указан']);
exit;
}
$posts = file_exists($scheduledFile) ? json_decode(file_get_contents($scheduledFile), true) ?: [] : [];
$found = false;
foreach ($posts as &$post) {
if (($post['id'] ?? null) === $postId && ($post['status'] ?? '') === 'pending') {
$post['status'] = 'published';
$post['published_at'] = date('Y-m-d H:i:s');
$post['results'] = $results;
$found = true;
break;
}
}
unset($post);
if (!$found) {
echo json_encode(['error' => 'Пост не найден или уже опубликован']);
exit;
}
if (file_put_contents($scheduledFile, json_encode($posts, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
echo json_encode(['success' => true]);
} else {
echo json_encode(['error' => 'Не удалось сохранить']);
}
break;
case 'get_published_posts':
$scheduledFile = __DIR__ . '/data/scheduled_posts.json';
if (file_exists($scheduledFile)) {
$posts = json_decode(file_get_contents($scheduledFile), true) ?: [];
// Filter only published posts
$published = array_filter($posts, function($p) {
return $p['status'] === 'published';
});
// Sort by published_at descending (newest first)
usort($published, function($a, $b) {
return strtotime($b['published_at'] ?? $b['scheduled_time']) - strtotime($a['published_at'] ?? $a['scheduled_time']);
});
// Return last 10
$published = array_slice($published, 0, 10);
echo json_encode(['success' => true, 'posts' => array_values($published)]);
} else {
echo json_encode(['success' => true, 'posts' => []]);
}
break;
// ============ CROSS-PROMO SETTINGS ============
case 'save_cross_promo':
$settingsFile = __DIR__ . '/data/cross_promo.json';
$dataDir = __DIR__ . '/data';
if (!is_dir($dataDir)) {
mkdir($dataDir, 0755, true);
}
$settings = [
'telegramLink' => trim($_POST['telegramLink'] ?? ''),
'vkLink' => trim($_POST['vkLink'] ?? ''),
'textForTg' => trim($_POST['textForTg'] ?? 'Мой канал ВКонтакте'),
'textForVk' => trim($_POST['textForVk'] ?? 'Мой канал в Telegram')
];
if (file_put_contents($settingsFile, json_encode($settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
echo json_encode(['success' => true]);
} else {
echo json_encode(['error' => 'Не удалось сохранить']);
}
break;
case 'get_cross_promo':
$settingsFile = __DIR__ . '/data/cross_promo.json';
if (file_exists($settingsFile)) {
$settings = json_decode(file_get_contents($settingsFile), true) ?: [];
echo json_encode(['success' => true, 'settings' => $settings]);
} else {
echo json_encode(['success' => true, 'settings' => [
'telegramLink' => '',
'vkLink' => '',
'textForTg' => 'Мой канал ВКонтакте',
'textForVk' => 'Мой канал в Telegram'
]]);
}
break;
// ============ DRAFTS ============
case 'save_draft':
$draftFile = __DIR__ . '/data/draft.json';
$dataDir = __DIR__ . '/data';
if (!is_dir($dataDir)) {
mkdir($dataDir, 0755, true);
}
$draft = [
'text' => $_POST['text'] ?? '',
'tags' => json_decode($_POST['tags'] ?? '[]', true),
'photos' => json_decode($_POST['photos'] ?? '[]', true),
'uploaded_files' => json_decode($_POST['uploaded_files'] ?? '[]', true),
'updated_at' => date('Y-m-d H:i:s')
];
if (file_put_contents($draftFile, json_encode($draft, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
echo json_encode(['success' => true]);
} else {
echo json_encode(['error' => 'Не удалось сохранить черновик']);
}
break;
case 'get_draft':
$draftFile = __DIR__ . '/data/draft.json';
if (file_exists($draftFile)) {
$draft = json_decode(file_get_contents($draftFile), true) ?: [];
echo json_encode(['success' => true, 'draft' => $draft]);
} else {
echo json_encode(['success' => true, 'draft' => null]);
}
break;
case 'clear_draft':
$draftFile = __DIR__ . '/data/draft.json';
if (file_exists($draftFile)) {
unlink($draftFile);
}
echo json_encode(['success' => true]);
break;
// ============ SETTINGS ============
case 'save_vk_token':
$token = trim($_POST['token'] ?? '');
if (empty($token)) {
echo json_encode(['error' => 'Токен не может быть пустым']);
exit;
}
// Update config.php with new VK token
$configFile = __DIR__ . '/config.php';
$configContent = file_get_contents($configFile);
// Check if vk section exists
if (strpos($configContent, "'vk'") !== false) {
// Update existing vk access_token
$configContent = preg_replace(
"/('vk'\s*=>\s*\[\s*'access_token'\s*=>\s*')[^']*(')/s",
"$1" . addslashes($token) . "$2",
$configContent
);
} else {
// Add vk section before the closing ];
$vkSection = "\n 'vk' => [\n 'access_token' => '" . addslashes($token) . "',\n ],\n";
$configContent = preg_replace("/(\];)\s*$/", $vkSection . "$1", $configContent);
}
if (file_put_contents($configFile, $configContent)) {
// Validate the new token
require_once __DIR__ . '/classes/VKAPI.php';
$vk = new VKAPI($token);
$validation = $vk->validateToken();
echo json_encode([
'success' => true,
'message' => 'Токен сохранён',
'validation' => $validation
]);
} else {
echo json_encode(['error' => 'Не удалось сохранить config.php']);
}
break;
case 'change_password':
$currentPassword = $_POST['current_password'] ?? '';
$newPassword = $_POST['new_password'] ?? '';
if (strlen($newPassword) < 8) {
echo json_encode(['error' => 'New password must be at least 8 characters']);
exit;
}
$username = $auth->getCurrentUser();
if ($auth->changePassword($username, $currentPassword, $newPassword)) {
echo json_encode(['success' => true, 'message' => 'Password changed successfully']);
} else {
echo json_encode(['error' => 'Current password is incorrect']);
}
break;
// ============ DIGITAL BADGE (round display) ============
case 'get_badge_settings':
$settingsFile = __DIR__ . '/data/badge_settings.json';
if (file_exists($settingsFile)) {
$settings = json_decode(file_get_contents($settingsFile), true) ?: [];
echo json_encode(['success' => true, 'settings' => $settings]);
} else {
echo json_encode(['success' => true, 'settings' => ['nickname' => '']]);
}
break;
case 'save_badge_settings':
$dataDir = __DIR__ . '/data';
if (!is_dir($dataDir)) {
mkdir($dataDir, 0755, true);
}
$settingsFile = $dataDir . '/badge_settings.json';
$settings = [
'nickname' => trim((string)($_POST['nickname'] ?? '')),
];
if (file_put_contents($settingsFile, json_encode($settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
echo json_encode(['success' => true]);
} else {
echo json_encode(['error' => 'Не удалось сохранить настройки']);
}
break;
case 'badge_save':
// Save generated badge PNG (sent as base64 data URL) to /data/badges/.
$dataUrl = $_POST['image'] ?? '';
if (strpos($dataUrl, 'data:image/png;base64,') !== 0) {
echo json_encode(['error' => 'Некорректный формат изображения']);
exit;
}
$base64 = substr($dataUrl, strlen('data:image/png;base64,'));
$binary = base64_decode($base64, true);
if ($binary === false) {
echo json_encode(['error' => 'Не удалось декодировать изображение']);
exit;
}
if (strlen($binary) > 5 * 1024 * 1024) {
echo json_encode(['error' => 'Файл слишком большой']);
exit;
}
$badgesDir = __DIR__ . '/data/badges';
if (!is_dir($badgesDir)) {
mkdir($badgesDir, 0755, true);
}
$filename = 'badge_' . date('Ymd_His') . '_' . substr(bin2hex(random_bytes(4)), 0, 8) . '.png';
$filepath = $badgesDir . '/' . $filename;
if (file_put_contents($filepath, $binary) === false) {
echo json_encode(['error' => 'Не удалось сохранить файл']);
exit;
}
$indexFile = $badgesDir . '/index.json';
$index = [];
if (file_exists($indexFile)) {
$index = json_decode(file_get_contents($indexFile), true) ?: [];
}
array_unshift($index, [
'filename' => $filename,
'created_at' => date('Y-m-d H:i:s'),
'has_price' => !empty($_POST['has_price']),
'has_nickname' => !empty($_POST['has_nickname']),
'label' => trim((string)($_POST['label'] ?? '')),
]);
// Keep at most 100 entries
$index = array_slice($index, 0, 100);
file_put_contents($indexFile, json_encode($index, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'];
$path = dirname($_SERVER['REQUEST_URI']);
$url = $protocol . '://' . $host . $path . '/data/badges/' . $filename;
echo json_encode(['success' => true, 'filename' => $filename, 'url' => $url]);
break;
case 'badge_list':
$indexFile = __DIR__ . '/data/badges/index.json';
if (!file_exists($indexFile)) {
echo json_encode(['success' => true, 'items' => []]);
break;
}
$index = json_decode(file_get_contents($indexFile), true) ?: [];
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'];
$path = dirname($_SERVER['REQUEST_URI']);
$items = [];
foreach ($index as $entry) {
$fname = $entry['filename'] ?? '';
if (!$fname) continue;
$absPath = __DIR__ . '/data/badges/' . $fname;
if (!file_exists($absPath)) continue;
$items[] = [
'filename' => $fname,
'created_at' => $entry['created_at'] ?? '',
'has_price' => !empty($entry['has_price']),
'has_nickname' => !empty($entry['has_nickname']),
'label' => $entry['label'] ?? '',
'url' => $protocol . '://' . $host . $path . '/data/badges/' . $fname,
];
}
echo json_encode(['success' => true, 'items' => $items]);
break;
case 'badge_delete':
$filename = basename($_POST['filename'] ?? '');
if (!$filename || strpos($filename, 'badge_') !== 0) {
echo json_encode(['error' => 'Некорректное имя файла']);
exit;
}
$badgesDir = __DIR__ . '/data/badges';
$filepath = $badgesDir . '/' . $filename;
if (file_exists($filepath)) {
@unlink($filepath);
}
$indexFile = $badgesDir . '/index.json';
if (file_exists($indexFile)) {
$index = json_decode(file_get_contents($indexFile), true) ?: [];
$index = array_values(array_filter($index, function($e) use ($filename) {
return ($e['filename'] ?? '') !== $filename;
}));
file_put_contents($indexFile, json_encode($index, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
echo json_encode(['success' => true]);
break;
default:
echo json_encode(['error' => 'Unknown action']);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}