Файловый менеджер - Редактировать - /home/gqdcvggs/vertchasseur.com/generate_wallet_pass.php
Назад
<?php session_start(); include 'db.php'; if (!isset($_SESSION['resident_id'])) { header('Location: login.php'); exit; } class VertChasseurWalletGenerator { private $passTypeId; private $teamId; public function __construct() { $this->passTypeId = 'pass.com.imators.vertchasseur-resident'; $this->teamId = 'TON_TEAM_ID'; } public function generatePass($residentData) { $passId = 'VC' . str_pad($residentData['id'], 6, '0', STR_PAD_LEFT); $tempDir = sys_get_temp_dir() . '/pass_' . uniqid(); if (!mkdir($tempDir, 0755, true)) { throw new Exception('Impossible de créer le dossier temporaire'); } try { $passJson = $this->createPassJson($residentData, $passId); file_put_contents($tempDir . '/pass.json', json_encode($passJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); $this->copyLogo($tempDir); $this->createManifest($tempDir); $this->signManifest($tempDir); $pkpassPath = $this->createPkpass($tempDir, $passId); $this->cleanup($tempDir); return $pkpassPath; } catch (Exception $e) { $this->cleanup($tempDir); throw $e; } } private function createPassJson($residentData, $passId) { $memberSince = new DateTime($residentData['created_at']); return [ 'formatVersion' => 1, 'passTypeIdentifier' => $this->passTypeId, 'serialNumber' => $passId, 'teamIdentifier' => $this->teamId, 'organizationName' => 'Vert Chasseur', 'description' => 'Carte Resident Vert Chasseur', 'foregroundColor' => 'rgb(255, 255, 255)', 'backgroundColor' => 'rgb(44, 62, 80)', 'labelColor' => 'rgb(255, 255, 255)', 'generic' => [ 'primaryFields' => [ [ 'key' => 'name', 'value' => $residentData['first_name'] . ' ' . $residentData['last_name'] ] ], 'secondaryFields' => [ [ 'key' => 'since', 'label' => 'Membre depuis', 'value' => $memberSince->format('Y') ] ] ] ]; } private function copyLogo($tempDir) { if (!file_exists('logo_new.png')) { throw new Exception('Logo logo_new.png introuvable'); } if (!function_exists('imagecreatefrompng')) { throw new Exception('Extension GD requise pour traiter les images'); } $this->resizeImage('logo_new.png', $tempDir . '/logo.png', 160, 50); $this->resizeImage('logo_new.png', $tempDir . '/logo@2x.png', 320, 100); $this->resizeImage('logo_new.png', $tempDir . '/icon.png', 29, 29); $this->resizeImage('logo_new.png', $tempDir . '/icon@2x.png', 58, 58); if (!file_exists($tempDir . '/logo.png')) { throw new Exception('Impossible de créer logo.png'); } } private function resizeImage($source, $dest, $width, $height) { list($originalWidth, $originalHeight, $type) = getimagesize($source); switch ($type) { case IMAGETYPE_PNG: $sourceImage = imagecreatefrompng($source); break; case IMAGETYPE_JPEG: $sourceImage = imagecreatefromjpeg($source); break; default: throw new Exception('Format d\'image non supporté'); } $destImage = imagecreatetruecolor($width, $height); imagealphablending($destImage, false); imagesavealpha($destImage, true); $transparent = imagecolorallocatealpha($destImage, 0, 0, 0, 127); imagefill($destImage, 0, 0, $transparent); imagecopyresampled($destImage, $sourceImage, 0, 0, 0, 0, $width, $height, $originalWidth, $originalHeight); imagepng($destImage, $dest); imagedestroy($sourceImage); imagedestroy($destImage); } private function createManifest($tempDir) { $files = scandir($tempDir); $manifest = []; foreach ($files as $file) { if ($file !== '.' && $file !== '..' && $file !== 'manifest.json' && $file !== 'signature') { $filePath = $tempDir . '/' . $file; $manifest[$file] = sha1(file_get_contents($filePath)); } } file_put_contents($tempDir . '/manifest.json', json_encode($manifest)); } private function signManifest($tempDir) { // Mode sans signature pour débugger file_put_contents($tempDir . '/signature', ''); } private function createPkpass($tempDir, $passId) { $pkpassPath = sys_get_temp_dir() . '/vert_chasseur_' . $passId . '.pkpass'; $zip = new ZipArchive(); if ($zip->open($pkpassPath, ZipArchive::CREATE) !== TRUE) { throw new Exception('Impossible de créer le fichier .pkpass'); } $files = scandir($tempDir); foreach ($files as $file) { if ($file !== '.' && $file !== '..') { $zip->addFile($tempDir . '/' . $file, $file); } } $zip->close(); return $pkpassPath; } private function cleanup($tempDir) { if (is_dir($tempDir)) { $files = glob($tempDir . '/*'); foreach ($files as $file) { if (is_file($file)) { unlink($file); } } rmdir($tempDir); } } } if (isset($_POST['action']) && $_POST['action'] === 'get_user_data') { header('Content-Type: application/json'); $resident_id = $_SESSION['resident_id']; try { $stmt = $conn->prepare("SELECT first_name, last_name, email, address, created_at FROM residents WHERE id = ?"); $stmt->bind_param("i", $resident_id); $stmt->execute(); $result = $stmt->get_result(); if ($resident = $result->fetch_assoc()) { echo json_encode([ 'success' => true, 'first_name' => $resident['first_name'], 'last_name' => $resident['last_name'], 'email' => $resident['email'], 'address' => $resident['address'], 'created_at' => $resident['created_at'] ]); } else { echo json_encode(['success' => false, 'error' => 'Utilisateur introuvable']); } } catch (Exception $e) { echo json_encode(['success' => false, 'error' => 'Erreur de base de données']); } exit; } if (isset($_POST['action']) && $_POST['action'] === 'generate_wallet') { ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); try { $resident_id = $_SESSION['resident_id']; $stmt = $conn->prepare("SELECT id, first_name, last_name, email, address, created_at FROM residents WHERE id = ?"); $stmt->bind_param("i", $resident_id); $stmt->execute(); $result = $stmt->get_result(); if (!$resident = $result->fetch_assoc()) { throw new Exception('Résident introuvable pour ID: ' . $resident_id); } if (empty($resident['first_name']) || empty($resident['last_name'])) { throw new Exception('Prénom ou nom manquant'); } if (!file_exists('logo_new.png')) { throw new Exception('Logo logo_new.png introuvable'); } $generator = new VertChasseurWalletGenerator(); $pkpassPath = $generator->generatePass($resident); if (!file_exists($pkpassPath)) { throw new Exception('Fichier pkpass non généré'); } $filename = 'carte_vert_chasseur_' . preg_replace('/[^a-zA-Z0-9]/', '_', $resident['first_name']) . '_' . preg_replace('/[^a-zA-Z0-9]/', '_', $resident['last_name']) . '.pkpass'; header('Content-Type: application/vnd.apple.pkpass'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Content-Length: ' . filesize($pkpassPath)); header('Cache-Control: no-cache'); readfile($pkpassPath); unlink($pkpassPath); } catch (Exception $e) { http_response_code(500); header('Content-Type: text/plain'); echo 'ERREUR: ' . $e->getMessage(); } exit; } ?> <!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Carte Apple Wallet - Vert Chasseur</title> <link rel="icon" type="image/png" href="logo_new.png"> <script src="https://cdn.tailwindcss.com"></script> <script> tailwind.config = { darkMode: 'class', theme: { extend: {} } } </script> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Princess+Sofia&display=swap" rel="stylesheet"> <style> @media (min-width: 768px) { body { background-image: url('font-homepage.png'); background-size: cover; background-position: center; background-attachment: fixed; background-repeat: no-repeat; } } @media (max-width: 767px) { body { background-color: #ffffff !important; background-image: none !important; } .dark body { background-color: #000000 !important; background-image: none !important; } } body { font-family: 'Inter', sans-serif; } .txt-chasseur { font-family: "Princess Sofia", serif; font-weight: 400; } .wallet-card { background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%); position: relative; overflow: hidden; } .wallet-card::before { content: ''; position: absolute; top: -50%; right: -50%; width: 200%; height: 200%; background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%); transform: rotate(45deg); } .logo-corner { position: absolute; bottom: 20px; right: 20px; width: 40px; height: 40px; background: rgba(255,255,255,0.9); border-radius: 8px; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #2c3e50; font-size: 12px; } </style> </head> <body class="bg-white dark:bg-black transition-colors duration-300"> <script> if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } </script> <main class="min-h-screen px-4 py-8 md:py-16"> <div class="max-w-4xl mx-auto"> <div class="flex flex-col items-center justify-center mb-8 md:mb-12 text-center"> <a href="index.php" class="absolute top-8 left-8 p-2 text-stone-600 dark:text-stone-400 hover:text-stone-800 dark:hover:text-stone-200 transition-colors"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/> </svg> </a> <h1 class="text-3xl md:text-5xl lg:text-6xl font-light text-stone-800 dark:text-white mb-2 md:mb-3 tracking-tight"> Carte Apple Wallet </h1> <p class="text-lg md:text-xl text-stone-600 dark:text-stone-400 font-light"> Génère ta carte de résident <span class="txt-chasseur text-green-600">Vert Chasseur</span> </p> </div> <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 md:gap-12"> <div class="bg-white dark:bg-stone-900 rounded-2xl md:rounded-3xl p-6 md:p-8 border border-stone-200 dark:border-stone-800 shadow-lg dark:shadow-stone-900/30"> <h2 class="text-2xl md:text-3xl font-light text-stone-800 dark:text-white mb-6 md:mb-8 tracking-tight"> Génération automatique </h2> <div class="space-y-6"> <div class="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 p-4 rounded-xl"> <div class="flex items-center space-x-3"> <svg class="w-5 h-5 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/> </svg> <span class="text-green-800 dark:text-green-200 font-medium">Tes informations sont déjà prêtes</span> </div> </div> <div class="space-y-4 text-stone-600 dark:text-stone-400 mb-6"> <div class="flex items-start space-x-3"> <div class="w-2 h-2 bg-green-500 rounded-full mt-2 flex-shrink-0"></div> <p>Ta carte sera générée avec tes informations de profil</p> </div> <div class="flex items-start space-x-3"> <div class="w-2 h-2 bg-green-500 rounded-full mt-2 flex-shrink-0"></div> <p>Compatible avec tous les appareils Apple</p> </div> <div class="flex items-start space-x-3"> <div class="w-2 h-2 bg-green-500 rounded-full mt-2 flex-shrink-0"></div> <p>Ajout automatique à Apple Wallet</p> </div> </div> <button onclick="generateWallet()" id="generateBtn" class="w-full bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 text-white font-medium py-4 px-6 rounded-xl transition-all duration-200 transform hover:scale-105"> <div class="flex items-center justify-center space-x-2"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z"/> </svg> <span>Générer ma carte Apple Wallet</span> </div> </button> </div> </div> <div class="space-y-6"> <div class="bg-white dark:bg-stone-900 rounded-2xl md:rounded-3xl p-6 md:p-8 border border-stone-200 dark:border-stone-800 shadow-lg dark:shadow-stone-900/30"> <h3 class="text-xl md:text-2xl font-light text-stone-800 dark:text-white mb-4 tracking-tight"> Aperçu de ta carte </h3> <div class="wallet-card text-white rounded-xl p-6 relative min-h-48"> <div class="relative z-10"> <div class="flex justify-between items-start mb-6"> <div> <div id="preview-name" class="text-xl font-bold mb-1">Ton nom</div> <div class="text-sm opacity-90">Résident du Quartier</div> </div> <div id="preview-member-since" class="text-right text-sm opacity-90"> Membre depuis<br> <span class="font-medium">--</span> </div> </div> <div class="flex items-center space-x-4 mt-8"> <div class="w-12 h-12 bg-white/20 rounded-lg flex items-center justify-center"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2v0"/> </svg> </div> <div> <div class="font-medium">Carte Résident</div> <div class="text-sm opacity-75">Vert Chasseur</div> </div> </div> </div> <div class="logo-corner"> <img src="logo_new.png" alt="Vert Chasseur" style="width: 100%; height: 100%; object-fit: contain; border-radius: 6px;"> </div> </div> </div> <div class="bg-amber-50 dark:bg-amber-900/20 rounded-2xl p-6 border border-amber-200 dark:border-amber-800"> <div class="flex items-start space-x-3"> <svg class="w-5 h-5 text-amber-600 dark:text-amber-400 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/> </svg> <div> <h4 class="font-medium text-amber-800 dark:text-amber-200 mb-1">À propos des cartes Apple Wallet</h4> <p class="text-sm text-amber-700 dark:text-amber-300"> Cette carte sera ajoutée à ton Apple Wallet et pourra être utilisée pour les offres exclusives du quartier. </p> </div> </div> </div> </div> </div> </div> </main> <footer class="w-full py-4 md:py-6 border-t border-stone-200 dark:border-stone-800 mt-16"> <p class="text-xs text-stone-500 dark:text-stone-400 text-center px-4"> © 2025 Vert Chasseur · <a href="https://aktascorp.com" class="underline hover:text-stone-700 dark:hover:text-stone-300">aktascorp</a> member </p> </footer> <script> function loadUserData() { const formData = new FormData(); formData.append('action', 'get_user_data'); fetch('', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { document.getElementById('preview-name').textContent = data.first_name + ' ' + data.last_name; const memberDate = new Date(data.created_at); const memberSince = memberDate.toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' }); document.getElementById('preview-member-since').innerHTML = `Membre depuis<br><span class="font-medium">${memberSince}</span>`; } }) .catch(error => { console.error('Erreur lors du chargement des données:', error); }); } function generateWallet() { const button = document.getElementById('generateBtn'); const originalContent = button.innerHTML; button.disabled = true; button.innerHTML = ` <div class="flex items-center justify-center space-x-2"> <svg class="animate-spin w-5 h-5" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> <span>Génération en cours...</span> </div> `; const formData = new FormData(); formData.append('action', 'generate_wallet'); fetch('', { method: 'POST', body: formData }) .then(response => { if (!response.ok) { return response.text().then(text => { throw new Error(`Erreur HTTP ${response.status}: ${text}`); }); } const contentType = response.headers.get('Content-Type'); if (contentType && contentType.includes('application/vnd.apple.pkpass')) { return response.blob(); } else { return response.text().then(text => { throw new Error(`Type de contenu inattendu: ${contentType}. Réponse: ${text}`); }); } }) .then(blob => { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = 'carte_resident_vert_chasseur.pkpass'; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); alert('Carte générée avec succès !'); }) .catch(error => { console.error('Erreur complète:', error); alert(`Erreur lors de la génération: ${error.message}`); }) .finally(() => { button.disabled = false; button.innerHTML = originalContent; }); } document.addEventListener('DOMContentLoaded', function() { loadUserData(); }); </script> </body> </html>
| ver. 1.6 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0.01 |
proxy
|
phpinfo
|
Настройка