diff --git a/api/pexels.php b/api/pexels.php index ce51ef0..990b07a 100644 --- a/api/pexels.php +++ b/api/pexels.php @@ -6,7 +6,8 @@ $action = $_GET['action'] ?? 'search'; if ($action === 'search') { $q = $_GET['query'] ?? 'avatar'; - $url = 'https://api.pexels.com/v1/search?query=' . urlencode($q) . '&per_page=12&page=1'; + $page = isset($_GET['page']) ? (int)$_GET['page'] : 1; + $url = 'https://api.pexels.com/v1/search?query=' . urlencode($q) . '&per_page=26&page=' . $page; $data = pexels_get($url); if (!$data) { echo json_encode(['error' => 'Failed to fetch images']); diff --git a/api/upload_avatar.php b/api/upload_avatar.php new file mode 100644 index 0000000..28f8886 --- /dev/null +++ b/api/upload_avatar.php @@ -0,0 +1,58 @@ + false, 'error' => 'Non autorisé']); + exit; +} + +if (!isset($_FILES['avatar']) || $_FILES['avatar']['error'] !== UPLOAD_ERR_OK) { + echo json_encode(['success' => false, 'error' => 'Aucun fichier reçu ou erreur de téléchargement']); + exit; +} + +$file = $_FILES['avatar']; +$allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif']; +$maxSize = 2 * 1024 * 1024; // 2MB + +if (!in_array($file['type'], $allowedTypes)) { + echo json_encode(['success' => false, 'error' => 'Format de fichier non supporté (JPG, PNG, WebP, GIF uniquement)']); + exit; +} + +if ($file['size'] > $maxSize) { + echo json_encode(['success' => false, 'error' => 'Le fichier est trop volumineux (max 2Mo)']); + exit; +} + +$extension = pathinfo($file['name'], PATHINFO_EXTENSION); +if (empty($extension)) { + $extensions = [ + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/webp' => 'webp', + 'image/gif' => 'gif' + ]; + $extension = $extensions[$file['type']] ?? 'jpg'; +} + +$filename = 'avatar_' . $user['id'] . '_' . time() . '.' . $extension; +$targetPath = __DIR__ . '/../assets/images/avatars/' . $filename; +$relativeUrl = 'assets/images/avatars/' . $filename; + +if (move_uploaded_file($file['tmp_name'], $targetPath)) { + // Optionally delete old local avatar if it exists + if (!empty($user['avatar_url']) && strpos($user['avatar_url'], 'assets/images/avatars/') === 0) { + $oldFile = __DIR__ . '/../' . $user['avatar_url']; + if (file_exists($oldFile)) { + unlink($oldFile); + } + } + + echo json_encode(['success' => true, 'url' => $relativeUrl]); +} else { + echo json_encode(['success' => false, 'error' => 'Erreur lors de l\'enregistrement du fichier']); +} diff --git a/api/upload_server_icon.php b/api/upload_server_icon.php new file mode 100644 index 0000000..c330add --- /dev/null +++ b/api/upload_server_icon.php @@ -0,0 +1,79 @@ + false, 'error' => 'Non autorisé']); + exit; +} + +$server_id = $_POST['server_id'] ?? 0; +if (!$server_id) { + echo json_encode(['success' => false, 'error' => 'ID du serveur manquant']); + exit; +} + +if (!Permissions::hasPermission($user['id'], $server_id, Permissions::MANAGE_SERVER)) { + echo json_encode(['success' => false, 'error' => 'Vous n\'avez pas la permission de gérer ce serveur']); + exit; +} + +if (!isset($_FILES['icon']) || $_FILES['icon']['error'] !== UPLOAD_ERR_OK) { + echo json_encode(['success' => false, 'error' => 'Aucun fichier reçu ou erreur de téléchargement']); + exit; +} + +$file = $_FILES['icon']; +$allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif']; +$maxSize = 2 * 1024 * 1024; // 2MB + +if (!in_array($file['type'], $allowedTypes)) { + echo json_encode(['success' => false, 'error' => 'Format de fichier non supporté (JPG, PNG, WebP, GIF uniquement)']); + exit; +} + +if ($file['size'] > $maxSize) { + echo json_encode(['success' => false, 'error' => 'Le fichier est trop volumineux (max 2Mo)']); + exit; +} + +$extension = pathinfo($file['name'], PATHINFO_EXTENSION); +if (empty($extension)) { + $extensions = [ + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/webp' => 'webp', + 'image/gif' => 'gif' + ]; + $extension = $extensions[$file['type']] ?? 'jpg'; +} + +$filename = 'server_' . $server_id . '_' . time() . '.' . $extension; +$dir = __DIR__ . '/../assets/images/servers/'; +if (!is_dir($dir)) { + mkdir($dir, 0775, true); +} + +$targetPath = $dir . $filename; +$relativeUrl = 'assets/images/servers/' . $filename; + +if (move_uploaded_file($file['tmp_name'], $targetPath)) { + // Optionally fetch old icon to delete it if it's local + $stmt = db()->prepare("SELECT icon_url FROM servers WHERE id = ?"); + $stmt->execute([$server_id]); + $server = $stmt->fetch(); + + if ($server && !empty($server['icon_url']) && strpos($server['icon_url'], 'assets/images/servers/') === 0) { + $oldFile = __DIR__ . '/../' . $server['icon_url']; + if (file_exists($oldFile)) { + unlink($oldFile); + } + } + + echo json_encode(['success' => true, 'url' => $relativeUrl]); +} else { + echo json_encode(['success' => false, 'error' => 'Erreur lors de l\'enregistrement du fichier']); +} diff --git a/assets/images/avatars/avatar_2_1771557067.png b/assets/images/avatars/avatar_2_1771557067.png new file mode 100644 index 0000000..63e17fb Binary files /dev/null and b/assets/images/avatars/avatar_2_1771557067.png differ diff --git a/assets/images/servers/server_1_1771557116.png b/assets/images/servers/server_1_1771557116.png new file mode 100644 index 0000000..63e17fb Binary files /dev/null and b/assets/images/servers/server_1_1771557116.png differ diff --git a/assets/js/main.js b/assets/js/main.js index 085dfe6..5b7ea1a 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -2544,33 +2544,125 @@ document.addEventListener('DOMContentLoaded', () => { // User Settings - Save logic removed and moved to index.php for reliability const avatarSearchBtn = document.getElementById('search-avatar-btn'); + const avatarRefreshBtn = document.getElementById('refresh-avatar-btn'); const avatarSearchQuery = document.getElementById('avatar-search-query'); const avatarResults = document.getElementById('avatar-results'); const avatarPreview = document.getElementById('settings-avatar-preview'); const avatarUrlInput = document.getElementById('settings-avatar-url'); + const avatarUploadInput = document.getElementById('avatar-upload-input'); - avatarSearchBtn?.addEventListener('click', async () => { - const q = avatarSearchQuery.value.trim(); + avatarUploadInput?.addEventListener('change', async (e) => { + const file = e.target.files[0]; + if (!file) return; + + const formData = new FormData(); + formData.append('avatar', file); + + try { + avatarPreview.innerHTML = '
'; + const resp = await fetch('api/upload_avatar.php', { + method: 'POST', + body: formData + }); + const data = await resp.json(); + avatarPreview.innerHTML = ''; + + if (data.success) { + avatarUrlInput.value = data.url; + avatarPreview.style.backgroundImage = `url('${data.url}')`; + } else { + alert(data.error || 'Erreur lors de l\'upload'); + } + } catch (err) { + console.error(err); + avatarPreview.innerHTML = ''; + alert('Erreur réseau lors de l\'upload'); + } + }); + + const serverIconUploadInput = document.getElementById('server-icon-upload-input'); + // serverIconPreview and serverIconUrlInput are already declared above + + serverIconUploadInput?.addEventListener('change', async (e) => { + const file = e.target.files[0]; + if (!file) return; + + const formData = new FormData(); + formData.append('icon', file); + formData.append('server_id', window.activeServerId); + + try { + serverIconPreview.innerHTML = '
'; + const resp = await fetch('api/upload_server_icon.php', { + method: 'POST', + body: formData + }); + const data = await resp.json(); + serverIconPreview.innerHTML = ''; + + if (data.success) { + serverIconUrlInput.value = data.url; + serverIconPreview.style.backgroundImage = `url('${data.url}')`; + } else { + alert(data.error || 'Erreur lors de l\'upload'); + } + } catch (err) { + console.error(err); + serverIconPreview.innerHTML = ''; + alert('Erreur réseau lors de l\'upload'); + } + }); + + let currentAvatarPage = 1; + + async function fetchAvatars(q, page = 1) { if (!q) return; avatarResults.innerHTML = '
Searching...
'; try { - const resp = await fetch(`api/pexels.php?action=search&query=${encodeURIComponent(q)}`); + const resp = await fetch(`api/pexels.php?action=search&query=${encodeURIComponent(q)}&page=${page}`); const data = await resp.json(); avatarResults.innerHTML = ''; - data.forEach(photo => { - const img = document.createElement('img'); - img.src = photo.url; - img.className = 'avatar-pick'; - img.style.width = '60px'; - img.style.height = '60px'; - img.style.cursor = 'pointer'; - img.onclick = () => { - avatarUrlInput.value = photo.url; - avatarPreview.style.backgroundImage = `url('${photo.url}')`; - }; - avatarResults.appendChild(img); - }); - } catch (e) { console.error(e); } + if (data && Array.isArray(data)) { + data.forEach(photo => { + const img = document.createElement('img'); + img.src = photo.url; + img.className = 'avatar-pick'; + img.style.width = '100%'; + img.style.height = 'auto'; + img.style.aspectRatio = '1/1'; + img.style.objectFit = 'cover'; + img.style.borderRadius = '4px'; + img.style.cursor = 'pointer'; + img.onclick = () => { + avatarUrlInput.value = photo.url; + avatarPreview.style.backgroundImage = `url('${photo.url}')`; + }; + avatarResults.appendChild(img); + }); + } else { + avatarResults.innerHTML = '
Aucun résultat trouvé.
'; + } + } catch (e) { + console.error(e); + avatarResults.innerHTML = '
Erreur lors de la récupération.
'; + } + } + + avatarSearchBtn?.addEventListener('click', () => { + currentAvatarPage = 1; + fetchAvatars(avatarSearchQuery.value.trim(), currentAvatarPage); + }); + + avatarRefreshBtn?.addEventListener('click', () => { + currentAvatarPage++; + fetchAvatars(avatarSearchQuery.value.trim() || 'avatar', currentAvatarPage); + }); + + avatarSearchQuery?.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + currentAvatarPage = 1; + fetchAvatars(avatarSearchQuery.value.trim(), currentAvatarPage); + } }); // Theme preview diff --git a/index.php b/index.php index c82ca2e..91e0b4e 100644 --- a/index.php +++ b/index.php @@ -264,6 +264,18 @@ if ($is_dm_view) { } } + // Always fetch tags if it's a forum channel + if ($channel_type === 'forum') { + $stmt_tags = db()->prepare("SELECT * FROM forum_tags WHERE channel_id = ? ORDER BY name ASC"); + $stmt_tags->execute([$active_channel_id]); + $forum_tags = $stmt_tags->fetchAll(); + + $selected_tag_ids = []; + if (!empty($_GET['tags'])) { + $selected_tag_ids = array_filter(explode(',', $_GET['tags']), 'is_numeric'); + } + } + if ($active_thread) { // Thread messages already fetched above } elseif ($channel_type === 'rules') { @@ -275,12 +287,13 @@ if ($is_dm_view) { $stmt->execute([$active_channel_id]); $autoroles = $stmt->fetchAll(); } elseif ($channel_type === 'forum') { - $filter_status = $_GET['status'] ?? 'all'; - $status_where = ""; - if ($filter_status === 'resolved') { - $status_where = " AND t.solution_message_id IS NOT NULL"; - } elseif ($filter_status === 'unresolved') { - $status_where = " AND t.solution_message_id IS NULL"; + $tag_where = ""; + $query_params = [$active_server_id, $active_server_id, $active_channel_id]; + + if (!empty($selected_tag_ids)) { + $placeholders = implode(',', array_fill(0, count($selected_tag_ids), '?')); + $tag_where = " AND EXISTS (SELECT 1 FROM thread_tags tt WHERE tt.thread_id = t.id AND tt.tag_id IN ($placeholders))"; + foreach ($selected_tag_ids as $tid) $query_params[] = $tid; } $stmt = db()->prepare(" @@ -291,10 +304,10 @@ if ($is_dm_view) { (SELECT GROUP_CONCAT(CONCAT(ft.name, ':', ft.color) SEPARATOR '|') FROM thread_tags tt JOIN forum_tags ft ON tt.tag_id = ft.id WHERE tt.thread_id = t.id) as tags FROM forum_threads t JOIN users u ON t.user_id = u.id - WHERE t.channel_id = ? " . $status_where . " + WHERE t.channel_id = ? " . $tag_where . " ORDER BY t.is_pinned DESC, t.last_activity_at DESC, t.created_at DESC "); - $stmt->execute([$active_server_id, $active_server_id, $active_channel_id]); + $stmt->execute($query_params); $threads = $stmt->fetchAll(); } else { // Fetch messages for normal chat channels @@ -924,20 +937,31 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; - +

🏛️

-
@@ -954,7 +978,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
Pas encore de discussions. Commencez-en une !
- +
@@ -997,6 +1021,23 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
+ + +

Bienvenue dans # !

@@ -1239,6 +1280,10 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
">
+ +
@@ -1253,8 +1298,9 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
+
-
+
Recherchez des images pour changer votre avatar.
@@ -1858,7 +1904,13 @@ document.addEventListener('DOMContentLoaded', () => { ?>
">
- +
+ + + +