This commit is contained in:
Francesco Picone
2025-12-03 18:35:21 +01:00
commit 4e41ca9bf7
22 changed files with 4836 additions and 0 deletions

485
README.md Normal file
View File

@@ -0,0 +1,485 @@
# 🧘‍♀️ Pilates Platform
Piattaforma completa per vendere videolezioni e lezioni live di Pilates. Sviluppata in PHP puro con design minimale bianco e celeste.
## 📋 Caratteristiche Principali
### Area Pubblica
- **Homepage** con lezioni demo gratuite
- **Sistema di autenticazione** completo (login, registrazione, recupero password)
- **Visualizzazione lezioni** con integrazione video (YouTube, Vimeo, locale)
- **Pagamento sicuro** tramite PayPal
### Area Utente
- **Dashboard personale** con lezioni acquistate
- **Catalogo completo** con filtri per tipo e livello
- **Gestione profilo** (dati personali e cambio password)
- **Accesso illimitato** alle lezioni acquistate
### Area Amministratore
- **Dashboard** con statistiche in tempo reale
- **Gestione lezioni** (crea, modifica, elimina)
- **Gestione utenti** (visualizza, blocca/sblocca)
- **Storico acquisti** con dettagli pagamenti PayPal
- **Report e analisi** vendite
### Funzionalità Tecniche
-**Sicurezza**: Password crittografate con bcrypt
-**Database**: MySQL con PDO per prevenire SQL injection
-**Sessioni**: Gestione sicura con timeout automatico
-**Responsive**: Design mobile-first
-**Codice commentato**: Ogni funzione è documentata per facilità di manutenzione
-**Log attività**: Tracciamento delle azioni importanti
-**Soft delete**: I dati eliminati non sono rimossi definitivamente
---
## 🚀 Installazione
### Requisiti
- **PHP** 7.4 o superiore
- **MySQL** 5.7 o superiore (o MariaDB 10.3+)
- **Web Server** (Apache, Nginx, o PHP built-in server per sviluppo)
- **Account PayPal** Sandbox (per test) o Business (per produzione)
### Passo 1: Copia i File
```bash
# Copia tutti i file nella cartella del tuo web server
# Esempio per XAMPP/WAMP:
C:\xampp\htdocs\pilates-platform\
# Esempio per Linux:
/var/www/html/pilates-platform/
```
### Passo 2: Crea il Database
1. Apri phpMyAdmin (o il tuo client MySQL preferito)
2. Esegui lo script `database/schema.sql`
3. Questo creerà:
- Il database `pilates_platform`
- Tutte le tabelle necessarie
- Un utente amministratore di default
- Alcune lezioni demo di esempio
**Credenziali Admin di Default:**
- Email: `admin@pilatesstudio.com`
- Password: `admin123`
- ⚠️ **IMPORTANTE**: Cambia questa password subito dopo il primo accesso!
### Passo 3: Configura l'Applicazione
Apri il file `includes/config.php` e modifica:
#### Database
```php
define('DB_HOST', 'localhost'); // Host del database
define('DB_NAME', 'pilates_platform'); // Nome database
define('DB_USER', 'root'); // Username (es: root per localhost)
define('DB_PASS', ''); // Password database
```
#### Sito
```php
define('SITE_URL', 'http://localhost/pilates-platform'); // URL del sito
define('ADMIN_EMAIL', 'tua-email@esempio.com'); // Tua email
```
#### Sicurezza
```php
// Genera una chiave casuale sicura (es: usando password_hash)
define('SECRET_KEY', 'CAMBIA-QUESTA-CHIAVE-CON-STRINGA-CASUALE-LUNGA-E-SICURA');
```
#### PayPal Sandbox (per Test)
1. Vai su [PayPal Developer](https://developer.paypal.com/)
2. Accedi con il tuo account PayPal
3. Vai su "My Apps & Credentials"
4. Crea un'app nella sezione Sandbox
5. Copia Client ID e Secret
```php
define('PAYPAL_SANDBOX', true); // true per test, false per produzione
define('PAYPAL_CLIENT_ID', 'il-tuo-client-id-sandbox');
define('PAYPAL_SECRET', 'il-tuo-secret-sandbox');
```
### Passo 4: Configura i Permessi (Linux/Mac)
```bash
# Rendi scrivibile la cartella uploads
chmod 755 uploads/
```
### Passo 5: Avvia il Server
#### Con PHP Built-in (per sviluppo)
```bash
cd pilates-platform
php -S localhost:8000
```
Poi apri: `http://localhost:8000`
#### Con XAMPP/WAMP
1. Avvia Apache e MySQL
2. Apri: `http://localhost/pilates-platform`
---
## 📖 Guida all'Uso
### Per Amministratori
#### Primo Accesso
1. Vai su `http://tuo-sito/login.php`
2. Accedi con le credenziali di default
3. Vai su "Area Admin" → "Profilo" e cambia la password
#### Creare una Nuova Lezione
1. **Area Admin****Gestione Lezioni****Nuova Lezione**
2. Compila i campi:
- **Titolo**: Nome della lezione
- **Descrizione**: Descrizione dettagliata
- **Tipo**: Scegli Video o Live
- **Per Video**:
- Piattaforma: Local, YouTube, Vimeo, o S3
- URL Video: Link al video
- Durata: Minuti
- **Per Live**:
- Piattaforma: Zoom, Google Meet, ecc.
- Link: URL della sessione
- Data e Ora: Quando si terrà
- **Livello**: Principiante, Intermedio, Avanzato
- **Categoria**: Es. Mat Work, Reformer, Stretching
- **Prezzo**: In euro
- **Opzioni**:
- ✓ Lezione Demo (gratuita per tutti)
- ✓ Lezione Attiva (visibile agli utenti)
3. Clicca **Crea Lezione**
#### Gestire gli Utenti
1. **Area Admin****Gestione Utenti**
2. Puoi vedere:
- Tutti gli utenti registrati
- Numero acquisti per utente
- Totale speso
- Data ultimo accesso
3. Azioni disponibili:
- **Blocca/Sblocca**: Disabilita temporaneamente un account
#### Visualizzare le Statistiche
La **Dashboard** mostra:
- 📊 Utenti registrati
- 🎥 Lezioni totali
- 💰 Acquisti completati
- 💵 Guadagni totali
- 📈 Lezioni più vendute
- 📋 Ultimi acquisti
### Per Utenti
#### Registrazione
1. Vai su `http://tuo-sito/register.php`
2. Compila il form con:
- Nome e Cognome
- Email
- Password (min 6 caratteri)
3. Verrai automaticamente loggato
#### Navigare il Catalogo
1. **Dashboard Utente****Catalogo**
2. Usa i filtri per:
- Tipo (Video/Live)
- Livello (Principiante/Intermedio/Avanzato)
3. Clicca su una lezione per vedere i dettagli
#### Acquistare una Lezione
1. Apri una lezione dal catalogo
2. Se non è demo, vedrai il prezzo
3. Clicca sul pulsante PayPal
4. Completa il pagamento
5. La lezione apparirà in "Le Mie Lezioni"
#### Guardare una Lezione
1. **Dashboard****Le Mie Lezioni**
2. Clicca su **Guarda** (per video) o **Partecipa** (per live)
3. Accesso illimitato per sempre!
#### Modificare il Profilo
1. **Dashboard****Profilo**
2. Puoi cambiare:
- Nome e Cognome
- Email
- Telefono
- Password
---
## 🎨 Personalizzazione
### Colori
Apri `assets/css/style.css` e modifica le variabili CSS:
```css
:root {
--primary-color: #4A90E2; /* Celeste principale */
--primary-light: #7AB8F5; /* Celeste chiaro */
--primary-dark: #357ABD; /* Celeste scuro */
--secondary-color: #E8F4F8; /* Celeste molto chiaro */
}
```
### Logo
Sostituisci il testo "Pilates Studio" in `index.php` e negli altri file con:
```html
<img src="assets/images/logo.png" alt="Logo" class="logo">
```
### Email
Per usare SMTP invece di `mail()` PHP, decomenta in `includes/config.php`:
```php
define('SMTP_HOST', 'smtp.gmail.com');
define('SMTP_PORT', 587);
define('SMTP_USERNAME', 'tua-email@gmail.com');
define('SMTP_PASSWORD', 'tua-app-password');
define('SMTP_ENCRYPTION', 'tls');
```
---
## 🎥 Hosting Video
### Opzione 1: YouTube (Consigliato per Iniziare)
1. Carica video su YouTube come **Non in elenco**
2. Copia l'URL (es: `https://youtube.com/watch?v=ABC123`)
3. Quando crei la lezione:
- Piattaforma: YouTube
- URL: Incolla il link
### Opzione 2: Vimeo
1. Carica su Vimeo
2. Imposta privacy su "Nascosto"
3. Copia URL e usa come YouTube
### Opzione 3: File Locali
1. Carica il file in `uploads/videos/`
2. URL Video: `/uploads/videos/nome-file.mp4`
3. ⚠️ Non consigliato per file grandi (limiti server)
### Opzione 4: AWS S3 (Professionale)
Per grandi quantità di video, usa Amazon S3:
1. Crea un bucket S3
2. Configura in `includes/config.php`:
```php
define('AWS_ACCESS_KEY', 'tua-key');
define('AWS_SECRET_KEY', 'tua-secret');
define('AWS_REGION', 'eu-west-1');
define('AWS_BUCKET', 'pilates-videos');
```
3. Carica video su S3
4. Usa URL S3 nelle lezioni
---
## 💳 Configurare PayPal
### Modalità Test (Sandbox)
1. Vai su [developer.paypal.com](https://developer.paypal.com)
2. Crea un'app Sandbox
3. Copia Client ID e Secret in `config.php`
4. Usa gli account test per simulare pagamenti
### Modalità Produzione (Reale)
1. Vai su [paypal.com/businessmanage](https://www.paypal.com/businessmanage)
2. Crea app Live
3. Copia credenziali Live
4. In `config.php`:
```php
define('PAYPAL_SANDBOX', false); // Passa a false!
define('PAYPAL_LIVE_CLIENT_ID', 'tuo-client-id-live');
define('PAYPAL_LIVE_SECRET', 'tuo-secret-live');
```
---
## 🔒 Sicurezza
### Checklist Produzione
Prima di mettere online:
- [ ] Cambia password admin di default
- [ ] Cambia `SECRET_KEY` in `config.php`
- [ ] Imposta `DEBUG_MODE` a `false`
- [ ] Usa HTTPS (certificato SSL)
- [ ] Limita permessi cartelle (755 per cartelle, 644 per file)
- [ ] Backup regolari del database
- [ ] Configura SMTP per email
- [ ] Testa tutti i flussi di pagamento
- [ ] Verifica che `includes/config.php` NON sia accessibile via web
### File .htaccess (per Apache)
Crea `.htaccess` nella cartella `includes/`:
```apache
# Nega accesso a config.php
<Files "config.php">
Require all denied
</Files>
```
---
## 🛠️ Risoluzione Problemi
### Errore: "Errore connessione database"
- Verifica che MySQL sia avviato
- Controlla credenziali in `config.php`
- Assicurati che il database esista
### Le email non vengono inviate
- Configura SMTP in `config.php`
- Verifica che `mail()` PHP funzioni sul tuo server
- Controlla la cartella spam
### I video non si vedono
- Verifica che l'URL sia corretto
- Per YouTube/Vimeo: usa URL diretti al video
- Per file locali: controlla i permessi della cartella
### PayPal non funziona
- Verifica Client ID e Secret
- In test, usa modalità Sandbox
- Controlla console browser per errori JavaScript
### La pagina è bianca / Errore 500
- Attiva `DEBUG_MODE` in `config.php`
- Controlla log errori PHP
- Verifica permessi file (644) e cartelle (755)
---
## 📁 Struttura File
```
pilates-platform/
├── admin/ # Area amministratore
│ ├── dashboard.php # Dashboard admin
│ ├── lessons.php # Gestione lezioni
│ ├── lesson_create.php # Crea lezione
│ ├── lesson_edit.php # Modifica lezione (da creare se serve)
│ ├── users.php # Gestione utenti
│ └── purchases.php # Storico acquisti
├── assets/ # Risorse statiche
│ ├── css/
│ │ └── style.css # Stili principali
│ └── js/
│ └── main.js # JavaScript
├── database/
│ └── schema.sql # Script creazione database
├── includes/ # File PHP condivisi
│ ├── config.php # Configurazione
│ ├── functions.php # Funzioni comuni
│ └── logout.php # Script logout
├── uploads/ # File caricati (da creare)
│ ├── thumbnails/ # Anteprime lezioni
│ └── videos/ # Video locali
├── user/ # Area utente
│ ├── dashboard.php # Dashboard utente
│ ├── catalog.php # Catalogo lezioni
│ └── profile.php # Profilo utente
├── index.php # Homepage
├── login.php # Login
├── register.php # Registrazione
├── forgot_password.php # Recupero password (step 1)
├── reset_password.php # Reset password (step 2)
├── lesson.php # Visualizza lezione
├── process_payment.php # Elabora pagamento PayPal
└── README.md # Questo file
```
---
## 🎓 Concetti per Neofiti
### Cos'è PHP?
PHP è il linguaggio che "genera" le pagine HTML dinamicamente. Quando visiti `index.php`, il server:
1. Esegue il codice PHP
2. Genera l'HTML finale
3. Lo invia al browser
### Cos'è il Database?
MySQL è dove vengono salvati i dati:
- Utenti registrati
- Lezioni create
- Acquisti effettuati
### Come Funziona il Login?
1. Utente inserisce email e password
2. PHP cerca l'email nel database
3. Verifica la password (hashata)
4. Se corretta, crea una "sessione"
5. La sessione ricorda che sei loggato
### Come Funziona PayPal?
1. Utente clicca "Acquista"
2. Si apre popup PayPal
3. Utente paga
4. PayPal conferma il pagamento
5. `process_payment.php` registra l'acquisto nel DB
### Dove Modificare il Codice?
- **Aspetto grafico**: `assets/css/style.css`
- **Comportamento**: File `.php` specifici
- **Database**: Usa phpMyAdmin o client MySQL
- **Impostazioni**: `includes/config.php`
---
## 📞 Supporto e Manutenzione
### Backup Database
Esegui regolarmente (es: settimanalmente):
```bash
mysqldump -u root -p pilates_platform > backup_$(date +%Y%m%d).sql
```
### Aggiornare PHP
Quando aggiorni PHP, testa la piattaforma in locale prima di aggiornare in produzione.
### Log Attività
La tabella `activity_log` traccia:
- Login/Logout
- Acquisti
- Modifiche importanti
Usa per debug e sicurezza.
---
## 📝 Licenza
Questo progetto è stato creato come piattaforma custom. Sei libero di modificarlo e usarlo come preferisci.
---
## ✅ Checklist Prossimi Passi
Dopo l'installazione:
- [ ] Cambia password admin
- [ ] Configura PayPal Sandbox
- [ ] Carica almeno 3 lezioni demo
- [ ] Testa registrazione utente
- [ ] Testa acquisto lezione (in sandbox)
- [ ] Personalizza colori e logo
- [ ] Configura backup automatici
- [ ] Passa a produzione PayPal
- [ ] Attiva HTTPS
- [ ] Lancia! 🚀
---
**Buon lavoro con la tua piattaforma Pilates! 🧘‍♀️**
Per domande o problemi, verifica sempre i log errori PHP e la console browser.

203
admin/dashboard.php Normal file
View File

@@ -0,0 +1,203 @@
<?php
/**
* Dashboard Amministratore
*
* Panoramica generale con statistiche e azioni rapide
*/
require_once '../includes/config.php';
require_once '../includes/functions.php';
session_start();
check_session_timeout();
require_admin();
// Ottieni statistiche
$pdo = get_db_connection();
// Conta utenti totali
$stmt = $pdo->query("SELECT COUNT(*) as count FROM users WHERE deleted_at IS NULL AND is_admin = 0");
$total_users = $stmt->fetch()['count'];
// Conta lezioni totali
$stmt = $pdo->query("SELECT COUNT(*) as count FROM lessons WHERE deleted_at IS NULL");
$total_lessons = $stmt->fetch()['count'];
// Conta acquisti completati
$stmt = $pdo->query("SELECT COUNT(*) as count FROM purchases WHERE status = 'completed'");
$total_purchases = $stmt->fetch()['count'];
// Totale guadagni
$stmt = $pdo->query("SELECT SUM(amount) as total FROM purchases WHERE status = 'completed'");
$total_revenue = $stmt->fetch()['total'] ?? 0;
// Ultimi acquisti
$stmt = $pdo->query("
SELECT p.*, u.first_name, u.last_name, u.email, l.title as lesson_title
FROM purchases p
INNER JOIN users u ON p.user_id = u.id
INNER JOIN lessons l ON p.lesson_id = l.id
WHERE p.status = 'completed'
ORDER BY p.purchased_at DESC
LIMIT 10
");
$recent_purchases = $stmt->fetchAll();
// Lezioni più vendute
$stmt = $pdo->query("
SELECT l.*, COUNT(p.id) as sales_count
FROM lessons l
LEFT JOIN purchases p ON l.id = p.lesson_id AND p.status = 'completed'
WHERE l.deleted_at IS NULL AND l.is_demo = 0
GROUP BY l.id
ORDER BY sales_count DESC
LIMIT 5
");
$top_lessons = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard Admin - Pilates Platform</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<header class="header">
<div class="container">
<div class="header-content">
<h1 class="logo">Pilates Studio - Admin</h1>
<nav class="nav">
<a href="../index.php" class="btn btn-outline">Vedi Sito</a>
<a href="../includes/logout.php" class="btn btn-secondary">Logout</a>
</nav>
</div>
</div>
</header>
<div class="container">
<div class="dashboard">
<!-- Sidebar -->
<aside class="sidebar">
<ul class="sidebar-menu">
<li><a href="dashboard.php" class="active">📊 Dashboard</a></li>
<li><a href="lessons.php">🎥 Gestione Lezioni</a></li>
<li><a href="users.php">👥 Gestione Utenti</a></li>
<li><a href="purchases.php">💰 Acquisti</a></li>
</ul>
</aside>
<!-- Main Content -->
<main class="main-content">
<h2 class="section-title" style="text-align: left;">Dashboard</h2>
<?php echo display_flash_message(); ?>
<!-- Statistiche -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value"><?php echo $total_users; ?></div>
<div class="stat-label">Utenti Registrati</div>
</div>
<div class="stat-card">
<div class="stat-value"><?php echo $total_lessons; ?></div>
<div class="stat-label">Lezioni Totali</div>
</div>
<div class="stat-card">
<div class="stat-value"><?php echo $total_purchases; ?></div>
<div class="stat-label">Acquisti</div>
</div>
<div class="stat-card">
<div class="stat-value"><?php echo format_price($total_revenue); ?></div>
<div class="stat-label">Guadagni Totali</div>
</div>
</div>
<!-- Azioni rapide -->
<div class="card">
<h3 class="card-header">Azioni Rapide</h3>
<div class="d-flex gap-1">
<a href="lesson_create.php" class="btn btn-primary"> Nuova Lezione</a>
<a href="lessons.php" class="btn btn-secondary">📋 Vedi Tutte le Lezioni</a>
<a href="users.php" class="btn btn-secondary">👥 Gestisci Utenti</a>
</div>
</div>
<!-- Lezioni più vendute -->
<div class="card">
<h3 class="card-header">Lezioni Più Vendute</h3>
<?php if (!empty($top_lessons)): ?>
<table class="table">
<thead>
<tr>
<th>Lezione</th>
<th>Tipo</th>
<th>Prezzo</th>
<th>Vendite</th>
</tr>
</thead>
<tbody>
<?php foreach ($top_lessons as $lesson): ?>
<tr>
<td>
<strong><?php echo htmlspecialchars($lesson['title']); ?></strong>
<?php if ($lesson['is_demo']): ?>
<span class="text-success"> (Demo)</span>
<?php endif; ?>
</td>
<td><?php echo ucfirst($lesson['type']); ?></td>
<td><?php echo format_price($lesson['price']); ?></td>
<td><strong><?php echo $lesson['sales_count']; ?></strong></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p class="text-muted">Nessuna lezione venduta ancora.</p>
<?php endif; ?>
</div>
<!-- Ultimi acquisti -->
<div class="card">
<h3 class="card-header">Ultimi Acquisti</h3>
<?php if (!empty($recent_purchases)): ?>
<table class="table">
<thead>
<tr>
<th>Data</th>
<th>Utente</th>
<th>Lezione</th>
<th>Importo</th>
</tr>
</thead>
<tbody>
<?php foreach ($recent_purchases as $purchase): ?>
<tr>
<td><?php echo format_datetime($purchase['purchased_at']); ?></td>
<td>
<?php echo htmlspecialchars($purchase['first_name'] . ' ' . $purchase['last_name']); ?>
<br>
<small class="text-muted"><?php echo htmlspecialchars($purchase['email']); ?></small>
</td>
<td><?php echo htmlspecialchars($purchase['lesson_title']); ?></td>
<td><strong><?php echo format_price($purchase['amount']); ?></strong></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p class="text-muted">Nessun acquisto ancora.</p>
<?php endif; ?>
</div>
</main>
</div>
</div>
<script src="../assets/js/main.js"></script>
</body>
</html>

286
admin/lesson_create.php Normal file
View File

@@ -0,0 +1,286 @@
<?php
/**
* Crea Nuova Lezione
*
* Form per creare una nuova videolezione o lezione live
*/
require_once '../includes/config.php';
require_once '../includes/functions.php';
session_start();
check_session_timeout();
require_admin();
$error = '';
$success = false;
// Processa il form
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = sanitize_input($_POST['title'] ?? '');
$description = sanitize_input($_POST['description'] ?? '');
$type = $_POST['type'] ?? 'video';
$level = $_POST['level'] ?? 'principiante';
$category = sanitize_input($_POST['category'] ?? '');
$price = floatval($_POST['price'] ?? 0);
$duration = !empty($_POST['duration']) ? intval($_POST['duration']) : null;
$video_url = sanitize_input($_POST['video_url'] ?? '');
$video_platform = $_POST['video_platform'] ?? 'local';
$live_url = sanitize_input($_POST['live_url'] ?? '');
$live_platform = sanitize_input($_POST['live_platform'] ?? '');
$live_date = !empty($_POST['live_date']) ? $_POST['live_date'] : null;
$is_demo = isset($_POST['is_demo']) ? 1 : 0;
$is_active = isset($_POST['is_active']) ? 1 : 0;
// Validazione
if (empty($title)) {
$error = 'Il titolo è obbligatorio';
} elseif (empty($description)) {
$error = 'La descrizione è obbligatoria';
} elseif ($type === 'live' && empty($live_date)) {
$error = 'Per le lezioni live, la data è obbligatoria';
} else {
$pdo = get_db_connection();
try {
$stmt = $pdo->prepare("
INSERT INTO lessons (
title, description, type, video_url, video_platform,
duration, live_platform, live_url, live_date,
level, category, price, is_demo, is_active,
created_by, created_at
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()
)
");
$stmt->execute([
$title,
$description,
$type,
$type === 'video' ? $video_url : null,
$type === 'video' ? $video_platform : null,
$duration,
$type === 'live' ? $live_platform : null,
$type === 'live' ? $live_url : null,
$live_date,
$level,
$category,
$price,
$is_demo,
$is_active,
$_SESSION['user_id']
]);
set_flash_message('success', 'Lezione creata con successo!');
header('Location: lessons.php');
exit;
} catch (PDOException $e) {
$error = 'Errore durante la creazione della lezione';
}
}
}
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nuova Lezione - Admin</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<header class="header">
<div class="container">
<div class="header-content">
<h1 class="logo">Pilates Studio - Admin</h1>
<nav class="nav">
<a href="lessons.php" class="btn btn-outline">← Torna alle Lezioni</a>
<a href="../includes/logout.php" class="btn btn-secondary">Logout</a>
</nav>
</div>
</div>
</header>
<div class="container">
<div class="dashboard">
<aside class="sidebar">
<ul class="sidebar-menu">
<li><a href="dashboard.php">📊 Dashboard</a></li>
<li><a href="lessons.php" class="active">🎥 Gestione Lezioni</a></li>
<li><a href="users.php">👥 Gestione Utenti</a></li>
<li><a href="purchases.php">💰 Acquisti</a></li>
</ul>
</aside>
<main class="main-content">
<h2 class="section-title" style="text-align: left;">Crea Nuova Lezione</h2>
<?php if ($error): ?>
<div class="alert alert-error"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<div class="card">
<form method="POST" action="">
<!-- Informazioni Base -->
<h3 style="margin-bottom: 1rem; color: var(--primary-color);">Informazioni Base</h3>
<div class="form-group">
<label for="title" class="form-label">Titolo *</label>
<input type="text" id="title" name="title" class="form-control" required
value="<?php echo htmlspecialchars($_POST['title'] ?? ''); ?>">
</div>
<div class="form-group">
<label for="description" class="form-label">Descrizione *</label>
<textarea id="description" name="description" class="form-control" required
rows="4"><?php echo htmlspecialchars($_POST['description'] ?? ''); ?></textarea>
</div>
<div class="form-group">
<label for="type" class="form-label">Tipo Lezione *</label>
<select id="type" name="type" class="form-control" required onchange="toggleTypeFields()">
<option value="video" <?php echo ($_POST['type'] ?? 'video') === 'video' ? 'selected' : ''; ?>>
Videolezione Registrata
</option>
<option value="live" <?php echo ($_POST['type'] ?? '') === 'live' ? 'selected' : ''; ?>>
Lezione Live
</option>
</select>
</div>
<!-- Campi Video -->
<div id="video-fields" style="display: <?php echo ($_POST['type'] ?? 'video') === 'video' ? 'block' : 'none'; ?>;">
<h3 style="margin: 2rem 0 1rem; color: var(--primary-color);">Dettagli Video</h3>
<div class="form-group">
<label for="video_platform" class="form-label">Piattaforma Video</label>
<select id="video_platform" name="video_platform" class="form-control">
<option value="local">File Locale</option>
<option value="youtube">YouTube</option>
<option value="vimeo">Vimeo</option>
<option value="s3">AWS S3</option>
</select>
</div>
<div class="form-group">
<label for="video_url" class="form-label">URL/Percorso Video</label>
<input type="text" id="video_url" name="video_url" class="form-control"
placeholder="es: https://youtube.com/watch?v=..."
value="<?php echo htmlspecialchars($_POST['video_url'] ?? ''); ?>">
<small class="text-muted">Lascia vuoto se caricherai il video successivamente</small>
</div>
<div class="form-group">
<label for="duration" class="form-label">Durata (minuti)</label>
<input type="number" id="duration" name="duration" class="form-control"
min="1" value="<?php echo htmlspecialchars($_POST['duration'] ?? ''); ?>">
</div>
</div>
<!-- Campi Live -->
<div id="live-fields" style="display: <?php echo ($_POST['type'] ?? '') === 'live' ? 'block' : 'none'; ?>;">
<h3 style="margin: 2rem 0 1rem; color: var(--primary-color);">Dettagli Lezione Live</h3>
<div class="form-group">
<label for="live_platform" class="form-label">Piattaforma Live</label>
<input type="text" id="live_platform" name="live_platform" class="form-control"
placeholder="es: Zoom, Google Meet, Teams..."
value="<?php echo htmlspecialchars($_POST['live_platform'] ?? ''); ?>">
</div>
<div class="form-group">
<label for="live_url" class="form-label">Link Lezione Live</label>
<input type="text" id="live_url" name="live_url" class="form-control"
placeholder="es: https://zoom.us/j/..."
value="<?php echo htmlspecialchars($_POST['live_url'] ?? ''); ?>">
</div>
<div class="form-group">
<label for="live_date" class="form-label">Data e Ora Lezione *</label>
<input type="datetime-local" id="live_date" name="live_date" class="form-control"
value="<?php echo htmlspecialchars($_POST['live_date'] ?? ''); ?>">
</div>
</div>
<!-- Classificazione -->
<h3 style="margin: 2rem 0 1rem; color: var(--primary-color);">Classificazione</h3>
<div class="form-group">
<label for="level" class="form-label">Livello *</label>
<select id="level" name="level" class="form-control" required>
<option value="principiante" <?php echo ($_POST['level'] ?? 'principiante') === 'principiante' ? 'selected' : ''; ?>>
Principiante
</option>
<option value="intermedio" <?php echo ($_POST['level'] ?? '') === 'intermedio' ? 'selected' : ''; ?>>
Intermedio
</option>
<option value="avanzato" <?php echo ($_POST['level'] ?? '') === 'avanzato' ? 'selected' : ''; ?>>
Avanzato
</option>
</select>
</div>
<div class="form-group">
<label for="category" class="form-label">Categoria</label>
<input type="text" id="category" name="category" class="form-control"
placeholder="es: Mat Work, Reformer, Stretching..."
value="<?php echo htmlspecialchars($_POST['category'] ?? ''); ?>">
</div>
<!-- Prezzo e Disponibilità -->
<h3 style="margin: 2rem 0 1rem; color: var(--primary-color);">Prezzo e Disponibilità</h3>
<div class="form-group">
<label for="price" class="form-label">Prezzo (€) *</label>
<input type="number" id="price" name="price" class="form-control"
min="0" step="0.01" required
value="<?php echo htmlspecialchars($_POST['price'] ?? '0'); ?>">
</div>
<div class="form-group">
<label class="form-label">
<input type="checkbox" name="is_demo" value="1"
<?php echo isset($_POST['is_demo']) ? 'checked' : ''; ?>>
Lezione Demo (gratuita per tutti)
</label>
</div>
<div class="form-group">
<label class="form-label">
<input type="checkbox" name="is_active" value="1"
<?php echo !isset($_POST['is_active']) || $_POST['is_active'] ? 'checked' : ''; ?>>
Lezione attiva (visibile agli utenti)
</label>
</div>
<div class="d-flex gap-1 mt-3">
<button type="submit" class="btn btn-primary">Crea Lezione</button>
<a href="lessons.php" class="btn btn-outline">Annulla</a>
</div>
</form>
</div>
</main>
</div>
</div>
<script>
// Mostra/nascondi campi in base al tipo di lezione
function toggleTypeFields() {
const type = document.getElementById('type').value;
const videoFields = document.getElementById('video-fields');
const liveFields = document.getElementById('live-fields');
if (type === 'video') {
videoFields.style.display = 'block';
liveFields.style.display = 'none';
} else {
videoFields.style.display = 'none';
liveFields.style.display = 'block';
}
}
</script>
<script src="../assets/js/main.js"></script>
</body>
</html>

148
admin/lessons.php Normal file
View File

@@ -0,0 +1,148 @@
<?php
/**
* Gestione Lezioni - Lista tutte le lezioni
*
* Visualizza tutte le lezioni con opzioni per modificare ed eliminare
*/
require_once '../includes/config.php';
require_once '../includes/functions.php';
session_start();
check_session_timeout();
require_admin();
// Gestione eliminazione
if (isset($_GET['delete']) && is_numeric($_GET['delete'])) {
$lesson_id = (int)$_GET['delete'];
$pdo = get_db_connection();
// Soft delete
$stmt = $pdo->prepare("UPDATE lessons SET deleted_at = NOW() WHERE id = ?");
if ($stmt->execute([$lesson_id])) {
set_flash_message('success', 'Lezione eliminata con successo');
} else {
set_flash_message('error', 'Errore durante l\'eliminazione');
}
header('Location: lessons.php');
exit;
}
// Ottieni tutte le lezioni
$pdo = get_db_connection();
$stmt = $pdo->query("
SELECT l.*,
COUNT(DISTINCT p.id) as purchase_count,
SUM(CASE WHEN p.status = 'completed' THEN p.amount ELSE 0 END) as total_revenue
FROM lessons l
LEFT JOIN purchases p ON l.id = p.lesson_id
WHERE l.deleted_at IS NULL
GROUP BY l.id
ORDER BY l.created_at DESC
");
$lessons = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gestione Lezioni - Admin</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<header class="header">
<div class="container">
<div class="header-content">
<h1 class="logo">Pilates Studio - Admin</h1>
<nav class="nav">
<a href="../index.php" class="btn btn-outline">Vedi Sito</a>
<a href="../includes/logout.php" class="btn btn-secondary">Logout</a>
</nav>
</div>
</div>
</header>
<div class="container">
<div class="dashboard">
<!-- Sidebar -->
<aside class="sidebar">
<ul class="sidebar-menu">
<li><a href="dashboard.php">📊 Dashboard</a></li>
<li><a href="lessons.php" class="active">🎥 Gestione Lezioni</a></li>
<li><a href="users.php">👥 Gestione Utenti</a></li>
<li><a href="purchases.php">💰 Acquisti</a></li>
</ul>
</aside>
<!-- Main Content -->
<main class="main-content">
<div class="d-flex justify-between align-center mb-2">
<h2 class="section-title" style="text-align: left; margin: 0;">Gestione Lezioni</h2>
<a href="lesson_create.php" class="btn btn-primary"> Nuova Lezione</a>
</div>
<?php echo display_flash_message(); ?>
<div class="card">
<?php if (!empty($lessons)): ?>
<table class="table">
<thead>
<tr>
<th>Titolo</th>
<th>Tipo</th>
<th>Livello</th>
<th>Prezzo</th>
<th>Vendite</th>
<th>Guadagno</th>
<th>Status</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
<?php foreach ($lessons as $lesson): ?>
<tr>
<td>
<strong><?php echo htmlspecialchars($lesson['title']); ?></strong>
<?php if ($lesson['is_demo']): ?>
<br><span class="text-success"><small>✓ Demo</small></span>
<?php endif; ?>
</td>
<td><?php echo ucfirst($lesson['type']); ?></td>
<td><?php echo ucfirst($lesson['level']); ?></td>
<td><?php echo format_price($lesson['price']); ?></td>
<td><?php echo $lesson['purchase_count']; ?></td>
<td><strong><?php echo format_price($lesson['total_revenue'] ?? 0); ?></strong></td>
<td>
<?php if ($lesson['is_active']): ?>
<span class="text-success">Attiva</span>
<?php else: ?>
<span class="text-muted">Disattiva</span>
<?php endif; ?>
</td>
<td>
<a href="lesson_edit.php?id=<?php echo $lesson['id']; ?>"
class="btn btn-small btn-secondary">Modifica</a>
<a href="lessons.php?delete=<?php echo $lesson['id']; ?>"
class="btn btn-small btn-danger"
data-confirm-delete>Elimina</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p class="text-muted text-center">Nessuna lezione disponibile. <a href="lesson_create.php">Creane una!</a></p>
<?php endif; ?>
</div>
</main>
</div>
</div>
<script src="../assets/js/main.js"></script>
</body>
</html>

165
admin/purchases.php Normal file
View File

@@ -0,0 +1,165 @@
<?php
/**
* Visualizza Acquisti
*
* Lista di tutti gli acquisti effettuati sulla piattaforma
*/
require_once '../includes/config.php';
require_once '../includes/functions.php';
session_start();
check_session_timeout();
require_admin();
// Ottieni tutti gli acquisti
$pdo = get_db_connection();
$stmt = $pdo->query("
SELECT p.*,
u.first_name, u.last_name, u.email,
l.title as lesson_title, l.type as lesson_type
FROM purchases p
INNER JOIN users u ON p.user_id = u.id
INNER JOIN lessons l ON p.lesson_id = l.id
ORDER BY p.created_at DESC
");
$purchases = $stmt->fetchAll();
// Statistiche
$stmt = $pdo->query("
SELECT
COUNT(*) as total_purchases,
SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END) as total_revenue,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count,
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed_count
FROM purchases
");
$stats = $stmt->fetch();
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Acquisti - Admin</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<header class="header">
<div class="container">
<div class="header-content">
<h1 class="logo">Pilates Studio - Admin</h1>
<nav class="nav">
<a href="../index.php" class="btn btn-outline">Vedi Sito</a>
<a href="../includes/logout.php" class="btn btn-secondary">Logout</a>
</nav>
</div>
</div>
</header>
<div class="container">
<div class="dashboard">
<!-- Sidebar -->
<aside class="sidebar">
<ul class="sidebar-menu">
<li><a href="dashboard.php">📊 Dashboard</a></li>
<li><a href="lessons.php">🎥 Gestione Lezioni</a></li>
<li><a href="users.php">👥 Gestione Utenti</a></li>
<li><a href="purchases.php" class="active">💰 Acquisti</a></li>
</ul>
</aside>
<!-- Main Content -->
<main class="main-content">
<h2 class="section-title" style="text-align: left;">Storico Acquisti</h2>
<!-- Statistiche rapide -->
<div class="stats-grid" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));">
<div class="stat-card">
<div class="stat-value"><?php echo $stats['total_purchases']; ?></div>
<div class="stat-label">Totale Transazioni</div>
</div>
<div class="stat-card" style="background: linear-gradient(135deg, #2ECC71, #27AE60);">
<div class="stat-value"><?php echo format_price($stats['total_revenue']); ?></div>
<div class="stat-label">Incassi Totali</div>
</div>
<div class="stat-card" style="background: linear-gradient(135deg, #F39C12, #E67E22);">
<div class="stat-value"><?php echo $stats['pending_count']; ?></div>
<div class="stat-label">In Attesa</div>
</div>
<div class="stat-card" style="background: linear-gradient(135deg, #E74C3C, #C0392B);">
<div class="stat-value"><?php echo $stats['failed_count']; ?></div>
<div class="stat-label">Falliti</div>
</div>
</div>
<div class="card">
<?php if (!empty($purchases)): ?>
<table class="table">
<thead>
<tr>
<th>Data</th>
<th>Utente</th>
<th>Lezione</th>
<th>Tipo</th>
<th>Importo</th>
<th>Status</th>
<th>PayPal ID</th>
</tr>
</thead>
<tbody>
<?php foreach ($purchases as $purchase): ?>
<tr>
<td>
<?php
echo $purchase['purchased_at']
? format_datetime($purchase['purchased_at'])
: format_datetime($purchase['created_at']);
?>
</td>
<td>
<?php echo htmlspecialchars($purchase['first_name'] . ' ' . $purchase['last_name']); ?>
<br>
<small class="text-muted"><?php echo htmlspecialchars($purchase['email']); ?></small>
</td>
<td><?php echo htmlspecialchars($purchase['lesson_title']); ?></td>
<td><?php echo ucfirst($purchase['lesson_type']); ?></td>
<td><strong><?php echo format_price($purchase['amount']); ?></strong></td>
<td>
<?php
$status_colors = [
'completed' => 'text-success',
'pending' => 'text-warning',
'failed' => 'text-danger',
'refunded' => 'text-muted'
];
$color = $status_colors[$purchase['status']] ?? '';
?>
<span class="<?php echo $color; ?>">
<?php echo ucfirst($purchase['status']); ?>
</span>
</td>
<td>
<small class="text-muted">
<?php echo $purchase['paypal_order_id'] ? htmlspecialchars($purchase['paypal_order_id']) : '-'; ?>
</small>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p class="text-muted text-center">Nessun acquisto ancora.</p>
<?php endif; ?>
</div>
</main>
</div>
</div>
<script src="../assets/js/main.js"></script>
</body>
</html>

145
admin/users.php Normal file
View File

@@ -0,0 +1,145 @@
<?php
/**
* Gestione Utenti
*
* Visualizza tutti gli utenti registrati con opzioni di gestione
*/
require_once '../includes/config.php';
require_once '../includes/functions.php';
session_start();
check_session_timeout();
require_admin();
// Gestione blocco/sblocco utente
if (isset($_GET['toggle_active']) && is_numeric($_GET['toggle_active'])) {
$user_id = (int)$_GET['toggle_active'];
$pdo = get_db_connection();
$stmt = $pdo->prepare("UPDATE users SET is_active = NOT is_active WHERE id = ?");
if ($stmt->execute([$user_id])) {
set_flash_message('success', 'Stato utente aggiornato');
} else {
set_flash_message('error', 'Errore durante l\'aggiornamento');
}
header('Location: users.php');
exit;
}
// Ottieni tutti gli utenti non admin
$pdo = get_db_connection();
$stmt = $pdo->query("
SELECT u.*,
COUNT(DISTINCT p.id) as purchase_count,
SUM(CASE WHEN p.status = 'completed' THEN p.amount ELSE 0 END) as total_spent
FROM users u
LEFT JOIN purchases p ON u.id = p.user_id
WHERE u.is_admin = 0 AND u.deleted_at IS NULL
GROUP BY u.id
ORDER BY u.created_at DESC
");
$users = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gestione Utenti - Admin</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<header class="header">
<div class="container">
<div class="header-content">
<h1 class="logo">Pilates Studio - Admin</h1>
<nav class="nav">
<a href="../index.php" class="btn btn-outline">Vedi Sito</a>
<a href="../includes/logout.php" class="btn btn-secondary">Logout</a>
</nav>
</div>
</div>
</header>
<div class="container">
<div class="dashboard">
<!-- Sidebar -->
<aside class="sidebar">
<ul class="sidebar-menu">
<li><a href="dashboard.php">📊 Dashboard</a></li>
<li><a href="lessons.php">🎥 Gestione Lezioni</a></li>
<li><a href="users.php" class="active">👥 Gestione Utenti</a></li>
<li><a href="purchases.php">💰 Acquisti</a></li>
</ul>
</aside>
<!-- Main Content -->
<main class="main-content">
<h2 class="section-title" style="text-align: left;">Gestione Utenti</h2>
<?php echo display_flash_message(); ?>
<div class="card">
<p class="text-muted mb-2">Totale utenti registrati: <strong><?php echo count($users); ?></strong></p>
<?php if (!empty($users)): ?>
<table class="table">
<thead>
<tr>
<th>Nome</th>
<th>Email</th>
<th>Registrato il</th>
<th>Ultimo Accesso</th>
<th>Acquisti</th>
<th>Speso</th>
<th>Status</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td>
<strong><?php echo htmlspecialchars($user['first_name'] . ' ' . $user['last_name']); ?></strong>
</td>
<td><?php echo htmlspecialchars($user['email']); ?></td>
<td><?php echo format_date($user['created_at']); ?></td>
<td>
<?php
echo $user['last_login'] ? format_datetime($user['last_login']) : 'Mai';
?>
</td>
<td><?php echo $user['purchase_count']; ?></td>
<td><strong><?php echo format_price($user['total_spent'] ?? 0); ?></strong></td>
<td>
<?php if ($user['is_active']): ?>
<span class="text-success">✓ Attivo</span>
<?php else: ?>
<span class="text-danger">✗ Bloccato</span>
<?php endif; ?>
</td>
<td>
<a href="users.php?toggle_active=<?php echo $user['id']; ?>"
class="btn btn-small <?php echo $user['is_active'] ? 'btn-danger' : 'btn-success'; ?>">
<?php echo $user['is_active'] ? 'Blocca' : 'Sblocca'; ?>
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p class="text-muted text-center">Nessun utente registrato ancora.</p>
<?php endif; ?>
</div>
</main>
</div>
</div>
<script src="../assets/js/main.js"></script>
</body>
</html>

683
assets/css/style.css Normal file
View File

@@ -0,0 +1,683 @@
/**
* PILATES PLATFORM - STILI PRINCIPALI
*
* Design minimale con palette bianco e celeste
* Responsive per tutti i dispositivi
*/
/* ============================================
VARIABILI CSS E RESET
============================================ */
:root {
/* Colori principali - Tonalità celeste */
--primary-color: #4A90E2; /* Celeste principale */
--primary-light: #7AB8F5; /* Celeste chiaro */
--primary-dark: #357ABD; /* Celeste scuro */
/* Colori secondari */
--secondary-color: #E8F4F8; /* Celeste molto chiaro per sfondi */
--accent-color: #2ECC71; /* Verde per successo */
--warning-color: #F39C12; /* Arancione per warning */
--danger-color: #E74C3C; /* Rosso per errori */
/* Scala di grigi */
--white: #FFFFFF;
--gray-100: #F8F9FA;
--gray-200: #E9ECEF;
--gray-300: #DEE2E6;
--gray-400: #CED4DA;
--gray-500: #ADB5BD;
--gray-600: #6C757D;
--gray-700: #495057;
--gray-800: #343A40;
--gray-900: #212529;
/* Tipografia */
--font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
--font-size-base: 16px;
--line-height-base: 1.6;
/* Spaziature */
--spacing-xs: 0.25rem; /* 4px */
--spacing-sm: 0.5rem; /* 8px */
--spacing-md: 1rem; /* 16px */
--spacing-lg: 1.5rem; /* 24px */
--spacing-xl: 2rem; /* 32px */
--spacing-2xl: 3rem; /* 48px */
/* Border radius */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
/* Ombre */
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.15);
/* Transizioni */
--transition-fast: 0.15s ease;
--transition-base: 0.3s ease;
}
/* Reset e base */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family);
font-size: var(--font-size-base);
line-height: var(--line-height-base);
color: var(--gray-800);
background-color: var(--white);
overflow-x: hidden;
}
img {
max-width: 100%;
height: auto;
display: block;
}
a {
color: var(--primary-color);
text-decoration: none;
transition: color var(--transition-fast);
}
a:hover {
color: var(--primary-dark);
}
/* ============================================
LAYOUT
============================================ */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-lg);
}
.container-sm {
max-width: 800px;
margin: 0 auto;
padding: 0 var(--spacing-lg);
}
/* ============================================
HEADER E NAVIGAZIONE
============================================ */
.header {
background: var(--white);
box-shadow: var(--shadow-sm);
padding: var(--spacing-lg) 0;
position: sticky;
top: 0;
z-index: 100;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 1.75rem;
font-weight: 700;
color: var(--primary-color);
margin: 0;
}
.nav {
display: flex;
gap: var(--spacing-md);
align-items: center;
}
/* ============================================
BOTTONI
============================================ */
.btn {
display: inline-block;
padding: 0.625rem 1.25rem;
font-size: 1rem;
font-weight: 500;
text-align: center;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-base);
text-decoration: none;
white-space: nowrap;
}
.btn-primary {
background: var(--primary-color);
color: var(--white);
}
.btn-primary:hover {
background: var(--primary-dark);
color: var(--white);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.btn-secondary {
background: var(--secondary-color);
color: var(--primary-color);
}
.btn-secondary:hover {
background: var(--primary-light);
color: var(--white);
}
.btn-outline {
background: transparent;
color: var(--primary-color);
border: 2px solid var(--primary-color);
}
.btn-outline:hover {
background: var(--primary-color);
color: var(--white);
}
.btn-success {
background: var(--accent-color);
color: var(--white);
}
.btn-danger {
background: var(--danger-color);
color: var(--white);
}
.btn-small {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.btn-large {
padding: 1rem 2rem;
font-size: 1.125rem;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* ============================================
HERO SECTION
============================================ */
.hero {
background: linear-gradient(135deg, var(--secondary-color) 0%, var(--white) 100%);
padding: var(--spacing-2xl) 0;
text-align: center;
}
.hero-content {
max-width: 700px;
margin: 0 auto;
}
.hero-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--gray-900);
margin-bottom: var(--spacing-md);
}
.hero-subtitle {
font-size: 1.25rem;
color: var(--gray-600);
margin-bottom: var(--spacing-xl);
}
/* ============================================
SEZIONI
============================================ */
.lessons-section,
.features-section {
padding: var(--spacing-2xl) 0;
}
.section-title {
font-size: 2rem;
font-weight: 700;
text-align: center;
color: var(--gray-900);
margin-bottom: var(--spacing-md);
}
.section-subtitle {
font-size: 1.125rem;
color: var(--gray-600);
text-align: center;
margin-bottom: var(--spacing-xl);
}
/* ============================================
GRIDS
============================================ */
.lessons-grid,
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--spacing-xl);
margin-top: var(--spacing-xl);
}
/* ============================================
LESSON CARD
============================================ */
.lesson-card {
background: var(--white);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-md);
transition: transform var(--transition-base), box-shadow var(--transition-base);
}
.lesson-card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-lg);
}
.lesson-thumbnail,
.lesson-thumbnail-placeholder {
width: 100%;
height: 200px;
object-fit: cover;
background: var(--secondary-color);
}
.lesson-thumbnail-placeholder {
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
}
.lesson-content {
padding: var(--spacing-lg);
}
.lesson-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--gray-900);
margin-bottom: var(--spacing-sm);
}
.lesson-description {
font-size: 0.95rem;
color: var(--gray-600);
margin-bottom: var(--spacing-md);
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.lesson-meta {
display: flex;
gap: var(--spacing-md);
margin-bottom: var(--spacing-lg);
font-size: 0.875rem;
color: var(--gray-600);
}
.lesson-price {
font-size: 1.5rem;
font-weight: 700;
color: var(--primary-color);
margin-bottom: var(--spacing-md);
}
/* ============================================
FEATURE CARD
============================================ */
.feature-card {
text-align: center;
padding: var(--spacing-xl);
background: var(--white);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
transition: box-shadow var(--transition-base);
}
.feature-card:hover {
box-shadow: var(--shadow-md);
}
.feature-icon {
font-size: 3rem;
margin-bottom: var(--spacing-md);
}
.feature-card h3 {
font-size: 1.25rem;
font-weight: 600;
color: var(--gray-900);
margin-bottom: var(--spacing-sm);
}
.feature-card p {
color: var(--gray-600);
}
/* ============================================
FORM
============================================ */
.form-group {
margin-bottom: var(--spacing-lg);
}
.form-label {
display: block;
margin-bottom: var(--spacing-sm);
font-weight: 500;
color: var(--gray-700);
}
.form-control {
width: 100%;
padding: 0.75rem;
font-size: 1rem;
border: 2px solid var(--gray-300);
border-radius: var(--radius-md);
transition: border-color var(--transition-fast);
}
.form-control:focus {
outline: none;
border-color: var(--primary-color);
}
.form-control.error {
border-color: var(--danger-color);
}
.form-error {
color: var(--danger-color);
font-size: 0.875rem;
margin-top: var(--spacing-xs);
}
textarea.form-control {
min-height: 120px;
resize: vertical;
}
select.form-control {
cursor: pointer;
}
/* ============================================
ALERTS
============================================ */
.alert {
padding: var(--spacing-md) var(--spacing-lg);
border-radius: var(--radius-md);
margin-bottom: var(--spacing-lg);
font-weight: 500;
}
.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.alert-warning {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeeba;
}
.alert-info {
background: var(--secondary-color);
color: var(--primary-dark);
border: 1px solid var(--primary-light);
}
/* ============================================
TABELLE
============================================ */
.table {
width: 100%;
border-collapse: collapse;
background: var(--white);
border-radius: var(--radius-md);
overflow: hidden;
box-shadow: var(--shadow-sm);
}
.table thead {
background: var(--secondary-color);
}
.table th,
.table td {
padding: var(--spacing-md);
text-align: left;
border-bottom: 1px solid var(--gray-200);
}
.table th {
font-weight: 600;
color: var(--primary-color);
}
.table tr:hover {
background: var(--gray-100);
}
/* ============================================
CARDS
============================================ */
.card {
background: var(--white);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
box-shadow: var(--shadow-md);
margin-bottom: var(--spacing-xl);
}
.card-header {
font-size: 1.5rem;
font-weight: 600;
color: var(--gray-900);
margin-bottom: var(--spacing-lg);
padding-bottom: var(--spacing-md);
border-bottom: 2px solid var(--secondary-color);
}
/* ============================================
DASHBOARD
============================================ */
.dashboard {
display: grid;
grid-template-columns: 250px 1fr;
min-height: calc(100vh - 80px);
gap: var(--spacing-xl);
}
.sidebar {
background: var(--gray-100);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
}
.sidebar-menu {
list-style: none;
}
.sidebar-menu li {
margin-bottom: var(--spacing-sm);
}
.sidebar-menu a {
display: block;
padding: var(--spacing-md);
color: var(--gray-700);
border-radius: var(--radius-md);
transition: all var(--transition-fast);
}
.sidebar-menu a:hover,
.sidebar-menu a.active {
background: var(--primary-color);
color: var(--white);
}
.main-content {
padding: var(--spacing-xl) 0;
}
/* ============================================
STATISTICHE
============================================ */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-xl);
}
.stat-card {
background: linear-gradient(135deg, var(--primary-light), var(--primary-color));
color: var(--white);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
text-align: center;
}
.stat-value {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: var(--spacing-sm);
}
.stat-label {
font-size: 0.875rem;
opacity: 0.9;
}
/* ============================================
FOOTER
============================================ */
.footer {
background: var(--gray-900);
color: var(--white);
padding: var(--spacing-xl) 0;
text-align: center;
margin-top: var(--spacing-2xl);
}
/* ============================================
UTILITY CLASSES
============================================ */
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-muted { color: var(--gray-600); }
.text-primary { color: var(--primary-color); }
.text-danger { color: var(--danger-color); }
.text-success { color: var(--accent-color); }
.mt-1 { margin-top: var(--spacing-md); }
.mt-2 { margin-top: var(--spacing-lg); }
.mt-3 { margin-top: var(--spacing-xl); }
.mb-1 { margin-bottom: var(--spacing-md); }
.mb-2 { margin-bottom: var(--spacing-lg); }
.mb-3 { margin-bottom: var(--spacing-xl); }
.d-none { display: none; }
.d-block { display: block; }
.d-flex { display: flex; }
.justify-between { justify-content: space-between; }
.align-center { align-items: center; }
.gap-1 { gap: var(--spacing-md); }
/* ============================================
RESPONSIVE
============================================ */
@media (max-width: 768px) {
.hero-title {
font-size: 2rem;
}
.hero-subtitle {
font-size: 1rem;
}
.header-content {
flex-direction: column;
gap: var(--spacing-md);
}
.nav {
flex-wrap: wrap;
justify-content: center;
}
.dashboard {
grid-template-columns: 1fr;
}
.sidebar {
order: 2;
}
.lessons-grid,
.features-grid {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 480px) {
.btn {
width: 100%;
display: block;
}
.btn + .btn {
margin-top: var(--spacing-sm);
}
}

52
assets/js/main.js Normal file
View File

@@ -0,0 +1,52 @@
/**
* JavaScript principale della piattaforma
*/
// Esegui quando il DOM è caricato
document.addEventListener('DOMContentLoaded', function() {
// Auto-chiudi gli alert dopo 5 secondi
const alerts = document.querySelectorAll('.alert');
alerts.forEach(function(alert) {
setTimeout(function() {
alert.style.transition = 'opacity 0.3s ease';
alert.style.opacity = '0';
setTimeout(function() {
alert.remove();
}, 300);
}, 5000);
});
// Conferma eliminazione
const deleteButtons = document.querySelectorAll('[data-confirm-delete]');
deleteButtons.forEach(function(button) {
button.addEventListener('click', function(e) {
if (!confirm('Sei sicuro di voler eliminare questo elemento?')) {
e.preventDefault();
}
});
});
});
/**
* Valida un form prima dell'invio
*/
function validateForm(formId) {
const form = document.getElementById(formId);
if (!form) return false;
let isValid = true;
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(function(field) {
if (!field.value.trim()) {
field.classList.add('error');
isValid = false;
} else {
field.classList.remove('error');
}
});
return isValid;
}

243
database/schema.sql Normal file
View File

@@ -0,0 +1,243 @@
-- ============================================
-- PILATES PLATFORM - DATABASE SCHEMA
-- ============================================
-- Script per creare tutte le tabelle necessarie
-- Esegui questo script nel tuo database MySQL
-- ============================================
-- Crea il database (se non esiste)
CREATE DATABASE IF NOT EXISTS pilates_platform CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE pilates_platform;
-- ============================================
-- TABELLA UTENTI
-- ============================================
-- Memorizza tutti gli utenti registrati (studenti e amministratori)
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL COMMENT 'Password hashata con bcrypt',
first_name VARCHAR(100) NOT NULL COMMENT 'Nome',
last_name VARCHAR(100) NOT NULL COMMENT 'Cognome',
is_admin BOOLEAN DEFAULT FALSE COMMENT 'True se amministratore',
is_active BOOLEAN DEFAULT TRUE COMMENT 'Permette di disabilitare account',
profile_image VARCHAR(255) DEFAULT NULL COMMENT 'URL immagine profilo',
phone VARCHAR(20) DEFAULT NULL COMMENT 'Numero telefono opzionale',
created_at DATETIME NOT NULL COMMENT 'Data registrazione',
updated_at DATETIME DEFAULT NULL COMMENT 'Data ultimo aggiornamento',
last_login DATETIME DEFAULT NULL COMMENT 'Data ultimo accesso',
deleted_at DATETIME DEFAULT NULL COMMENT 'Soft delete - se valorizzato, utente cancellato',
INDEX idx_email (email),
INDEX idx_is_admin (is_admin),
INDEX idx_deleted_at (deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Utenti della piattaforma';
-- ============================================
-- TABELLA LEZIONI
-- ============================================
-- Memorizza sia videolezioni che lezioni live
CREATE TABLE IF NOT EXISTS lessons (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL COMMENT 'Titolo della lezione',
description TEXT NOT NULL COMMENT 'Descrizione dettagliata',
type ENUM('video', 'live') NOT NULL COMMENT 'Tipo: video registrato o live',
-- Informazioni video
video_url VARCHAR(500) DEFAULT NULL COMMENT 'URL video (YouTube, Vimeo, S3, o percorso locale)',
video_platform VARCHAR(50) DEFAULT 'local' COMMENT 'Piattaforma: local, youtube, vimeo, s3',
thumbnail VARCHAR(500) DEFAULT NULL COMMENT 'URL immagine anteprima',
duration INT DEFAULT NULL COMMENT 'Durata in minuti',
-- Informazioni lezione live
live_platform VARCHAR(100) DEFAULT NULL COMMENT 'Piattaforma streaming (Zoom, Google Meet, ecc.)',
live_url VARCHAR(500) DEFAULT NULL COMMENT 'Link alla sessione live',
live_date DATETIME DEFAULT NULL COMMENT 'Data e ora della lezione live',
-- Classificazione
level ENUM('principiante', 'intermedio', 'avanzato') NOT NULL COMMENT 'Livello difficoltà',
category VARCHAR(100) DEFAULT NULL COMMENT 'Categoria (es: mat, reformer, stretching)',
tags VARCHAR(255) DEFAULT NULL COMMENT 'Tag separati da virgola',
-- Prezzo e disponibilità
price DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT 'Prezzo in euro',
is_demo BOOLEAN DEFAULT FALSE COMMENT 'Se true, lezione gratuita',
is_active BOOLEAN DEFAULT TRUE COMMENT 'Se false, lezione non visibile',
-- Statistiche
view_count INT DEFAULT 0 COMMENT 'Numero visualizzazioni',
purchase_count INT DEFAULT 0 COMMENT 'Numero acquisti',
-- Metadata
created_by INT DEFAULT NULL COMMENT 'ID admin che ha creato',
created_at DATETIME NOT NULL COMMENT 'Data creazione',
updated_at DATETIME DEFAULT NULL COMMENT 'Data ultimo aggiornamento',
deleted_at DATETIME DEFAULT NULL COMMENT 'Soft delete',
INDEX idx_type (type),
INDEX idx_is_demo (is_demo),
INDEX idx_is_active (is_active),
INDEX idx_level (level),
INDEX idx_live_date (live_date),
INDEX idx_deleted_at (deleted_at),
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Lezioni video e live';
-- ============================================
-- TABELLA ACQUISTI
-- ============================================
-- Traccia tutti gli acquisti effettuati dagli utenti
CREATE TABLE IF NOT EXISTS purchases (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT 'Utente che ha acquistato',
lesson_id INT NOT NULL COMMENT 'Lezione acquistata',
-- Informazioni pagamento
amount DECIMAL(10, 2) NOT NULL COMMENT 'Importo pagato',
currency VARCHAR(3) DEFAULT 'EUR' COMMENT 'Valuta',
payment_method VARCHAR(50) DEFAULT 'paypal' COMMENT 'Metodo pagamento',
-- Dettagli transazione PayPal
paypal_order_id VARCHAR(100) DEFAULT NULL COMMENT 'ID ordine PayPal',
paypal_payer_id VARCHAR(100) DEFAULT NULL COMMENT 'ID pagatore PayPal',
paypal_payment_id VARCHAR(100) DEFAULT NULL COMMENT 'ID pagamento PayPal',
paypal_status VARCHAR(50) DEFAULT NULL COMMENT 'Status PayPal',
-- Status acquisto
status ENUM('pending', 'completed', 'failed', 'refunded') DEFAULT 'pending' COMMENT 'Stato acquisto',
-- Timestamp
purchased_at DATETIME DEFAULT NULL COMMENT 'Data completamento acquisto',
created_at DATETIME NOT NULL COMMENT 'Data creazione record',
INDEX idx_user_id (user_id),
INDEX idx_lesson_id (lesson_id),
INDEX idx_status (status),
INDEX idx_purchased_at (purchased_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (lesson_id) REFERENCES lessons(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Acquisti lezioni';
-- ============================================
-- TABELLA RECUPERO PASSWORD
-- ============================================
-- Gestisce i token per il reset della password
CREATE TABLE IF NOT EXISTS password_resets (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT 'Utente che ha richiesto reset',
token VARCHAR(255) NOT NULL UNIQUE COMMENT 'Token univoco per reset',
expires_at DATETIME NOT NULL COMMENT 'Scadenza token',
used_at DATETIME DEFAULT NULL COMMENT 'Quando il token è stato usato',
created_at DATETIME NOT NULL COMMENT 'Data creazione richiesta',
INDEX idx_token (token),
INDEX idx_user_id (user_id),
INDEX idx_expires_at (expires_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Token reset password';
-- ============================================
-- TABELLA LOG ATTIVITÀ (opzionale ma utile)
-- ============================================
-- Traccia le azioni importanti per debug e sicurezza
CREATE TABLE IF NOT EXISTS activity_log (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT DEFAULT NULL COMMENT 'Utente che ha eseguito azione',
action VARCHAR(100) NOT NULL COMMENT 'Tipo azione (login, purchase, etc.)',
description TEXT DEFAULT NULL COMMENT 'Descrizione dettagliata',
ip_address VARCHAR(45) DEFAULT NULL COMMENT 'IP utente (supporta IPv6)',
user_agent TEXT DEFAULT NULL COMMENT 'Browser/dispositivo',
created_at DATETIME NOT NULL COMMENT 'Quando è avvenuta',
INDEX idx_user_id (user_id),
INDEX idx_action (action),
INDEX idx_created_at (created_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Log attività utenti';
-- ============================================
-- DATI DI ESEMPIO
-- ============================================
-- Inserisci un utente amministratore di default
-- Email: admin@pilatesstudio.com
-- Password: admin123 (CAMBIALA SUBITO dopo il primo accesso!)
INSERT INTO users (email, password, first_name, last_name, is_admin, created_at)
VALUES (
'admin@pilatesstudio.com',
'$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', -- Hash di 'admin123'
'Admin',
'Pilates',
TRUE,
NOW()
);
-- Inserisci alcune lezioni demo di esempio
INSERT INTO lessons (title, description, type, video_url, video_platform, thumbnail, duration, level, category, price, is_demo, is_active, created_at) VALUES
(
'Introduzione al Pilates - Esercizi Base',
'Una lezione introduttiva perfetta per chi inizia. Imparerai i principi fondamentali del Pilates e gli esercizi base per sviluppare forza e flessibilità.',
'video',
NULL,
'local',
NULL,
30,
'principiante',
'Mat Work',
0.00,
TRUE,
TRUE,
NOW()
),
(
'Pilates Intermedio - Full Body Workout',
'Sessione completa per lavorare su tutto il corpo. Include esercizi di tonificazione, equilibrio e controllo del core.',
'video',
NULL,
'local',
NULL,
45,
'intermedio',
'Mat Work',
9.99,
FALSE,
TRUE,
NOW()
),
(
'Stretching e Mobilità',
'Lezione dedicata allo stretching profondo e al miglioramento della mobilità articolare. Perfetta per recupero e rilassamento.',
'video',
NULL,
'local',
NULL,
25,
'principiante',
'Stretching',
0.00,
TRUE,
TRUE,
NOW()
);
-- ============================================
-- FINE SCRIPT
-- ============================================
-- Verifica che tutte le tabelle siano state create
SELECT
TABLE_NAME as 'Tabella',
TABLE_ROWS as 'Righe',
CREATE_TIME as 'Creata il'
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'pilates_platform'
ORDER BY TABLE_NAME;

119
forgot_password.php Normal file
View File

@@ -0,0 +1,119 @@
<?php
/**
* Pagina Recupero Password - Step 1
*
* L'utente inserisce la sua email e riceve un link per resettare la password
*/
require_once 'includes/config.php';
require_once 'includes/functions.php';
session_start();
// Se l'utente è già loggato, reindirizza
if (is_logged_in()) {
header('Location: user/dashboard.php');
exit;
}
$email = '';
$error = '';
$success = false;
// Processa il form se inviato
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = sanitize_input($_POST['email'] ?? '');
// Validazione
if (empty($email)) {
$error = 'Inserisci il tuo indirizzo email';
} elseif (!validate_email($email)) {
$error = 'Email non valida';
} else {
// Cerca l'utente
$user = get_user_by_email($email);
if ($user) {
// Crea token di reset
$token = create_password_reset_token($user['id']);
// Invia email
if (send_password_reset_email($email, $token)) {
$success = true;
} else {
$error = 'Errore nell\'invio dell\'email. Riprova più tardi.';
}
} else {
// Per sicurezza, mostra lo stesso messaggio anche se l'email non esiste
// Questo previene di scoprire quali email sono registrate
$success = true;
}
}
}
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Recupero Password - Pilates Platform</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<div class="container-sm" style="padding-top: 3rem;">
<div class="card">
<div class="text-center mb-3">
<h1 class="logo">Pilates Studio</h1>
</div>
<h2 class="card-header text-center">Recupero Password</h2>
<?php if ($success): ?>
<div class="alert alert-success">
<strong>Email inviata!</strong><br>
Se l'indirizzo email è registrato, riceverai un'email con le istruzioni per reimpostare la password.
Controlla anche la cartella spam.
</div>
<div class="text-center">
<a href="login.php" class="btn btn-primary">Torna al Login</a>
</div>
<?php else: ?>
<?php if ($error): ?>
<div class="alert alert-error">
<?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<p class="text-muted text-center mb-2">
Inserisci il tuo indirizzo email e ti invieremo un link per reimpostare la password.
</p>
<form method="POST" action="">
<div class="form-group">
<label for="email" class="form-label">Email</label>
<input
type="email"
id="email"
name="email"
class="form-control"
value="<?php echo htmlspecialchars($email); ?>"
required
autofocus
>
</div>
<button type="submit" class="btn btn-primary btn-large" style="width: 100%;">
Invia Link di Reset
</button>
</form>
<?php endif; ?>
<div class="text-center mt-2">
<p class="mt-2">
<a href="login.php">← Torna al Login</a>
</p>
</div>
</div>
</div>
</body>
</html>

172
includes/config.php Normal file
View File

@@ -0,0 +1,172 @@
<?php
/**
* File di Configurazione
*
* Contiene tutte le impostazioni del sito: database, email, PayPal, ecc.
* IMPORTANTE: Non condividere mai questo file pubblicamente!
*/
// ============================================
// CONFIGURAZIONE DATABASE
// ============================================
define('DB_HOST', 'localhost'); // Host del database
define('DB_NAME', 'pilates_platform'); // Nome del database
define('DB_USER', 'root'); // Username database
define('DB_PASS', ''); // Password database
define('DB_CHARSET', 'utf8mb4'); // Charset per supportare tutti i caratteri
// ============================================
// CONFIGURAZIONE GENERALE SITO
// ============================================
define('SITE_NAME', 'Pilates Studio');
define('SITE_URL', 'http://localhost/pilates-platform'); // Modifica con il tuo dominio in produzione
define('ADMIN_EMAIL', 'admin@pilatesstudio.com'); // Email amministratore
// ============================================
// CONFIGURAZIONE SICUREZZA
// ============================================
// Chiave segreta per crittografia (CAMBIALA in produzione!)
define('SECRET_KEY', 'cambia-questa-chiave-con-una-stringa-casuale-sicura');
// Durata sessione in secondi (default: 2 ore)
define('SESSION_LIFETIME', 7200);
// Durata token recupero password (default: 1 ora)
define('PASSWORD_RESET_TOKEN_LIFETIME', 3600);
// ============================================
// CONFIGURAZIONE PAYPAL
// ============================================
// Modalità sandbox per test (true) o produzione (false)
define('PAYPAL_SANDBOX', true);
// Credenziali PayPal Sandbox (per test)
define('PAYPAL_CLIENT_ID', 'inserisci-qui-il-tuo-client-id-sandbox');
define('PAYPAL_SECRET', 'inserisci-qui-il-tuo-secret-sandbox');
// Credenziali PayPal Live (per produzione - NON compilare finché non sei pronto!)
define('PAYPAL_LIVE_CLIENT_ID', '');
define('PAYPAL_LIVE_SECRET', '');
// URL API PayPal
define('PAYPAL_API_URL', PAYPAL_SANDBOX ?
'https://api-m.sandbox.paypal.com' :
'https://api-m.paypal.com'
);
// ============================================
// CONFIGURAZIONE EMAIL
// ============================================
// Configurazione per l'invio di email (usa SMTP per produzione)
define('MAIL_FROM', 'noreply@pilatesstudio.com');
define('MAIL_FROM_NAME', 'Pilates Studio');
// Impostazioni SMTP (opzionali - commentate se usi mail() PHP di base)
/*
define('SMTP_HOST', 'smtp.gmail.com');
define('SMTP_PORT', 587);
define('SMTP_USERNAME', 'tua-email@gmail.com');
define('SMTP_PASSWORD', 'tua-password-applicazione');
define('SMTP_ENCRYPTION', 'tls');
*/
// ============================================
// CONFIGURAZIONE UPLOAD FILE
// ============================================
// Cartella per upload file (thumbnails, video se locali)
define('UPLOAD_DIR', __DIR__ . '/../uploads/');
define('UPLOAD_URL', SITE_URL . '/uploads/');
// Dimensione massima file upload (in bytes - default 50MB)
define('MAX_UPLOAD_SIZE', 50 * 1024 * 1024);
// Estensioni permesse per upload
define('ALLOWED_IMAGE_TYPES', ['jpg', 'jpeg', 'png', 'gif', 'webp']);
define('ALLOWED_VIDEO_TYPES', ['mp4', 'mov', 'avi', 'webm']);
// ============================================
// CONFIGURAZIONE VIDEO
// ============================================
// Tipo di hosting video: 'local', 'youtube', 'vimeo', 's3'
define('VIDEO_HOSTING_TYPE', 'local');
// Configurazione AWS S3 (se usi S3)
/*
define('AWS_ACCESS_KEY', 'tua-access-key');
define('AWS_SECRET_KEY', 'tua-secret-key');
define('AWS_REGION', 'eu-west-1');
define('AWS_BUCKET', 'pilates-videos');
*/
// ============================================
// CONFIGURAZIONE TIMEZONE
// ============================================
date_default_timezone_set('Europe/Rome');
// ============================================
// MODALITÀ DEBUG
// ============================================
// Attiva/disattiva visualizzazione errori (FALSE in produzione!)
define('DEBUG_MODE', true);
if (DEBUG_MODE) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
} else {
error_reporting(0);
ini_set('display_errors', 0);
}
// ============================================
// CONNESSIONE DATABASE
// ============================================
/**
* Crea e restituisce la connessione al database
* Usa PDO per sicurezza contro SQL injection
*/
function get_db_connection() {
static $pdo = null;
// Se la connessione esiste già, restituiscila
if ($pdo !== null) {
return $pdo;
}
try {
// Crea stringa DSN (Data Source Name)
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
// Opzioni PDO per sicurezza
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // Lancia eccezioni per errori
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // Fetch come array associativo
PDO::ATTR_EMULATE_PREPARES => false, // Usa prepared statements veri
PDO::ATTR_PERSISTENT => false, // Non usare connessioni persistenti
];
// Crea connessione PDO
$pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
return $pdo;
} catch (PDOException $e) {
// In caso di errore di connessione
if (DEBUG_MODE) {
die("Errore connessione database: " . $e->getMessage());
} else {
die("Errore di connessione al database. Contatta l'amministratore.");
}
}
}
?>

564
includes/functions.php Normal file
View File

@@ -0,0 +1,564 @@
<?php
/**
* Funzioni Comuni
*
* Raccolta di funzioni utilizzate in tutta la piattaforma
*/
// ============================================
// FUNZIONI AUTENTICAZIONE E SESSIONE
// ============================================
/**
* Verifica se l'utente è loggato
*
* @return bool True se loggato, false altrimenti
*/
function is_logged_in() {
return isset($_SESSION['user_id']) && !empty($_SESSION['user_id']);
}
/**
* Verifica se l'utente è un amministratore
*
* @return bool True se admin, false altrimenti
*/
function is_admin() {
return isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
}
/**
* Richiede che l'utente sia loggato, altrimenti reindirizza al login
*/
function require_login() {
if (!is_logged_in()) {
$_SESSION['redirect_after_login'] = $_SERVER['REQUEST_URI'];
header('Location: ' . SITE_URL . '/login.php');
exit;
}
}
/**
* Richiede che l'utente sia amministratore, altrimenti reindirizza
*/
function require_admin() {
require_login();
if (!is_admin()) {
header('Location: ' . SITE_URL . '/index.php');
exit;
}
}
/**
* Effettua il login dell'utente
*
* @param int $user_id ID dell'utente
* @param bool $is_admin Se l'utente è admin
*/
function login_user($user_id, $is_admin = false) {
// Rigenera l'ID di sessione per sicurezza
session_regenerate_id(true);
$_SESSION['user_id'] = $user_id;
$_SESSION['is_admin'] = $is_admin;
$_SESSION['last_activity'] = time();
}
/**
* Effettua il logout dell'utente
*/
function logout_user() {
// Cancella tutte le variabili di sessione
$_SESSION = array();
// Distruggi il cookie di sessione se esiste
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time() - 3600, '/');
}
// Distruggi la sessione
session_destroy();
}
/**
* Verifica la validità della sessione (timeout)
*
* @return bool True se sessione valida, false se scaduta
*/
function check_session_timeout() {
if (isset($_SESSION['last_activity'])) {
$elapsed = time() - $_SESSION['last_activity'];
if ($elapsed > SESSION_LIFETIME) {
logout_user();
return false;
}
}
$_SESSION['last_activity'] = time();
return true;
}
// ============================================
// FUNZIONI SICUREZZA
// ============================================
/**
* Hash sicuro di una password
*
* @param string $password Password in chiaro
* @return string Password hashata
*/
function hash_password($password) {
return password_hash($password, PASSWORD_DEFAULT);
}
/**
* Verifica una password contro il suo hash
*
* @param string $password Password in chiaro
* @param string $hash Hash salvato nel database
* @return bool True se corrisponde, false altrimenti
*/
function verify_password($password, $hash) {
return password_verify($password, $hash);
}
/**
* Genera un token casuale sicuro
*
* @param int $length Lunghezza del token
* @return string Token generato
*/
function generate_token($length = 32) {
return bin2hex(random_bytes($length));
}
/**
* Sanitizza input utente per prevenire XSS
*
* @param string $data Dati da sanitizzare
* @return string Dati sanitizzati
*/
function sanitize_input($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
return $data;
}
/**
* Valida un indirizzo email
*
* @param string $email Email da validare
* @return bool True se valida, false altrimenti
*/
function validate_email($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
// ============================================
// FUNZIONI DATABASE - UTENTI
// ============================================
/**
* Ottiene un utente dal database tramite email
*
* @param string $email Email dell'utente
* @return array|false Dati utente o false se non trovato
*/
function get_user_by_email($email) {
$pdo = get_db_connection();
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ? AND deleted_at IS NULL");
$stmt->execute([$email]);
return $stmt->fetch();
}
/**
* Ottiene un utente dal database tramite ID
*
* @param int $user_id ID dell'utente
* @return array|false Dati utente o false se non trovato
*/
function get_user_by_id($user_id) {
$pdo = get_db_connection();
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ? AND deleted_at IS NULL");
$stmt->execute([$user_id]);
return $stmt->fetch();
}
/**
* Crea un nuovo utente nel database
*
* @param string $email Email
* @param string $password Password in chiaro (verrà hashata)
* @param string $first_name Nome
* @param string $last_name Cognome
* @param bool $is_admin Se è amministratore
* @return int|false ID del nuovo utente o false in caso di errore
*/
function create_user($email, $password, $first_name, $last_name, $is_admin = false) {
$pdo = get_db_connection();
$hashed_password = hash_password($password);
try {
$stmt = $pdo->prepare("
INSERT INTO users (email, password, first_name, last_name, is_admin, created_at)
VALUES (?, ?, ?, ?, ?, NOW())
");
$stmt->execute([
$email,
$hashed_password,
$first_name,
$last_name,
$is_admin ? 1 : 0
]);
return $pdo->lastInsertId();
} catch (PDOException $e) {
return false;
}
}
// ============================================
// FUNZIONI DATABASE - LEZIONI
// ============================================
/**
* Ottiene tutte le lezioni demo (gratuite)
*
* @return array Array di lezioni demo
*/
function get_demo_lessons() {
$pdo = get_db_connection();
$stmt = $pdo->query("
SELECT * FROM lessons
WHERE is_demo = 1 AND is_active = 1
ORDER BY created_at DESC
");
return $stmt->fetchAll();
}
/**
* Ottiene tutte le lezioni attive
*
* @param string $type Tipo di lezione: 'all', 'video', 'live'
* @return array Array di lezioni
*/
function get_all_lessons($type = 'all') {
$pdo = get_db_connection();
$sql = "SELECT * FROM lessons WHERE is_active = 1";
if ($type !== 'all') {
$sql .= " AND type = :type";
}
$sql .= " ORDER BY created_at DESC";
$stmt = $pdo->prepare($sql);
if ($type !== 'all') {
$stmt->execute(['type' => $type]);
} else {
$stmt->execute();
}
return $stmt->fetchAll();
}
/**
* Ottiene una lezione specifica tramite ID
*
* @param int $lesson_id ID della lezione
* @return array|false Dati lezione o false se non trovata
*/
function get_lesson_by_id($lesson_id) {
$pdo = get_db_connection();
$stmt = $pdo->prepare("SELECT * FROM lessons WHERE id = ?");
$stmt->execute([$lesson_id]);
return $stmt->fetch();
}
/**
* Verifica se un utente ha acquistato una lezione
*
* @param int $user_id ID utente
* @param int $lesson_id ID lezione
* @return bool True se acquistata, false altrimenti
*/
function user_owns_lesson($user_id, $lesson_id) {
$pdo = get_db_connection();
$stmt = $pdo->prepare("
SELECT COUNT(*) as count
FROM purchases
WHERE user_id = ? AND lesson_id = ? AND status = 'completed'
");
$stmt->execute([$user_id, $lesson_id]);
$result = $stmt->fetch();
return $result['count'] > 0;
}
/**
* Ottiene tutte le lezioni acquistate da un utente
*
* @param int $user_id ID utente
* @return array Array di lezioni acquistate
*/
function get_user_purchased_lessons($user_id) {
$pdo = get_db_connection();
$stmt = $pdo->prepare("
SELECT l.*, p.purchased_at, p.amount as paid_amount
FROM lessons l
INNER JOIN purchases p ON l.id = p.lesson_id
WHERE p.user_id = ? AND p.status = 'completed'
ORDER BY p.purchased_at DESC
");
$stmt->execute([$user_id]);
return $stmt->fetchAll();
}
// ============================================
// FUNZIONI RECUPERO PASSWORD
// ============================================
/**
* Crea un token per il recupero password
*
* @param int $user_id ID utente
* @return string Token generato
*/
function create_password_reset_token($user_id) {
$pdo = get_db_connection();
$token = generate_token(32);
$expires_at = date('Y-m-d H:i:s', time() + PASSWORD_RESET_TOKEN_LIFETIME);
// Cancella eventuali token precedenti per questo utente
$stmt = $pdo->prepare("DELETE FROM password_resets WHERE user_id = ?");
$stmt->execute([$user_id]);
// Crea nuovo token
$stmt = $pdo->prepare("
INSERT INTO password_resets (user_id, token, expires_at, created_at)
VALUES (?, ?, ?, NOW())
");
$stmt->execute([$user_id, $token, $expires_at]);
return $token;
}
/**
* Verifica la validità di un token di reset password
*
* @param string $token Token da verificare
* @return int|false User ID se valido, false altrimenti
*/
function verify_password_reset_token($token) {
$pdo = get_db_connection();
$stmt = $pdo->prepare("
SELECT user_id FROM password_resets
WHERE token = ? AND expires_at > NOW() AND used_at IS NULL
");
$stmt->execute([$token]);
$result = $stmt->fetch();
return $result ? $result['user_id'] : false;
}
/**
* Marca un token come utilizzato
*
* @param string $token Token da marcare
*/
function mark_token_as_used($token) {
$pdo = get_db_connection();
$stmt = $pdo->prepare("UPDATE password_resets SET used_at = NOW() WHERE token = ?");
$stmt->execute([$token]);
}
// ============================================
// FUNZIONI EMAIL
// ============================================
/**
* Invia un'email (funzione base con mail() PHP)
*
* @param string $to Destinatario
* @param string $subject Oggetto
* @param string $message Messaggio HTML
* @return bool True se inviata, false altrimenti
*/
function send_email($to, $subject, $message) {
$headers = "MIME-Version: 1.0" . "\r\n";
$headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";
$headers .= "From: " . MAIL_FROM_NAME . " <" . MAIL_FROM . ">" . "\r\n";
return mail($to, $subject, $message, $headers);
}
/**
* Invia email di recupero password
*
* @param string $email Email destinatario
* @param string $token Token di reset
* @return bool True se inviata, false altrimenti
*/
function send_password_reset_email($email, $token) {
$reset_link = SITE_URL . "/reset_password.php?token=" . $token;
$subject = "Recupero Password - " . SITE_NAME;
$message = "
<html>
<head>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.button {
display: inline-block;
padding: 12px 24px;
background: #4A90E2;
color: white;
text-decoration: none;
border-radius: 5px;
margin: 20px 0;
}
</style>
</head>
<body>
<div class='container'>
<h2>Recupero Password</h2>
<p>Hai richiesto il recupero della password per il tuo account su " . SITE_NAME . ".</p>
<p>Clicca sul pulsante qui sotto per reimpostare la tua password:</p>
<a href='{$reset_link}' class='button'>Reimposta Password</a>
<p>Oppure copia e incolla questo link nel tuo browser:</p>
<p>{$reset_link}</p>
<p>Questo link è valido per " . (PASSWORD_RESET_TOKEN_LIFETIME / 60) . " minuti.</p>
<p>Se non hai richiesto tu questo recupero, ignora questa email.</p>
<hr>
<p><small>Questa è un'email automatica, non rispondere a questo messaggio.</small></p>
</div>
</body>
</html>
";
return send_email($email, $subject, $message);
}
// ============================================
// FUNZIONI MESSAGGI FLASH
// ============================================
/**
* Imposta un messaggio flash da visualizzare nella prossima pagina
*
* @param string $type Tipo: 'success', 'error', 'warning', 'info'
* @param string $message Messaggio da visualizzare
*/
function set_flash_message($type, $message) {
$_SESSION['flash_message'] = [
'type' => $type,
'message' => $message
];
}
/**
* Ottiene e cancella il messaggio flash
*
* @return array|null Array con tipo e messaggio, o null se non presente
*/
function get_flash_message() {
if (isset($_SESSION['flash_message'])) {
$flash = $_SESSION['flash_message'];
unset($_SESSION['flash_message']);
return $flash;
}
return null;
}
/**
* Visualizza il messaggio flash HTML
*
* @return string HTML del messaggio o stringa vuota
*/
function display_flash_message() {
$flash = get_flash_message();
if ($flash) {
$type = htmlspecialchars($flash['type']);
$message = htmlspecialchars($flash['message']);
return "<div class='alert alert-{$type}'>{$message}</div>";
}
return '';
}
// ============================================
// FUNZIONI UTILITY
// ============================================
/**
* Formatta una data in formato italiano
*
* @param string $date Data in formato SQL
* @return string Data formattata
*/
function format_date($date) {
return date('d/m/Y', strtotime($date));
}
/**
* Formatta data e ora in formato italiano
*
* @param string $datetime Datetime in formato SQL
* @return string Datetime formattata
*/
function format_datetime($datetime) {
return date('d/m/Y H:i', strtotime($datetime));
}
/**
* Formatta un prezzo in euro
*
* @param float $price Prezzo
* @return string Prezzo formattato
*/
function format_price($price) {
return '€ ' . number_format($price, 2, ',', '.');
}
/**
* Redirect a un'altra pagina
*
* @param string $url URL di destinazione
*/
function redirect($url) {
header("Location: $url");
exit;
}
?>

33
includes/logout.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
/**
* Logout
*
* Termina la sessione utente e reindirizza alla home
*/
require_once '../config.php';
require_once 'functions.php';
session_start();
// Log attività prima di fare logout
if (is_logged_in()) {
$pdo = get_db_connection();
$stmt = $pdo->prepare("
INSERT INTO activity_log (user_id, action, description, ip_address, user_agent, created_at)
VALUES (?, 'logout', 'Utente ha effettuato il logout', ?, ?, NOW())
");
$stmt->execute([
$_SESSION['user_id'],
$_SERVER['REMOTE_ADDR'] ?? null,
$_SERVER['HTTP_USER_AGENT'] ?? null
]);
}
// Effettua il logout
logout_user();
// Reindirizza alla home
header('Location: ../index.php');
exit;
?>

148
index.php Normal file
View File

@@ -0,0 +1,148 @@
<?php
/**
* Pilates Platform - Homepage
*
* Pagina principale della piattaforma. Mostra le lezioni demo e permette
* l'accesso per utenti registrati e amministratori.
*/
// Includi le configurazioni e funzioni comuni
require_once 'includes/config.php';
require_once 'includes/functions.php';
// Avvia la sessione
session_start();
// Recupera le lezioni demo dal database
$demo_lessons = get_demo_lessons();
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pilates Platform - Videolezioni e Lezioni Live</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<!-- Header con navigazione -->
<header class="header">
<div class="container">
<div class="header-content">
<h1 class="logo">Pilates Studio</h1>
<nav class="nav">
<?php if (is_logged_in()): ?>
<?php if (is_admin()): ?>
<a href="admin/dashboard.php" class="btn btn-secondary">Area Admin</a>
<?php else: ?>
<a href="user/dashboard.php" class="btn btn-secondary">Le Mie Lezioni</a>
<?php endif; ?>
<a href="includes/logout.php" class="btn btn-outline">Logout</a>
<?php else: ?>
<a href="login.php" class="btn btn-secondary">Accedi</a>
<a href="register.php" class="btn btn-primary">Registrati</a>
<?php endif; ?>
</nav>
</div>
</div>
</header>
<!-- Hero Section -->
<section class="hero">
<div class="container">
<div class="hero-content">
<h2 class="hero-title">Trasforma il tuo corpo con il Pilates</h2>
<p class="hero-subtitle">Videolezioni professionali e sessioni live per tutti i livelli</p>
<?php if (!is_logged_in()): ?>
<a href="register.php" class="btn btn-primary btn-large">Inizia Ora</a>
<?php endif; ?>
</div>
</div>
</section>
<!-- Sezione Lezioni Demo -->
<section class="lessons-section">
<div class="container">
<h2 class="section-title">Lezioni Demo Gratuite</h2>
<p class="section-subtitle">Prova gratuitamente alcune delle nostre lezioni migliori</p>
<div class="lessons-grid">
<?php if (!empty($demo_lessons)): ?>
<?php foreach ($demo_lessons as $lesson): ?>
<div class="lesson-card">
<?php if ($lesson['thumbnail']): ?>
<img src="<?php echo htmlspecialchars($lesson['thumbnail']); ?>"
alt="<?php echo htmlspecialchars($lesson['title']); ?>"
class="lesson-thumbnail">
<?php else: ?>
<div class="lesson-thumbnail-placeholder">
<span>📹</span>
</div>
<?php endif; ?>
<div class="lesson-content">
<h3 class="lesson-title"><?php echo htmlspecialchars($lesson['title']); ?></h3>
<p class="lesson-description"><?php echo htmlspecialchars($lesson['description']); ?></p>
<div class="lesson-meta">
<span class="lesson-duration">⏱️ <?php echo $lesson['duration']; ?> min</span>
<span class="lesson-level">📊 <?php echo ucfirst($lesson['level']); ?></span>
</div>
<a href="lesson.php?id=<?php echo $lesson['id']; ?>" class="btn btn-outline btn-small">
Guarda Gratis
</a>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<p class="no-lessons">Nessuna lezione demo disponibile al momento.</p>
<?php endif; ?>
</div>
</div>
</section>
<!-- Sezione Vantaggi -->
<section class="features-section">
<div class="container">
<h2 class="section-title">Perché scegliere Pilates Studio?</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">🎥</div>
<h3>Videolezioni HD</h3>
<p>Contenuti di alta qualità disponibili 24/7</p>
</div>
<div class="feature-card">
<div class="feature-icon">📅</div>
<h3>Lezioni Live</h3>
<p>Sessioni interattive con l'istruttrice</p>
</div>
<div class="feature-card">
<div class="feature-icon">📱</div>
<h3>Accessibile Ovunque</h3>
<p>Su computer, tablet e smartphone</p>
</div>
<div class="feature-card">
<div class="feature-icon">⭐</div>
<h3>Tutti i Livelli</h3>
<p>Principiante, intermedio e avanzato</p>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container">
<p>&copy; <?php echo date('Y'); ?> Pilates Studio. Tutti i diritti riservati.</p>
</div>
</footer>
<script src="assets/js/main.js"></script>
</body>
</html>

262
lesson.php Normal file
View File

@@ -0,0 +1,262 @@
<?php
/**
* Visualizza Lezione
*
* Mostra i dettagli di una lezione e permette di acquistarla o visualizzarla
*/
require_once 'includes/config.php';
require_once 'includes/functions.php';
session_start();
// Ottieni ID lezione
$lesson_id = isset($_GET['id']) && is_numeric($_GET['id']) ? (int)$_GET['id'] : 0;
if (!$lesson_id) {
header('Location: index.php');
exit;
}
// Ottieni dati lezione
$lesson = get_lesson_by_id($lesson_id);
if (!$lesson || !$lesson['is_active']) {
header('Location: index.php');
exit;
}
// Verifica se l'utente è loggato e possiede la lezione
$is_logged_in = is_logged_in();
$user_owns = false;
if ($is_logged_in) {
$user_owns = user_owns_lesson($_SESSION['user_id'], $lesson_id);
}
// Se è demo, tutti possono vedere
$can_view = $lesson['is_demo'] || $user_owns;
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($lesson['title']); ?> - Pilates Platform</title>
<link rel="stylesheet" href="assets/css/style.css">
<!-- PayPal SDK -->
<?php if (!$can_view && $is_logged_in): ?>
<script src="https://www.paypal.com/sdk/js?client-id=<?php echo PAYPAL_CLIENT_ID; ?>&currency=EUR"></script>
<?php endif; ?>
</head>
<body>
<header class="header">
<div class="container">
<div class="header-content">
<h1 class="logo">Pilates Studio</h1>
<nav class="nav">
<a href="index.php" class="btn btn-outline">Home</a>
<?php if ($is_logged_in): ?>
<?php if (is_admin()): ?>
<a href="admin/dashboard.php" class="btn btn-secondary">Area Admin</a>
<?php else: ?>
<a href="user/dashboard.php" class="btn btn-secondary">Le Mie Lezioni</a>
<?php endif; ?>
<a href="includes/logout.php" class="btn btn-outline">Logout</a>
<?php else: ?>
<a href="login.php" class="btn btn-secondary">Accedi</a>
<?php endif; ?>
</nav>
</div>
</div>
</header>
<div class="container" style="padding: 2rem 0;">
<div class="card">
<?php echo display_flash_message(); ?>
<h1 style="margin-bottom: 1rem;"><?php echo htmlspecialchars($lesson['title']); ?></h1>
<div style="display: flex; gap: 1rem; margin-bottom: 1.5rem; flex-wrap: wrap;">
<span class="badge">
<?php echo $lesson['type'] === 'video' ? '📹 Video' : '📡 Live'; ?>
</span>
<span class="badge">
📊 <?php echo ucfirst($lesson['level']); ?>
</span>
<?php if ($lesson['duration']): ?>
<span class="badge">⏱️ <?php echo $lesson['duration']; ?> min</span>
<?php endif; ?>
<?php if ($lesson['category']): ?>
<span class="badge">📂 <?php echo htmlspecialchars($lesson['category']); ?></span>
<?php endif; ?>
</div>
<!-- Video Player o Info Live -->
<?php if ($can_view): ?>
<?php if ($lesson['type'] === 'video'): ?>
<div style="background: #000; border-radius: 8px; margin-bottom: 2rem; aspect-ratio: 16/9;">
<?php if ($lesson['video_url']): ?>
<?php if ($lesson['video_platform'] === 'youtube'): ?>
<!-- Embed YouTube -->
<?php
$video_id = '';
if (preg_match('/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&\?\/]+)/', $lesson['video_url'], $matches)) {
$video_id = $matches[1];
}
?>
<?php if ($video_id): ?>
<iframe width="100%" height="100%"
src="https://www.youtube.com/embed/<?php echo $video_id; ?>"
frameborder="0" allowfullscreen
style="border-radius: 8px;"></iframe>
<?php endif; ?>
<?php elseif ($lesson['video_platform'] === 'vimeo'): ?>
<!-- Embed Vimeo -->
<?php
$video_id = '';
if (preg_match('/vimeo\.com\/(\d+)/', $lesson['video_url'], $matches)) {
$video_id = $matches[1];
}
?>
<?php if ($video_id): ?>
<iframe width="100%" height="100%"
src="https://player.vimeo.com/video/<?php echo $video_id; ?>"
frameborder="0" allowfullscreen
style="border-radius: 8px;"></iframe>
<?php endif; ?>
<?php else: ?>
<!-- Video locale o altro -->
<video controls width="100%" height="100%" style="border-radius: 8px;">
<source src="<?php echo htmlspecialchars($lesson['video_url']); ?>">
Il tuo browser non supporta il tag video.
</video>
<?php endif; ?>
<?php else: ?>
<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: white;">
Video non ancora disponibile
</div>
<?php endif; ?>
</div>
<?php else: ?>
<!-- Lezione Live -->
<div class="alert alert-info">
<h3>📡 Lezione Live</h3>
<p><strong>Data e ora:</strong> <?php echo format_datetime($lesson['live_date']); ?></p>
<?php if ($lesson['live_platform']): ?>
<p><strong>Piattaforma:</strong> <?php echo htmlspecialchars($lesson['live_platform']); ?></p>
<?php endif; ?>
<?php if ($lesson['live_url']): ?>
<a href="<?php echo htmlspecialchars($lesson['live_url']); ?>"
target="_blank" class="btn btn-primary" style="margin-top: 1rem;">
Partecipa alla Lezione Live
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endif; ?>
<!-- Descrizione -->
<h3>Descrizione</h3>
<p style="line-height: 1.8; margin-bottom: 2rem;">
<?php echo nl2br(htmlspecialchars($lesson['description'])); ?>
</p>
<!-- Acquisto o Accesso -->
<?php if ($lesson['is_demo']): ?>
<div class="alert alert-success">
<strong>✓ Lezione Gratuita!</strong> Questa lezione è disponibile gratuitamente per tutti.
</div>
<?php elseif ($user_owns): ?>
<div class="alert alert-success">
<strong>✓ Hai già acquistato questa lezione!</strong>
</div>
<?php elseif ($is_logged_in): ?>
<!-- Form Acquisto PayPal -->
<div style="border: 2px solid var(--primary-color); border-radius: 12px; padding: 2rem; text-align: center;">
<h2 style="color: var(--primary-color); margin-bottom: 1rem;">
Prezzo: <?php echo format_price($lesson['price']); ?>
</h2>
<p class="text-muted mb-2">Acquista questa lezione e avrai accesso illimitato per sempre</p>
<!-- PayPal Button Container -->
<div id="paypal-button-container" style="max-width: 400px; margin: 0 auto;"></div>
<p class="text-muted" style="font-size: 0.875rem; margin-top: 1rem;">
Pagamento sicuro tramite PayPal
</p>
</div>
<?php else: ?>
<div class="alert alert-warning text-center">
<h3>Accedi per acquistare questa lezione</h3>
<p>Prezzo: <strong><?php echo format_price($lesson['price']); ?></strong></p>
<a href="login.php" class="btn btn-primary" style="margin-top: 1rem;">Accedi o Registrati</a>
</div>
<?php endif; ?>
</div>
</div>
<?php if (!$can_view && $is_logged_in): ?>
<script>
// Integrazione PayPal
paypal.Buttons({
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
description: '<?php echo addslashes($lesson['title']); ?>',
amount: {
value: '<?php echo number_format($lesson['price'], 2, '.', ''); ?>'
}
}]
});
},
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
// Invia i dettagli al server per registrare l'acquisto
fetch('process_payment.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
orderID: data.orderID,
lessonID: <?php echo $lesson_id; ?>,
payerID: details.payer.payer_id,
amount: '<?php echo number_format($lesson['price'], 2, '.', ''); ?>'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
window.location.href = 'lesson.php?id=<?php echo $lesson_id; ?>&payment=success';
} else {
alert('Errore durante la registrazione del pagamento. Contatta il supporto.');
}
});
});
},
onError: function(err) {
console.error(err);
alert('Si è verificato un errore durante il pagamento. Riprova.');
}
}).render('#paypal-button-container');
</script>
<?php endif; ?>
<style>
.badge {
display: inline-block;
padding: 0.5rem 1rem;
background: var(--secondary-color);
color: var(--primary-color);
border-radius: 20px;
font-size: 0.875rem;
font-weight: 500;
}
</style>
<script src="assets/js/main.js"></script>
</body>
</html>

148
login.php Normal file
View File

@@ -0,0 +1,148 @@
<?php
/**
* Pagina di Login
*
* Permette agli utenti di accedere alla piattaforma
*/
require_once 'includes/config.php';
require_once 'includes/functions.php';
session_start();
// Se l'utente è già loggato, reindirizza
if (is_logged_in()) {
if (is_admin()) {
header('Location: admin/dashboard.php');
} else {
header('Location: user/dashboard.php');
}
exit;
}
// Variabili per il form
$email = '';
$error = '';
// Processa il form se inviato
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = sanitize_input($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
// Validazione
if (empty($email) || empty($password)) {
$error = 'Inserisci email e password';
} elseif (!validate_email($email)) {
$error = 'Email non valida';
} else {
// Cerca l'utente nel database
$user = get_user_by_email($email);
if ($user && verify_password($password, $user['password'])) {
// Password corretta - effettua il login
// Aggiorna data ultimo accesso
$pdo = get_db_connection();
$stmt = $pdo->prepare("UPDATE users SET last_login = NOW() WHERE id = ?");
$stmt->execute([$user['id']]);
// Log attività
$stmt = $pdo->prepare("
INSERT INTO activity_log (user_id, action, description, ip_address, user_agent, created_at)
VALUES (?, 'login', 'Utente ha effettuato il login', ?, ?, NOW())
");
$stmt->execute([
$user['id'],
$_SERVER['REMOTE_ADDR'] ?? null,
$_SERVER['HTTP_USER_AGENT'] ?? null
]);
// Imposta la sessione
login_user($user['id'], $user['is_admin']);
// Reindirizza alla pagina appropriata
if (isset($_SESSION['redirect_after_login'])) {
$redirect = $_SESSION['redirect_after_login'];
unset($_SESSION['redirect_after_login']);
header("Location: $redirect");
} elseif ($user['is_admin']) {
header('Location: admin/dashboard.php');
} else {
header('Location: user/dashboard.php');
}
exit;
} else {
$error = 'Email o password non corretti';
}
}
}
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Pilates Platform</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<div class="container-sm" style="padding-top: 3rem;">
<div class="card">
<div class="text-center mb-3">
<h1 class="logo">Pilates Studio</h1>
</div>
<h2 class="card-header text-center">Accedi al tuo account</h2>
<?php if ($error): ?>
<div class="alert alert-error">
<?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<form method="POST" action="">
<div class="form-group">
<label for="email" class="form-label">Email</label>
<input
type="email"
id="email"
name="email"
class="form-control"
value="<?php echo htmlspecialchars($email); ?>"
required
autofocus
>
</div>
<div class="form-group">
<label for="password" class="form-label">Password</label>
<input
type="password"
id="password"
name="password"
class="form-control"
required
>
</div>
<button type="submit" class="btn btn-primary btn-large" style="width: 100%;">
Accedi
</button>
</form>
<div class="text-center mt-2">
<p class="text-muted">
<a href="forgot_password.php">Hai dimenticato la password?</a>
</p>
<p class="text-muted">
Non hai un account? <a href="register.php"><strong>Registrati</strong></a>
</p>
<p class="mt-2">
<a href="index.php">← Torna alla home</a>
</p>
</div>
</div>
</div>
</body>
</html>

104
process_payment.php Normal file
View File

@@ -0,0 +1,104 @@
<?php
/**
* Process Payment
*
* Riceve la conferma del pagamento da PayPal e registra l'acquisto nel database
*/
require_once 'includes/config.php';
require_once 'includes/functions.php';
session_start();
// Verifica che l'utente sia loggato
if (!is_logged_in()) {
echo json_encode(['success' => false, 'error' => 'Not logged in']);
exit;
}
// Leggi i dati JSON
$json = file_get_contents('php://input');
$data = json_decode($json, true);
if (!$data) {
echo json_encode(['success' => false, 'error' => 'Invalid data']);
exit;
}
$order_id = $data['orderID'] ?? '';
$lesson_id = $data['lessonID'] ?? 0;
$payer_id = $data['payerID'] ?? '';
$amount = $data['amount'] ?? 0;
$user_id = $_SESSION['user_id'];
// Validazione
if (empty($order_id) || !$lesson_id || !$amount) {
echo json_encode(['success' => false, 'error' => 'Missing required fields']);
exit;
}
// Verifica che la lezione esista
$lesson = get_lesson_by_id($lesson_id);
if (!$lesson) {
echo json_encode(['success' => false, 'error' => 'Lesson not found']);
exit;
}
// Verifica che l'utente non abbia già acquistato questa lezione
if (user_owns_lesson($user_id, $lesson_id)) {
echo json_encode(['success' => false, 'error' => 'Already purchased']);
exit;
}
try {
$pdo = get_db_connection();
// Registra l'acquisto nel database
$stmt = $pdo->prepare("
INSERT INTO purchases (
user_id, lesson_id, amount, currency, payment_method,
paypal_order_id, paypal_payer_id, paypal_status,
status, purchased_at, created_at
) VALUES (
?, ?, ?, 'EUR', 'paypal',
?, ?, 'completed',
'completed', NOW(), NOW()
)
");
$stmt->execute([
$user_id,
$lesson_id,
$amount,
$order_id,
$payer_id
]);
// Aggiorna il contatore acquisti della lezione
$stmt = $pdo->prepare("
UPDATE lessons
SET purchase_count = purchase_count + 1
WHERE id = ?
");
$stmt->execute([$lesson_id]);
// Log attività
$stmt = $pdo->prepare("
INSERT INTO activity_log (user_id, action, description, ip_address, user_agent, created_at)
VALUES (?, 'purchase', ?, ?, ?, NOW())
");
$stmt->execute([
$user_id,
"Acquisto lezione: {$lesson['title']}",
$_SERVER['REMOTE_ADDR'] ?? null,
$_SERVER['HTTP_USER_AGENT'] ?? null
]);
echo json_encode(['success' => true]);
} catch (Exception $e) {
error_log("Payment processing error: " . $e->getMessage());
echo json_encode(['success' => false, 'error' => 'Database error']);
}
?>

187
register.php Normal file
View File

@@ -0,0 +1,187 @@
<?php
/**
* Pagina di Registrazione
*
* Permette ai nuovi utenti di creare un account
*/
require_once 'includes/config.php';
require_once 'includes/functions.php';
session_start();
// Se l'utente è già loggato, reindirizza
if (is_logged_in()) {
header('Location: user/dashboard.php');
exit;
}
// Variabili per il form
$first_name = '';
$last_name = '';
$email = '';
$error = '';
$success = false;
// Processa il form se inviato
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$first_name = sanitize_input($_POST['first_name'] ?? '');
$last_name = sanitize_input($_POST['last_name'] ?? '');
$email = sanitize_input($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
$password_confirm = $_POST['password_confirm'] ?? '';
// Validazione
if (empty($first_name) || empty($last_name) || empty($email) || empty($password)) {
$error = 'Tutti i campi sono obbligatori';
} elseif (!validate_email($email)) {
$error = 'Email non valida';
} elseif (strlen($password) < 6) {
$error = 'La password deve essere di almeno 6 caratteri';
} elseif ($password !== $password_confirm) {
$error = 'Le password non corrispondono';
} else {
// Verifica se l'email esiste già
if (get_user_by_email($email)) {
$error = 'Questa email è già registrata';
} else {
// Crea il nuovo utente
$user_id = create_user($email, $password, $first_name, $last_name);
if ($user_id) {
$success = true;
// Log attività
$pdo = get_db_connection();
$stmt = $pdo->prepare("
INSERT INTO activity_log (user_id, action, description, ip_address, user_agent, created_at)
VALUES (?, 'register', 'Nuovo utente registrato', ?, ?, NOW())
");
$stmt->execute([
$user_id,
$_SERVER['REMOTE_ADDR'] ?? null,
$_SERVER['HTTP_USER_AGENT'] ?? null
]);
// Login automatico dopo registrazione
login_user($user_id, false);
// Reindirizza dopo 2 secondi
header("refresh:2;url=user/dashboard.php");
} else {
$error = 'Errore durante la registrazione. Riprova più tardi.';
}
}
}
}
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Registrazione - Pilates Platform</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<div class="container-sm" style="padding-top: 3rem; padding-bottom: 3rem;">
<div class="card">
<div class="text-center mb-3">
<h1 class="logo">Pilates Studio</h1>
</div>
<h2 class="card-header text-center">Crea un nuovo account</h2>
<?php if ($success): ?>
<div class="alert alert-success">
<strong>Registrazione completata!</strong> Verrai reindirizzato alla tua area personale...
</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-error">
<?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<?php if (!$success): ?>
<form method="POST" action="">
<div class="form-group">
<label for="first_name" class="form-label">Nome</label>
<input
type="text"
id="first_name"
name="first_name"
class="form-control"
value="<?php echo htmlspecialchars($first_name); ?>"
required
autofocus
>
</div>
<div class="form-group">
<label for="last_name" class="form-label">Cognome</label>
<input
type="text"
id="last_name"
name="last_name"
class="form-control"
value="<?php echo htmlspecialchars($last_name); ?>"
required
>
</div>
<div class="form-group">
<label for="email" class="form-label">Email</label>
<input
type="email"
id="email"
name="email"
class="form-control"
value="<?php echo htmlspecialchars($email); ?>"
required
>
</div>
<div class="form-group">
<label for="password" class="form-label">Password (minimo 6 caratteri)</label>
<input
type="password"
id="password"
name="password"
class="form-control"
required
minlength="6"
>
</div>
<div class="form-group">
<label for="password_confirm" class="form-label">Conferma Password</label>
<input
type="password"
id="password_confirm"
name="password_confirm"
class="form-control"
required
minlength="6"
>
</div>
<button type="submit" class="btn btn-primary btn-large" style="width: 100%;">
Registrati
</button>
</form>
<?php endif; ?>
<div class="text-center mt-2">
<p class="text-muted">
Hai già un account? <a href="login.php"><strong>Accedi</strong></a>
</p>
<p class="mt-2">
<a href="index.php">← Torna alla home</a>
</p>
</div>
</div>
</div>
</body>
</html>

156
reset_password.php Normal file
View File

@@ -0,0 +1,156 @@
<?php
/**
* Pagina Reset Password - Step 2
*
* L'utente arriva qui cliccando il link nell'email e può impostare una nuova password
*/
require_once 'includes/config.php';
require_once 'includes/functions.php';
session_start();
// Se l'utente è già loggato, reindirizza
if (is_logged_in()) {
header('Location: user/dashboard.php');
exit;
}
$token = $_GET['token'] ?? '';
$error = '';
$success = false;
// Verifica che il token esista e sia valido
$user_id = verify_password_reset_token($token);
if (!$user_id) {
$error = 'Link non valido o scaduto. Richiedi un nuovo link di reset.';
}
// Processa il form se inviato
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $user_id) {
$password = $_POST['password'] ?? '';
$password_confirm = $_POST['password_confirm'] ?? '';
// Validazione
if (empty($password)) {
$error = 'Inserisci la nuova password';
} elseif (strlen($password) < 6) {
$error = 'La password deve essere di almeno 6 caratteri';
} elseif ($password !== $password_confirm) {
$error = 'Le password non corrispondono';
} else {
// Aggiorna la password
$pdo = get_db_connection();
$hashed_password = hash_password($password);
$stmt = $pdo->prepare("UPDATE users SET password = ?, updated_at = NOW() WHERE id = ?");
if ($stmt->execute([$hashed_password, $user_id])) {
// Marca il token come usato
mark_token_as_used($token);
// Log attività
$stmt = $pdo->prepare("
INSERT INTO activity_log (user_id, action, description, ip_address, user_agent, created_at)
VALUES (?, 'password_reset', 'Password reimpostata', ?, ?, NOW())
");
$stmt->execute([
$user_id,
$_SERVER['REMOTE_ADDR'] ?? null,
$_SERVER['HTTP_USER_AGENT'] ?? null
]);
$success = true;
} else {
$error = 'Errore durante il reset della password. Riprova più tardi.';
}
}
}
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reimposta Password - Pilates Platform</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<div class="container-sm" style="padding-top: 3rem;">
<div class="card">
<div class="text-center mb-3">
<h1 class="logo">Pilates Studio</h1>
</div>
<h2 class="card-header text-center">Reimposta Password</h2>
<?php if ($success): ?>
<div class="alert alert-success">
<strong>Password aggiornata!</strong><br>
La tua password è stata reimpostata con successo. Ora puoi effettuare il login.
</div>
<div class="text-center">
<a href="login.php" class="btn btn-primary btn-large">Vai al Login</a>
</div>
<?php elseif (!$user_id): ?>
<div class="alert alert-error">
<?php echo htmlspecialchars($error); ?>
</div>
<div class="text-center">
<a href="forgot_password.php" class="btn btn-primary">Richiedi Nuovo Link</a>
</div>
<?php else: ?>
<?php if ($error): ?>
<div class="alert alert-error">
<?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<p class="text-muted text-center mb-2">
Inserisci la tua nuova password.
</p>
<form method="POST" action="">
<div class="form-group">
<label for="password" class="form-label">Nuova Password (minimo 6 caratteri)</label>
<input
type="password"
id="password"
name="password"
class="form-control"
required
minlength="6"
autofocus
>
</div>
<div class="form-group">
<label for="password_confirm" class="form-label">Conferma Password</label>
<input
type="password"
id="password_confirm"
name="password_confirm"
class="form-control"
required
minlength="6"
>
</div>
<button type="submit" class="btn btn-primary btn-large" style="width: 100%;">
Reimposta Password
</button>
</form>
<?php endif; ?>
<div class="text-center mt-2">
<p class="mt-2">
<a href="login.php">← Torna al Login</a>
</p>
</div>
</div>
</div>
</body>
</html>

192
user/catalog.php Normal file
View File

@@ -0,0 +1,192 @@
<?php
/**
* Catalogo Lezioni
*
* Mostra tutte le lezioni disponibili per l'acquisto
*/
require_once '../includes/config.php';
require_once '../includes/functions.php';
session_start();
check_session_timeout();
require_login();
$user_id = $_SESSION['user_id'];
// Ottieni tutte le lezioni attive
$all_lessons = get_all_lessons('all');
// Ottieni le lezioni già acquistate dall'utente
$pdo = get_db_connection();
$stmt = $pdo->prepare("
SELECT lesson_id FROM purchases
WHERE user_id = ? AND status = 'completed'
");
$stmt->execute([$user_id]);
$owned_lesson_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Catalogo Lezioni - Pilates Platform</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<header class="header">
<div class="container">
<div class="header-content">
<h1 class="logo">Pilates Studio</h1>
<nav class="nav">
<a href="../index.php" class="btn btn-outline">Home</a>
<a href="dashboard.php" class="btn btn-secondary">Le Mie Lezioni</a>
<a href="../includes/logout.php" class="btn btn-outline">Logout</a>
</nav>
</div>
</div>
</header>
<div class="container">
<div class="dashboard">
<!-- Sidebar con filtri -->
<aside class="sidebar">
<h3 style="margin-bottom: 1rem;">Filtri</h3>
<div style="margin-bottom: 1.5rem;">
<label style="font-weight: 500; display: block; margin-bottom: 0.5rem;">Tipo</label>
<label style="display: block; margin-bottom: 0.25rem;">
<input type="checkbox" class="filter-type" value="video" checked> Videolezioni
</label>
<label style="display: block;">
<input type="checkbox" class="filter-type" value="live" checked> Lezioni Live
</label>
</div>
<div style="margin-bottom: 1.5rem;">
<label style="font-weight: 500; display: block; margin-bottom: 0.5rem;">Livello</label>
<label style="display: block; margin-bottom: 0.25rem;">
<input type="checkbox" class="filter-level" value="principiante" checked> Principiante
</label>
<label style="display: block; margin-bottom: 0.25rem;">
<input type="checkbox" class="filter-level" value="intermedio" checked> Intermedio
</label>
<label style="display: block;">
<input type="checkbox" class="filter-level" value="avanzato" checked> Avanzato
</label>
</div>
<button onclick="applyFilters()" class="btn btn-primary" style="width: 100%;">
Applica Filtri
</button>
</aside>
<!-- Main Content -->
<main class="main-content">
<h2 class="section-title" style="text-align: left;">Catalogo Lezioni</h2>
<?php echo display_flash_message(); ?>
<div class="lessons-grid" id="lessons-container">
<?php foreach ($all_lessons as $lesson): ?>
<?php
$is_owned = in_array($lesson['id'], $owned_lesson_ids);
$is_demo = $lesson['is_demo'];
?>
<div class="lesson-card"
data-type="<?php echo $lesson['type']; ?>"
data-level="<?php echo $lesson['level']; ?>">
<?php if ($lesson['thumbnail']): ?>
<img src="<?php echo htmlspecialchars($lesson['thumbnail']); ?>"
alt="<?php echo htmlspecialchars($lesson['title']); ?>"
class="lesson-thumbnail">
<?php else: ?>
<div class="lesson-thumbnail-placeholder">
<span><?php echo $lesson['type'] === 'video' ? '📹' : '📡'; ?></span>
</div>
<?php endif; ?>
<div class="lesson-content">
<h3 class="lesson-title"><?php echo htmlspecialchars($lesson['title']); ?></h3>
<p class="lesson-description">
<?php echo htmlspecialchars(substr($lesson['description'], 0, 100)) . '...'; ?>
</p>
<div class="lesson-meta">
<?php if ($lesson['duration']): ?>
<span>⏱️ <?php echo $lesson['duration']; ?> min</span>
<?php endif; ?>
<span>📊 <?php echo ucfirst($lesson['level']); ?></span>
</div>
<?php if ($lesson['type'] === 'live' && $lesson['live_date']): ?>
<p class="text-muted" style="font-size: 0.875rem; margin: 0.5rem 0;">
📅 <?php echo format_datetime($lesson['live_date']); ?>
</p>
<?php endif; ?>
<?php if ($is_demo): ?>
<div class="lesson-price text-success">GRATIS</div>
<a href="../lesson.php?id=<?php echo $lesson['id']; ?>" class="btn btn-success btn-small">
Guarda Gratis
</a>
<?php elseif ($is_owned): ?>
<div class="text-success" style="margin-bottom: 0.5rem;">✓ Già acquistata</div>
<a href="../lesson.php?id=<?php echo $lesson['id']; ?>" class="btn btn-primary btn-small">
Vai alla Lezione
</a>
<?php else: ?>
<div class="lesson-price"><?php echo format_price($lesson['price']); ?></div>
<a href="../lesson.php?id=<?php echo $lesson['id']; ?>" class="btn btn-primary btn-small">
Vedi Dettagli
</a>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php if (empty($all_lessons)): ?>
<div class="card text-center">
<p class="text-muted">Nessuna lezione disponibile al momento.</p>
</div>
<?php endif; ?>
</main>
</div>
</div>
<script>
// Funzione per applicare i filtri
function applyFilters() {
// Ottieni i tipi selezionati
const selectedTypes = Array.from(document.querySelectorAll('.filter-type:checked'))
.map(cb => cb.value);
// Ottieni i livelli selezionati
const selectedLevels = Array.from(document.querySelectorAll('.filter-level:checked'))
.map(cb => cb.value);
// Filtra le lezioni
const lessonCards = document.querySelectorAll('.lesson-card');
lessonCards.forEach(card => {
const cardType = card.dataset.type;
const cardLevel = card.dataset.level;
const typeMatch = selectedTypes.length === 0 || selectedTypes.includes(cardType);
const levelMatch = selectedLevels.length === 0 || selectedLevels.includes(cardLevel);
if (typeMatch && levelMatch) {
card.style.display = 'block';
} else {
card.style.display = 'none';
}
});
}
</script>
<script src="../assets/js/main.js"></script>
</body>
</html>

132
user/dashboard.php Normal file
View File

@@ -0,0 +1,132 @@
<?php
/**
* Dashboard Utente
*
* Area personale dell'utente con le sue lezioni acquistate
*/
require_once '../includes/config.php';
require_once '../includes/functions.php';
session_start();
check_session_timeout();
require_login();
$user_id = $_SESSION['user_id'];
// Ottieni le lezioni acquistate dall'utente
$purchased_lessons = get_user_purchased_lessons($user_id);
// Ottieni info utente
$user = get_user_by_id($user_id);
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Le Mie Lezioni - Pilates Platform</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<header class="header">
<div class="container">
<div class="header-content">
<h1 class="logo">Pilates Studio</h1>
<nav class="nav">
<a href="../index.php" class="btn btn-outline">Home</a>
<a href="catalog.php" class="btn btn-secondary">Catalogo Lezioni</a>
<a href="../includes/logout.php" class="btn btn-outline">Logout</a>
</nav>
</div>
</div>
</header>
<div class="container">
<div class="dashboard">
<!-- Sidebar -->
<aside class="sidebar">
<div style="text-align: center; margin-bottom: 1.5rem;">
<div style="width: 80px; height: 80px; border-radius: 50%; background: var(--primary-light);
margin: 0 auto 1rem; display: flex; align-items: center; justify-content: center;
font-size: 2rem; color: white;">
<?php echo strtoupper(substr($user['first_name'], 0, 1)); ?>
</div>
<strong><?php echo htmlspecialchars($user['first_name'] . ' ' . $user['last_name']); ?></strong>
<p class="text-muted" style="font-size: 0.875rem; margin-top: 0.25rem;">
<?php echo htmlspecialchars($user['email']); ?>
</p>
</div>
<ul class="sidebar-menu">
<li><a href="dashboard.php" class="active">📚 Le Mie Lezioni</a></li>
<li><a href="catalog.php">🔍 Catalogo</a></li>
<li><a href="profile.php">⚙️ Profilo</a></li>
</ul>
</aside>
<!-- Main Content -->
<main class="main-content">
<h2 class="section-title" style="text-align: left;">Le Mie Lezioni</h2>
<?php echo display_flash_message(); ?>
<?php if (!empty($purchased_lessons)): ?>
<div class="lessons-grid">
<?php foreach ($purchased_lessons as $lesson): ?>
<div class="lesson-card">
<?php if ($lesson['thumbnail']): ?>
<img src="<?php echo htmlspecialchars($lesson['thumbnail']); ?>"
alt="<?php echo htmlspecialchars($lesson['title']); ?>"
class="lesson-thumbnail">
<?php else: ?>
<div class="lesson-thumbnail-placeholder">
<span><?php echo $lesson['type'] === 'video' ? '📹' : '📡'; ?></span>
</div>
<?php endif; ?>
<div class="lesson-content">
<h3 class="lesson-title"><?php echo htmlspecialchars($lesson['title']); ?></h3>
<p class="lesson-description">
<?php echo htmlspecialchars(substr($lesson['description'], 0, 100)) . '...'; ?>
</p>
<div class="lesson-meta">
<?php if ($lesson['duration']): ?>
<span class="lesson-duration">⏱️ <?php echo $lesson['duration']; ?> min</span>
<?php endif; ?>
<span class="lesson-level">📊 <?php echo ucfirst($lesson['level']); ?></span>
</div>
<?php if ($lesson['type'] === 'live'): ?>
<p class="text-muted" style="font-size: 0.875rem; margin: 0.5rem 0;">
📅 <?php echo format_datetime($lesson['live_date']); ?>
</p>
<?php endif; ?>
<a href="../lesson.php?id=<?php echo $lesson['id']; ?>" class="btn btn-primary btn-small">
<?php echo $lesson['type'] === 'video' ? 'Guarda' : 'Partecipa'; ?>
</a>
<p class="text-muted" style="font-size: 0.75rem; margin-top: 0.5rem;">
Acquistata il <?php echo format_date($lesson['purchased_at']); ?>
</p>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="card text-center">
<h3>Non hai ancora acquistato nessuna lezione</h3>
<p class="text-muted mb-2">Esplora il nostro catalogo e inizia il tuo percorso di Pilates!</p>
<a href="catalog.php" class="btn btn-primary">Sfoglia il Catalogo</a>
</div>
<?php endif; ?>
</main>
</div>
</div>
<script src="../assets/js/main.js"></script>
</body>
</html>

209
user/profile.php Normal file
View File

@@ -0,0 +1,209 @@
<?php
/**
* Profilo Utente
*
* Permette all'utente di visualizzare e modificare i propri dati
*/
require_once '../includes/config.php';
require_once '../includes/functions.php';
session_start();
check_session_timeout();
require_login();
$user_id = $_SESSION['user_id'];
$user = get_user_by_id($user_id);
$error = '';
$success = false;
// Processa aggiornamento profilo
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_profile'])) {
$first_name = sanitize_input($_POST['first_name'] ?? '');
$last_name = sanitize_input($_POST['last_name'] ?? '');
$email = sanitize_input($_POST['email'] ?? '');
$phone = sanitize_input($_POST['phone'] ?? '');
if (empty($first_name) || empty($last_name) || empty($email)) {
$error = 'Nome, cognome ed email sono obbligatori';
} elseif (!validate_email($email)) {
$error = 'Email non valida';
} else {
// Verifica se l'email è già usata da un altro utente
$pdo = get_db_connection();
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ? AND id != ?");
$stmt->execute([$email, $user_id]);
if ($stmt->fetch()) {
$error = 'Questa email è già in uso';
} else {
// Aggiorna profilo
$stmt = $pdo->prepare("
UPDATE users
SET first_name = ?, last_name = ?, email = ?, phone = ?, updated_at = NOW()
WHERE id = ?
");
if ($stmt->execute([$first_name, $last_name, $email, $phone, $user_id])) {
$success = true;
$user = get_user_by_id($user_id); // Ricarica dati
set_flash_message('success', 'Profilo aggiornato con successo!');
} else {
$error = 'Errore durante l\'aggiornamento';
}
}
}
}
// Processa cambio password
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['change_password'])) {
$current_password = $_POST['current_password'] ?? '';
$new_password = $_POST['new_password'] ?? '';
$confirm_password = $_POST['confirm_password'] ?? '';
if (empty($current_password) || empty($new_password)) {
$error = 'Inserisci la password attuale e la nuova password';
} elseif (!verify_password($current_password, $user['password'])) {
$error = 'Password attuale non corretta';
} elseif (strlen($new_password) < 6) {
$error = 'La nuova password deve essere di almeno 6 caratteri';
} elseif ($new_password !== $confirm_password) {
$error = 'Le password non corrispondono';
} else {
$pdo = get_db_connection();
$hashed_password = hash_password($new_password);
$stmt = $pdo->prepare("UPDATE users SET password = ?, updated_at = NOW() WHERE id = ?");
if ($stmt->execute([$hashed_password, $user_id])) {
set_flash_message('success', 'Password cambiata con successo!');
header('Location: profile.php');
exit;
} else {
$error = 'Errore durante il cambio password';
}
}
}
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Profilo - Pilates Platform</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<header class="header">
<div class="container">
<div class="header-content">
<h1 class="logo">Pilates Studio</h1>
<nav class="nav">
<a href="../index.php" class="btn btn-outline">Home</a>
<a href="dashboard.php" class="btn btn-secondary">Le Mie Lezioni</a>
<a href="../includes/logout.php" class="btn btn-outline">Logout</a>
</nav>
</div>
</div>
</header>
<div class="container">
<div class="dashboard">
<!-- Sidebar -->
<aside class="sidebar">
<ul class="sidebar-menu">
<li><a href="dashboard.php">📚 Le Mie Lezioni</a></li>
<li><a href="catalog.php">🔍 Catalogo</a></li>
<li><a href="profile.php" class="active">⚙️ Profilo</a></li>
</ul>
</aside>
<!-- Main Content -->
<main class="main-content">
<h2 class="section-title" style="text-align: left;">Il Mio Profilo</h2>
<?php echo display_flash_message(); ?>
<?php if ($error): ?>
<div class="alert alert-error"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<!-- Form Dati Personali -->
<div class="card">
<h3 class="card-header">Dati Personali</h3>
<form method="POST" action="">
<div class="form-group">
<label for="first_name" class="form-label">Nome</label>
<input type="text" id="first_name" name="first_name" class="form-control"
value="<?php echo htmlspecialchars($user['first_name']); ?>" required>
</div>
<div class="form-group">
<label for="last_name" class="form-label">Cognome</label>
<input type="text" id="last_name" name="last_name" class="form-control"
value="<?php echo htmlspecialchars($user['last_name']); ?>" required>
</div>
<div class="form-group">
<label for="email" class="form-label">Email</label>
<input type="email" id="email" name="email" class="form-control"
value="<?php echo htmlspecialchars($user['email']); ?>" required>
</div>
<div class="form-group">
<label for="phone" class="form-label">Telefono (opzionale)</label>
<input type="tel" id="phone" name="phone" class="form-control"
value="<?php echo htmlspecialchars($user['phone'] ?? ''); ?>">
</div>
<button type="submit" name="update_profile" class="btn btn-primary">
Aggiorna Profilo
</button>
</form>
</div>
<!-- Form Cambio Password -->
<div class="card">
<h3 class="card-header">Cambia Password</h3>
<form method="POST" action="">
<div class="form-group">
<label for="current_password" class="form-label">Password Attuale</label>
<input type="password" id="current_password" name="current_password"
class="form-control" required>
</div>
<div class="form-group">
<label for="new_password" class="form-label">Nuova Password</label>
<input type="password" id="new_password" name="new_password"
class="form-control" required minlength="6">
</div>
<div class="form-group">
<label for="confirm_password" class="form-label">Conferma Nuova Password</label>
<input type="password" id="confirm_password" name="confirm_password"
class="form-control" required minlength="6">
</div>
<button type="submit" name="change_password" class="btn btn-primary">
Cambia Password
</button>
</form>
</div>
<!-- Info Account -->
<div class="card">
<h3 class="card-header">Informazioni Account</h3>
<p><strong>Registrato il:</strong> <?php echo format_date($user['created_at']); ?></p>
<?php if ($user['last_login']): ?>
<p><strong>Ultimo accesso:</strong> <?php echo format_datetime($user['last_login']); ?></p>
<?php endif; ?>
</div>
</main>
</div>
</div>
<script src="../assets/js/main.js"></script>
</body>
</html>