exec(" CREATE TABLE IF NOT EXISTS user_calendar_integrations ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, provider VARCHAR(50) NOT NULL DEFAULT 'icloud_caldav', username VARCHAR(255) NOT NULL, secret_encrypted TEXT NOT NULL, dav_principal_url VARCHAR(1024) DEFAULT NULL, calendar_url VARCHAR(1024) DEFAULT NULL, status VARCHAR(30) DEFAULT 'connected', last_sync_at DATETIME DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uq_user_provider (user_id, provider) )"); // ─── Actions ────────────────────────────────────────────────────────────────── if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!verify_csrf($_POST['csrf_token'] ?? null)) { if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') { header('Content-Type: application/json'); echo json_encode(['success' => false, 'error' => 'Session invalide (CSRF). Rechargez la page.']); exit; } $error = "Session invalide (CSRF). Rechargez la page."; } else { $action = $_POST['action'] ?? ''; // 🟢 SAUVEGARDE GLOBALE : CONFIGURATION DU FOYER (Unifiée) if ($action === 'update_family_info') { header('Content-Type: application/json'); try { require_once __DIR__ . '/includes/db.php'; $pdo->beginTransaction(); // 1. Sauvegarde des paramètres globaux $currency = trim($_POST['currency'] ?? '€'); $zone = trim($_POST['zone_scolaire'] ?? 'C'); $careModesJson = $_POST['custom_care_modes'] ?? '[]'; $stmtSave = $pdo->prepare("UPDATE pf_foyer_settings SET currency = ?, zone_scolaire = ?, care_modes = ? WHERE id = 1"); $stmtSave->execute([$currency, $zone, $careModesJson]); // 2. Gestion des enfants $kids = $_POST['kids'] ?? []; $deleted = json_decode($_POST['deleted_kids'] ?? '[]', true); if (!empty($deleted)) { $in = str_repeat('?,', count($deleted) - 1) . '?'; $stmtDel = $pdo->prepare("DELETE FROM pf_people WHERE id IN ($in) AND role = 'enfant'"); $stmtDel->execute($deleted); } $stmtInsert = $pdo->prepare("INSERT INTO pf_people (name, role, care_modes) VALUES (?, 'enfant', ?)"); $stmtUpdate = $pdo->prepare("UPDATE pf_people SET name = ?, care_modes = ? WHERE id = ? AND role = 'enfant'"); foreach ($kids as $kidId => $kidData) { $name = trim($kidData['name'] ?? ''); if (empty($name)) continue; $modes = $kidData['modes'] ?? []; $modesJson = json_encode(array_values($modes)); if (strpos($kidId, 'new_') === 0) { $stmtInsert->execute([$name, $modesJson]); } else { $stmtUpdate->execute([$name, $modesJson, (int)$kidId]); } } $pdo->commit(); echo json_encode(['success' => true]); } catch (Exception $e) { $pdo->rollBack(); echo json_encode(['success' => false, 'error' => $e->getMessage()]); } exit; } if ($action === 'set_modules' && $family_id) { $all = ['calendar', 'budget', 'holidays', 'gifts', 'garage', 'memo', 'todo', 'liste', 'calendar_ios', 'printvault', 'planka']; $enabled = array_values(array_filter($all, fn($m) => isset($_POST['mod_' . $m]))); if (empty($enabled)) { $error = "Vous devez garder au moins un module actif."; } else { $meta_pdo->prepare("UPDATE families SET enabled_modules = ? WHERE id = ?") ->execute([json_encode($enabled), $family_id]); $_SESSION['enabled_modules'] = $enabled; $success = "Modules mis à jour."; } } if ($action === 'set_lang') { $lang = $_POST['lang'] ?? 'fr'; if (in_array($lang, ['fr', 'ca', 'en'])) { $_SESSION['app_lang'] = $lang; $meta_pdo->prepare("UPDATE users SET lang = ? WHERE id = ?") ->execute([$lang, $user_id]); $success = "Language updated."; } } if ($action === 'update_profile') { $display_name = trim($_POST['display_name'] ?? ''); $color = trim($_POST['color'] ?? '#0891b2'); if (!$display_name) { $error = "Le prénom ne peut pas être vide."; } else { $meta_pdo->prepare("UPDATE users SET display_name = ? WHERE id = ?") ->execute([$display_name, $user_id]); require_once __DIR__ . '/includes/db.php'; if (isset($pdo)) { $stmtPeopleColor = $pdo->prepare("UPDATE pf_people SET color = ? WHERE user_id = ?"); $stmtPeopleColor->execute([$color, $user_id]); } $_SESSION['user']['display_name'] = $display_name; $success = "Profil mis à jour."; } } if ($action === 'change_password') { $current = $_POST['current_password'] ?? ''; $new = $_POST['new_password'] ?? ''; $confirm = $_POST['confirm_password'] ?? ''; $row = $meta_pdo->prepare("SELECT password_hash FROM users WHERE id = ?"); $row->execute([$user_id]); $row = $row->fetch(); if (!password_verify($current, $row['password_hash'])) { $error = "Mot de passe actuel incorrect."; } elseif (strlen($new) < 6) { $error = "Le nouveau mot de passe doit faire au moins 6 caractères."; } elseif ($new !== $confirm) { $error = "Les mots de passe ne correspondent pas."; } else { $meta_pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?") ->execute([password_hash($new, PASSWORD_BCRYPT), $user_id]); $success = "Mot de passe modifié avec succès."; } } if ($action === 'update_family_name' && $family_id) { $name = trim($_POST['family_name'] ?? ''); if ($name) { $meta_pdo->prepare("UPDATE families SET name = ? WHERE id = ?") ->execute([$name, $family_id]); $success = "Nom de l'espace mis à jour."; } } if ($action === 'regen_invite' && $family_id) { $new_code = bin2hex(random_bytes(8)); $meta_pdo->prepare("UPDATE families SET invite_code = ? WHERE id = ?") ->execute([$new_code, $family_id]); $success = "Nouveau code d'invitation généré."; } if ($action === 'upload_home_bg' && $family_id) { $upload_dir = '/uploads/'; if (!is_dir($upload_dir)) @mkdir($upload_dir, 0755, true); $file = $_FILES['home_bg'] ?? null; if ($file && $file['error'] === 0) { $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($ext, ['jpg','jpeg','png','webp'])) { $error = "Format non supporté (jpg, png, webp)."; } else { $dest = $upload_dir . 'home_bg_' . $family_id . '.' . $ext; foreach (glob($upload_dir . 'home_bg_' . $family_id . '.*') as $old) @unlink($old); if (move_uploaded_file($file['tmp_name'], $dest)) { $success = "Image d'accueil mise à jour."; } else { $error = "Erreur lors de l'upload."; } } } } if ($action === 'reset_home_bg' && $family_id) { foreach (glob('/uploads/home_bg_' . $family_id . '.*') as $old) @unlink($old); $success = "Image d'accueil réinitialisée."; } if ($action === 'grocery_history_max' && $family_id) { require_once __DIR__ . '/includes/db.php'; if (!isset($pdo)) { $error = "Base famille indisponible."; } else { $n = (int) ($_POST['history_max'] ?? 20); $n = max(1, min(50, $n)); try { $pdo->prepare( "INSERT INTO pf_notes (note_type, reference_id, content) VALUES ('grocery_settings','history_max',?) ON DUPLICATE KEY UPDATE content=VALUES(content)" )->execute([(string) $n]); $success = tr('groceries_settings_saved'); } catch (\Throwable $e) { $error = tr('groceries_settings_error') . ' ' . $e->getMessage(); } } } if ($action === 'calendar_ios_save') { $username = trim($_POST['icloud_username'] ?? ''); $appPassword = hh_normalize_apple_app_password((string) ($_POST['icloud_app_password'] ?? '')); $calendarUrl = trim($_POST['icloud_calendar_url'] ?? ''); if (!$username || !$appPassword || !$calendarUrl) { $error = "Merci de renseigner identifiant iCloud, mot de passe d'app et URL CalDAV."; } else { try { $resolvedUrl = hh_icloud_resolve_calendar_url_if_needed($username, $appPassword, $calendarUrl); $encrypted = hh_encrypt_secret($appPassword); $meta_pdo->prepare(" INSERT INTO user_calendar_integrations (user_id, provider, username, secret_encrypted, calendar_url, status, updated_at) VALUES (?, 'icloud_caldav', ?, ?, ?, 'connected', NOW()) ON DUPLICATE KEY UPDATE username=VALUES(username), secret_encrypted=VALUES(secret_encrypted), calendar_url=VALUES(calendar_url), status='connected', updated_at=NOW() ")->execute([$user_id, $username, $encrypted, $resolvedUrl]); $msg = "Connexion calendrier iOS enregistrée."; if (rtrim($resolvedUrl, '/') !== rtrim($calendarUrl, '/')) { $msg .= " URL du calendrier détectée automatiquement."; } $success = $msg; } catch (\Throwable $e) { $error = "Impossible d'enregistrer la connexion iOS: " . $e->getMessage(); } } } if ($action === 'calendar_ios_test') { $row = $meta_pdo->prepare("SELECT id, username, secret_encrypted, calendar_url FROM user_calendar_integrations WHERE user_id = ? AND provider='icloud_caldav'"); $row->execute([$user_id]); $integration = $row->fetch(); if (!$integration) { $error = "Aucune connexion iOS configurée."; } else { try { $pwd = hh_normalize_apple_app_password(hh_decrypt_secret($integration['secret_encrypted'])); $calendarUrl = trim($integration['calendar_url']); $resolvedUrl = hh_icloud_resolve_calendar_url_if_needed($integration['username'], $pwd, $calendarUrl); $code = hh_caldav_test_calendar_collection($integration['username'], $pwd, $resolvedUrl); if (in_array($code, [200, 207], true)) { if (rtrim($resolvedUrl, '/') !== rtrim($calendarUrl, '/')) { $meta_pdo->prepare("UPDATE user_calendar_integrations SET calendar_url = ? WHERE id = ?") ->execute([$resolvedUrl, $integration['id']]); } $success = "Connexion iCloud CalDAV valide (HTTP $code)."; } else { $error = "Test connexion échoué (HTTP $code). Vérifiez vos identifiants."; } } catch (\Throwable $e) { $error = "Test connexion impossible: " . $e->getMessage(); } } } if ($action === 'calendar_ios_disconnect') { $meta_pdo->prepare("DELETE FROM user_calendar_integrations WHERE user_id = ? AND provider='icloud_caldav'")->execute([$user_id]); $success = "Connexion iOS supprimée."; } } } // ─── Chargement données ─────────────────────────────────────────────────────── if ($family_id) { require_once __DIR__ . '/includes/db.php'; } $stmtUser = $meta_pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmtUser->execute([$user_id]); $user = $stmtUser->fetch(); $user_color = '#0891b2'; if ($family_id && isset($pdo)) { $stmtColorFetch = $pdo->prepare("SELECT color FROM pf_people WHERE user_id = ?"); $stmtColorFetch->execute([$user_id]); $fetched_color = $stmtColorFetch->fetchColumn(); if ($fetched_color) { $user_color = $fetched_color; } } // 🟢 Récupération Unifiée : Foyer, Modes de Garde, Enfants $kidsList = []; $familyCareModes = ['Nounou', 'Centre', 'Avis']; $currentCurrency = defined('CURRENCY') ? CURRENCY : '€'; $currentZone = defined('ZONE_SCOLAIRE') ? ZONE_SCOLAIRE : 'C'; if ($family_id && isset($pdo)) { $foyerSettings = $pdo->query("SELECT currency, zone_scolaire, care_modes FROM pf_foyer_settings WHERE id = 1")->fetch(PDO::FETCH_ASSOC); if ($foyerSettings) { if (!empty($foyerSettings['currency'])) $currentCurrency = $foyerSettings['currency']; if (!empty($foyerSettings['zone_scolaire'])) $currentZone = $foyerSettings['zone_scolaire']; if (!empty($foyerSettings['care_modes'])) { $parsed = json_decode($foyerSettings['care_modes'], true); if (is_array($parsed)) $familyCareModes = $parsed; } } $stmtKids = $pdo->query("SELECT id, name, care_modes FROM pf_people WHERE role = 'enfant' ORDER BY id ASC"); $kidsList = $stmtKids->fetchAll(PDO::FETCH_ASSOC); } $family = null; $members = []; if ($family_id) { $fam = $meta_pdo->prepare("SELECT * FROM families WHERE id = ?"); $fam->execute([$family_id]); $family = $fam->fetch(); $mem = $meta_pdo->prepare("SELECT display_name, username, created_at, is_admin FROM users WHERE family_id = ? ORDER BY id"); $mem->execute([$family_id]); $members = $mem->fetchAll(); } $calendarIntegration = $meta_pdo->prepare("SELECT username, calendar_url, status, last_sync_at FROM user_calendar_integrations WHERE user_id = ? AND provider='icloud_caldav'"); $calendarIntegration->execute([$user_id]); $calendarIntegration = $calendarIntegration->fetch(); $groceryHistoryMaxSetting = 20; if ($family_id && isset($pdo)) { try { $gv = $pdo->query("SELECT content FROM pf_notes WHERE note_type='grocery_settings' AND reference_id='history_max'")->fetchColumn(); if ($gv !== false && $gv !== null && $gv !== '') { $groceryHistoryMaxSetting = max(1, min(50, (int) $gv)); } } catch (\Throwable $e) {} } $pageTitle = "Paramètres — HouseHub"; $activePage = "settings"; require __DIR__ . '/header.php'; ?>
Choisissez les modules visibles dans la navigation (partagé avec tous les membres de l'espace).
['icon' => '📅', 'label' => tr('menu_calendar')], 'budget' => ['icon' => '💰', 'label' => tr('menu_budget')], 'holidays' => ['icon' => '🏖️', 'label' => tr('menu_holidays')], 'gifts' => ['icon' => '🎁', 'label' => tr('menu_gifts')], 'garage' => ['icon' => '🚗', 'label' => tr('menu_garage')], 'memo' => ['icon' => '📝', 'label' => tr('menu_memo')], 'todo' => ['icon' => '✅', 'label' => tr('menu_todo')], 'liste' => ['icon' => '📝', 'label' => tr('menu_liste')], 'calendar_ios' => ['icon' => '📱', 'label' => tr('menu_calendar_ios')], 'printvault' => ['icon' => '🖨️', 'label' => tr('menu_printvault')], 'planka' => ['icon' => '📋', 'label' => tr('menu_planka')], ]; ?>= htmlspecialchars(tr('liste_settings_intro')) ?>
Configurez ici votre calendrier iCloud pour synchroniser les événements créés dans HouseHub.
Photo de fond affichée sur la page d'accueil (partagée avec tous les membres).
Configurez les indicateurs et les membres partagés par votre foyer.
Code d'invitation — partagez-le pour inviter quelqu'un
= htmlspecialchars($family['invite_code']) ?>
✓ Copié !
Lien d'inscription : = $_SERVER['HTTP_HOST'] ?>/register.php