fixed subscription
This commit is contained in:
parent
7f7dff32d9
commit
1551e6aca7
BIN
public/phototeachers/qrcodes/2-354894dfb52e966e.png
Normal file
BIN
public/phototeachers/qrcodes/2-354894dfb52e966e.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 425 B |
557
public/userarea/admin_subscription_plans.php
Normal file
557
public/userarea/admin_subscription_plans.php
Normal file
@ -0,0 +1,557 @@
|
||||
<?php
|
||||
// admin_subscription_plans.php
|
||||
|
||||
// Show errors (remove in production)
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
include('include/headscript.php');
|
||||
|
||||
// DB connection
|
||||
$dbHandler = DBHandlerSelect::getInstance();
|
||||
$pdo = $dbHandler->getConnection();
|
||||
|
||||
// Check login
|
||||
if (!isset($iduserlogin)) {
|
||||
die("Errore: ID utente non definito.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user is admin.
|
||||
* IMPORTANT: set your real admin role_id(s) here.
|
||||
*/
|
||||
function isAdmin(PDO $pdo, int $userId): bool
|
||||
{
|
||||
// TODO: adjust these role ids according to your system
|
||||
$adminRoleIds = [1]; // e.g. 1 = superadmin
|
||||
|
||||
$stmt = $pdo->prepare("SELECT role_id FROM auth_users WHERE id = ?");
|
||||
$stmt->execute([$userId]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) return false;
|
||||
return in_array((int)$row['role_id'], $adminRoleIds, true);
|
||||
}
|
||||
|
||||
if (!isAdmin($pdo, (int)$iduserlogin)) {
|
||||
die("Accesso negato: pagina riservata all'amministratore.");
|
||||
}
|
||||
|
||||
function formatMoneyFromCents(int $cents, string $currency): string
|
||||
{
|
||||
$amount = number_format($cents / 100, 2, ',', '.');
|
||||
return $amount . ' ' . strtoupper($currency);
|
||||
}
|
||||
|
||||
// Handle POST actions
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
// Common fields
|
||||
$code = trim($_POST['code'] ?? '');
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$description = trim($_POST['description'] ?? '');
|
||||
|
||||
$stripe_product_id = trim($_POST['stripe_product_id'] ?? '');
|
||||
$stripe_price_id = trim($_POST['stripe_price_id'] ?? '');
|
||||
|
||||
$currency = strtoupper(trim($_POST['currency'] ?? 'EUR'));
|
||||
$unit_amount = (int)($_POST['unit_amount'] ?? 0); // cents
|
||||
$interval = in_array(($_POST['interval'] ?? ''), ['day', 'week', 'month', 'year'], true) ? $_POST['interval'] : 'month';
|
||||
$interval_count = max(1, (int)($_POST['interval_count'] ?? 1));
|
||||
$trial_days = max(0, (int)($_POST['trial_days'] ?? 0));
|
||||
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
||||
|
||||
// ADD
|
||||
if ($action === 'add_plan') {
|
||||
if ($code === '' || $name === '') {
|
||||
$error = "Code e Nome sono obbligatori.";
|
||||
} elseif ($stripe_price_id === '') {
|
||||
$error = "Stripe Price ID è obbligatorio (campo NOT NULL in tabella).";
|
||||
} elseif (strlen($currency) !== 3) {
|
||||
$error = "Currency deve essere nel formato ISO (es. EUR).";
|
||||
} elseif ($unit_amount < 0) {
|
||||
$error = "Unit amount non può essere negativo.";
|
||||
} else {
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO billing_plans
|
||||
(code, name, description, stripe_product_id, stripe_price_id, currency, unit_amount, `interval`, interval_count, trial_days, is_active, created_at, updated_at)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
");
|
||||
$stmt->execute([
|
||||
$code,
|
||||
$name,
|
||||
$description ?: null,
|
||||
$stripe_product_id ?: null,
|
||||
$stripe_price_id,
|
||||
$currency,
|
||||
$unit_amount,
|
||||
$interval,
|
||||
$interval_count,
|
||||
$trial_days,
|
||||
$is_active
|
||||
]);
|
||||
$success_message = "Piano creato con successo.";
|
||||
} catch (PDOException $e) {
|
||||
$error = "Errore durante la creazione del piano: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EDIT
|
||||
if ($action === 'edit_plan') {
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
$error = "ID piano non valido.";
|
||||
} elseif ($code === '' || $name === '') {
|
||||
$error = "Code e Nome sono obbligatori.";
|
||||
} elseif ($stripe_price_id === '') {
|
||||
$error = "Stripe Price ID è obbligatorio (campo NOT NULL in tabella).";
|
||||
} elseif (strlen($currency) !== 3) {
|
||||
$error = "Currency deve essere nel formato ISO (es. EUR).";
|
||||
} elseif ($unit_amount < 0) {
|
||||
$error = "Unit amount non può essere negativo.";
|
||||
} else {
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE billing_plans
|
||||
SET
|
||||
code = ?,
|
||||
name = ?,
|
||||
description = ?,
|
||||
stripe_product_id = ?,
|
||||
stripe_price_id = ?,
|
||||
currency = ?,
|
||||
unit_amount = ?,
|
||||
`interval` = ?,
|
||||
interval_count = ?,
|
||||
trial_days = ?,
|
||||
is_active = ?,
|
||||
updated_at = NOW()
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([
|
||||
$code,
|
||||
$name,
|
||||
$description ?: null,
|
||||
$stripe_product_id ?: null,
|
||||
$stripe_price_id,
|
||||
$currency,
|
||||
$unit_amount,
|
||||
$interval,
|
||||
$interval_count,
|
||||
$trial_days,
|
||||
$is_active,
|
||||
$id
|
||||
]);
|
||||
$success_message = "Piano aggiornato con successo.";
|
||||
} catch (PDOException $e) {
|
||||
$error = "Errore durante l'aggiornamento del piano: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DISABLE
|
||||
if ($action === 'disable_plan') {
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
$error = "ID piano non valido.";
|
||||
} else {
|
||||
$stmt = $pdo->prepare("UPDATE billing_plans SET is_active = 0, updated_at = NOW() WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$success_message = "Piano disattivato.";
|
||||
}
|
||||
}
|
||||
|
||||
// ENABLE
|
||||
if ($action === 'enable_plan') {
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
$error = "ID piano non valido.";
|
||||
} else {
|
||||
$stmt = $pdo->prepare("UPDATE billing_plans SET is_active = 1, updated_at = NOW() WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$success_message = "Piano riattivato.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch plans
|
||||
$stmt = $pdo->prepare("SELECT * FROM billing_plans ORDER BY is_active DESC, name ASC");
|
||||
$stmt->execute();
|
||||
$plans = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
?>
|
||||
|
||||
<!doctype html>
|
||||
<html lang="it">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||
<?php include('cssinclude.php'); ?>
|
||||
<?php include('siteinfo.php'); ?>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<?php include('include/navbar.php'); ?>
|
||||
<?php include('include/topbar.php'); ?>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<div class="page-content">
|
||||
|
||||
<div class="card radius-10 mb-4">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<h5 class="mb-1">Subscription Plans (Admin)</h5>
|
||||
<p class="mb-0 text-muted">Gestione piani abbonamento delle scuole</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addPlanModal">
|
||||
<i class="bx bx-plus"></i> Nuovo Piano
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (isset($error)): ?>
|
||||
<div class="alert alert-danger" role="alert"><?php echo htmlspecialchars($error); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($success_message)): ?>
|
||||
<div class="alert alert-success" role="alert"><?php echo htmlspecialchars($success_message); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card radius-10">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Elenco Piani</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table id="plansTable" class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Nome</th>
|
||||
<th>Prezzo</th>
|
||||
<th>Intervallo</th>
|
||||
<th>Trial</th>
|
||||
<th>Stripe Product</th>
|
||||
<th>Stripe Price *</th>
|
||||
<th>Stato</th>
|
||||
<th>Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($plans as $p): ?>
|
||||
<tr>
|
||||
<td><span class="badge bg-dark"><?php echo htmlspecialchars($p['code']); ?></span></td>
|
||||
<td>
|
||||
<div class="fw-bold"><?php echo htmlspecialchars($p['name']); ?></div>
|
||||
<?php if (!empty($p['description'])): ?>
|
||||
<div class="text-muted small"><?php echo nl2br(htmlspecialchars($p['description'])); ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo formatMoneyFromCents((int)$p['unit_amount'], $p['currency']); ?></td>
|
||||
<td><?php echo (int)$p['interval_count'] . ' / ' . htmlspecialchars($p['interval']); ?></td>
|
||||
<td><?php echo (int)$p['trial_days'] > 0 ? ((int)$p['trial_days'] . ' gg') : '—'; ?></td>
|
||||
<td>
|
||||
<?php if (!empty($p['stripe_product_id'])): ?>
|
||||
<span class="badge bg-secondary"><?php echo htmlspecialchars($p['stripe_product_id']); ?></span>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">—</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-info"><?php echo htmlspecialchars($p['stripe_price_id']); ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge <?php echo ((int)$p['is_active'] === 1) ? 'bg-success' : 'bg-danger'; ?>">
|
||||
<?php echo ((int)$p['is_active'] === 1) ? 'Attivo' : 'Disattivo'; ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-nowrap">
|
||||
<button class="btn btn-sm btn-warning"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#editPlanModal"
|
||||
onclick='fillEditPlanModal(<?php echo json_encode([
|
||||
"id" => (int)$p["id"],
|
||||
"code" => $p["code"],
|
||||
"name" => $p["name"],
|
||||
"description" => $p["description"],
|
||||
"stripe_product_id" => $p["stripe_product_id"],
|
||||
"stripe_price_id" => $p["stripe_price_id"],
|
||||
"currency" => $p["currency"],
|
||||
"unit_amount" => (int)$p["unit_amount"],
|
||||
"interval" => $p["interval"],
|
||||
"interval_count" => (int)$p["interval_count"],
|
||||
"trial_days" => (int)$p["trial_days"],
|
||||
"is_active" => (int)$p["is_active"],
|
||||
]); ?>)'>
|
||||
<i class="bx bx-edit"></i>
|
||||
</button>
|
||||
|
||||
<?php if ((int)$p['is_active'] === 1): ?>
|
||||
<form method="POST" style="display:inline;" onsubmit="return confirm('Disattivare questo piano?');">
|
||||
<input type="hidden" name="action" value="disable_plan">
|
||||
<input type="hidden" name="id" value="<?php echo (int)$p['id']; ?>">
|
||||
<button class="btn btn-sm btn-danger" type="submit" title="Disattiva">
|
||||
<i class="bx bx-power-off"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<form method="POST" style="display:inline;" onsubmit="return confirm('Riattivare questo piano?');">
|
||||
<input type="hidden" name="action" value="enable_plan">
|
||||
<input type="hidden" name="id" value="<?php echo (int)$p['id']; ?>">
|
||||
<button class="btn btn-sm btn-success" type="submit" title="Riattiva">
|
||||
<i class="bx bx-check-circle"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 text-muted small">
|
||||
<strong>Nota:</strong> `unit_amount` è in centesimi (es. 2990 = 29,90 EUR)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overlay toggle-icon"></div>
|
||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||
<?php include('include/footer.php'); ?>
|
||||
</div>
|
||||
|
||||
<?php include('jsinclude.php'); ?>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#plansTable').DataTable({
|
||||
"language": {
|
||||
"url": "//cdn.datatables.net/plug-ins/1.10.25/i18n/Italian.json"
|
||||
},
|
||||
"pageLength": 10,
|
||||
"lengthMenu": [10, 25, 50, 100],
|
||||
"order": [
|
||||
[1, "asc"]
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
function fillEditPlanModal(p) {
|
||||
document.getElementById('edit_id').value = p.id;
|
||||
document.getElementById('edit_code').value = p.code || '';
|
||||
document.getElementById('edit_name').value = p.name || '';
|
||||
document.getElementById('edit_description').value = p.description || '';
|
||||
document.getElementById('edit_stripe_product_id').value = p.stripe_product_id || '';
|
||||
document.getElementById('edit_stripe_price_id').value = p.stripe_price_id || '';
|
||||
document.getElementById('edit_currency').value = p.currency || 'EUR';
|
||||
document.getElementById('edit_unit_amount').value = (p.unit_amount ?? 0);
|
||||
document.getElementById('edit_interval').value = p.interval || 'month';
|
||||
document.getElementById('edit_interval_count').value = p.interval_count || 1;
|
||||
document.getElementById('edit_trial_days').value = p.trial_days || 0;
|
||||
document.getElementById('edit_is_active').checked = (parseInt(p.is_active, 10) === 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- ADD MODAL -->
|
||||
<div class="modal fade" id="addPlanModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Nuovo Piano</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
||||
</div>
|
||||
|
||||
<form method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="action" value="add_plan">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Code *</label>
|
||||
<input type="text" name="code" class="form-control" required>
|
||||
</div>
|
||||
<div class="col-md-8 mb-3">
|
||||
<label class="form-label">Nome *</label>
|
||||
<input type="text" name="name" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Descrizione</label>
|
||||
<textarea name="description" class="form-control" rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Valuta</label>
|
||||
<input type="text" name="currency" class="form-control" value="EUR" maxlength="3">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Unit amount (centesimi) *</label>
|
||||
<input type="number" name="unit_amount" class="form-control" min="0" value="0" required>
|
||||
<small class="text-muted">es: 2990 = 29,90</small>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Trial days</label>
|
||||
<input type="number" name="trial_days" class="form-control" min="0" value="0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Interval</label>
|
||||
<select name="interval" class="form-control">
|
||||
<option value="day">day</option>
|
||||
<option value="week">week</option>
|
||||
<option value="month" selected>month</option>
|
||||
<option value="year">year</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Interval count</label>
|
||||
<input type="number" name="interval_count" class="form-control" min="1" value="1">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Stato</label>
|
||||
<div class="form-check form-switch mt-2">
|
||||
<input class="form-check-input" type="checkbox" name="is_active" value="1" checked>
|
||||
<label class="form-check-label">Attivo</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Stripe product id</label>
|
||||
<input type="text" name="stripe_product_id" class="form-control" placeholder="prod_... (optional)">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Stripe price id *</label>
|
||||
<input type="text" name="stripe_price_id" class="form-control" placeholder="price_..." required>
|
||||
<small class="text-muted">Obbligatorio (NOT NULL)</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
|
||||
<button type="submit" class="btn btn-primary">Crea Piano</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EDIT MODAL -->
|
||||
<div class="modal fade" id="editPlanModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Modifica Piano</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
||||
</div>
|
||||
|
||||
<form method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="action" value="edit_plan">
|
||||
<input type="hidden" name="id" id="edit_id">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Code *</label>
|
||||
<input type="text" name="code" id="edit_code" class="form-control" required>
|
||||
</div>
|
||||
<div class="col-md-8 mb-3">
|
||||
<label class="form-label">Nome *</label>
|
||||
<input type="text" name="name" id="edit_name" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Descrizione</label>
|
||||
<textarea name="description" id="edit_description" class="form-control" rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Valuta</label>
|
||||
<input type="text" name="currency" id="edit_currency" class="form-control" maxlength="3">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Unit amount (centesimi) *</label>
|
||||
<input type="number" name="unit_amount" id="edit_unit_amount" class="form-control" min="0" required>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Trial days</label>
|
||||
<input type="number" name="trial_days" id="edit_trial_days" class="form-control" min="0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Interval</label>
|
||||
<select name="interval" id="edit_interval" class="form-control">
|
||||
<option value="day">day</option>
|
||||
<option value="week">week</option>
|
||||
<option value="month">month</option>
|
||||
<option value="year">year</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Interval count</label>
|
||||
<input type="number" name="interval_count" id="edit_interval_count" class="form-control" min="1">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Stato</label>
|
||||
<div class="form-check form-switch mt-2">
|
||||
<input class="form-check-input" type="checkbox" name="is_active" id="edit_is_active" value="1">
|
||||
<label class="form-check-label">Attivo</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Stripe product id</label>
|
||||
<input type="text" name="stripe_product_id" id="edit_stripe_product_id" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Stripe price id *</label>
|
||||
<input type="text" name="stripe_price_id" id="edit_stripe_price_id" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
|
||||
<button type="submit" class="btn btn-primary">Salva</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
544
public/userarea/admin_subscriptions.php
Normal file
544
public/userarea/admin_subscriptions.php
Normal file
@ -0,0 +1,544 @@
|
||||
<?php
|
||||
// admin_subscriptions.php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
include('include/headscript.php');
|
||||
|
||||
// DB
|
||||
$dbHandler = DBHandlerSelect::getInstance();
|
||||
$pdo = $dbHandler->getConnection();
|
||||
|
||||
// ---- Auth check ----
|
||||
if (!isset($iduserlogin)) {
|
||||
die("Access denied.");
|
||||
}
|
||||
|
||||
// ---- Admin check (Vanguard usually uses role_id=1 for admin) ----
|
||||
// Adjust role_id list if needed.
|
||||
$stmt = $pdo->prepare("SELECT role_id, email FROM auth_users WHERE id = ? LIMIT 1");
|
||||
$stmt->execute([$iduserlogin]);
|
||||
$me = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$me || !in_array((int)$me['role_id'], [1])) {
|
||||
die("Access denied: admin only.");
|
||||
}
|
||||
|
||||
// ---- Handle POST actions ----
|
||||
$success_message = null;
|
||||
$error = null;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
$action = $_POST['action'];
|
||||
|
||||
// Update school status (local app status)
|
||||
if ($action === 'update_school_status') {
|
||||
$school_id = (int)($_POST['school_id'] ?? 0);
|
||||
$new_status = $_POST['status'] ?? 'active';
|
||||
$allowed = ['active', 'inactive', 'suspended'];
|
||||
|
||||
if ($school_id <= 0 || !in_array($new_status, $allowed, true)) {
|
||||
$error = "Invalid request.";
|
||||
} else {
|
||||
$stmt = $pdo->prepare("UPDATE schools SET status = ? WHERE id = ?");
|
||||
if ($stmt->execute([$new_status, $school_id])) {
|
||||
$success_message = "School status updated.";
|
||||
} else {
|
||||
$error = "Failed updating school status.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Stripe actions (cancel, resume, change plan) should call Stripe API.
|
||||
// Here we provide placeholders so UI is ready.
|
||||
if ($action === 'flag_cancel_at_period_end') {
|
||||
$sub_id = (int)($_POST['subscription_row_id'] ?? 0);
|
||||
$flag = (int)($_POST['flag'] ?? 0);
|
||||
$school_id = (int)($_POST['school_id'] ?? 0);
|
||||
|
||||
if ($sub_id <= 0 || $school_id <= 0 || !in_array($flag, [0, 1], true)) {
|
||||
$error = "Invalid request.";
|
||||
} else {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE school_subscriptions
|
||||
SET cancel_at_period_end = ?
|
||||
WHERE id = ? AND school_id = ?
|
||||
");
|
||||
$ok = $stmt->execute([$flag, $sub_id, $school_id]);
|
||||
|
||||
if ($ok) {
|
||||
$success_message = $flag ? "Marked cancel at period end (LOCAL)." : "Unmarked cancel at period end (LOCAL).";
|
||||
} else {
|
||||
$error = "Failed updating subscription flag.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Filters (GET) ----
|
||||
$q = trim($_GET['q'] ?? '');
|
||||
$plan_id = (int)($_GET['plan_id'] ?? 0);
|
||||
$sub_status = trim($_GET['sub_status'] ?? ''); // e.g. active, trialing, past_due, canceled, incomplete, unpaid
|
||||
$school_status = trim($_GET['school_status'] ?? ''); // active, inactive, suspended
|
||||
$has_sub = $_GET['has_sub'] ?? ''; // '1' or '0' or ''
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
// Search by school name/email/owner email
|
||||
if ($q !== '') {
|
||||
$where[] = "(s.name LIKE ? OR s.email LIKE ? OR ou.email LIKE ? OR CONCAT(ou.first_name,' ',ou.last_name) LIKE ?)";
|
||||
$like = '%' . $q . '%';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
}
|
||||
|
||||
if ($plan_id > 0) {
|
||||
$where[] = "ss.plan_id = ?";
|
||||
$params[] = $plan_id;
|
||||
}
|
||||
|
||||
if ($sub_status !== '') {
|
||||
$where[] = "ss.status = ?";
|
||||
$params[] = $sub_status;
|
||||
}
|
||||
|
||||
if ($school_status !== '') {
|
||||
$where[] = "s.status = ?";
|
||||
$params[] = $school_status;
|
||||
}
|
||||
|
||||
if ($has_sub === '1') {
|
||||
$where[] = "ss.id IS NOT NULL";
|
||||
} elseif ($has_sub === '0') {
|
||||
$where[] = "ss.id IS NULL";
|
||||
}
|
||||
|
||||
$sqlWhere = '';
|
||||
if (!empty($where)) {
|
||||
$sqlWhere = "WHERE " . implode(" AND ", $where);
|
||||
}
|
||||
|
||||
// ---- Load plans for filter dropdown ----
|
||||
$stmt = $pdo->prepare("SELECT id, code, name, currency, unit_amount, `interval`, interval_count, is_active FROM billing_plans ORDER BY is_active DESC, unit_amount ASC, name ASC");
|
||||
$stmt->execute();
|
||||
$plans = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// ---- Main query: schools + subscription + owner + plan ----
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
s.id AS school_id,
|
||||
s.name AS school_name,
|
||||
s.email AS school_email,
|
||||
s.status AS school_status,
|
||||
s.created_at AS school_created_at,
|
||||
|
||||
ou.id AS owner_id,
|
||||
ou.first_name AS owner_first_name,
|
||||
ou.last_name AS owner_last_name,
|
||||
ou.email AS owner_email,
|
||||
|
||||
ss.id AS subscription_row_id,
|
||||
ss.stripe_customer_id,
|
||||
ss.stripe_subscription_id,
|
||||
COALESCE(ss.status, 'none') AS subscription_status,
|
||||
ss.current_period_start,
|
||||
ss.current_period_end,
|
||||
ss.trial_start,
|
||||
ss.trial_end,
|
||||
ss.cancel_at_period_end,
|
||||
ss.updated_at AS subscription_updated_at,
|
||||
|
||||
bp.id AS plan_id,
|
||||
bp.code AS plan_code,
|
||||
bp.name AS plan_name,
|
||||
bp.currency,
|
||||
bp.unit_amount,
|
||||
bp.`interval`,
|
||||
bp.interval_count
|
||||
|
||||
FROM schools s
|
||||
JOIN auth_users ou ON ou.id = s.owner_id
|
||||
LEFT JOIN school_subscriptions ss ON ss.school_id = s.id
|
||||
LEFT JOIN billing_plans bp ON bp.id = ss.plan_id
|
||||
|
||||
$sqlWhere
|
||||
ORDER BY
|
||||
-- Put schools without subscription at the bottom
|
||||
CASE WHEN ss.id IS NULL THEN 1 ELSE 0 END ASC,
|
||||
|
||||
-- Show problematic subscriptions first
|
||||
FIELD(ss.status, 'past_due','unpaid','incomplete','incomplete_expired','canceled','paused','trialing','active') ASC,
|
||||
|
||||
s.created_at DESC
|
||||
|
||||
");
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// ---- Helpers ----
|
||||
function h($v)
|
||||
{
|
||||
return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function moneyFmt($unit_amount, $currency)
|
||||
{
|
||||
if ($unit_amount === null || $currency === null) return '—';
|
||||
$amount = ((int)$unit_amount) / 100;
|
||||
return number_format($amount, 2, ',', '.') . ' ' . strtoupper($currency);
|
||||
}
|
||||
|
||||
function dateFmt($dt)
|
||||
{
|
||||
if (!$dt) return '—';
|
||||
// Accept both timestamp and datetime strings
|
||||
$ts = is_numeric($dt) ? (int)$dt : strtotime($dt);
|
||||
if (!$ts) return '—';
|
||||
return date('d/m/Y', $ts);
|
||||
}
|
||||
|
||||
function badgeClassForSub($status)
|
||||
{
|
||||
$map = [
|
||||
'active' => 'bg-success',
|
||||
'trialing' => 'bg-info',
|
||||
'past_due' => 'bg-warning',
|
||||
'unpaid' => 'bg-danger',
|
||||
'incomplete' => 'bg-warning',
|
||||
'incomplete_expired' => 'bg-danger',
|
||||
'canceled' => 'bg-secondary',
|
||||
'paused' => 'bg-secondary',
|
||||
'none' => 'bg-secondary',
|
||||
];
|
||||
return $map[$status] ?? 'bg-dark';
|
||||
}
|
||||
|
||||
function badgeClassForSchool($status)
|
||||
{
|
||||
$map = [
|
||||
'active' => 'bg-success',
|
||||
'inactive' => 'bg-secondary',
|
||||
'suspended' => 'bg-danger',
|
||||
];
|
||||
return $map[$status] ?? 'bg-dark';
|
||||
}
|
||||
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="it">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||
<?php include('cssinclude.php'); ?>
|
||||
<?php include('siteinfo.php'); ?>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<?php include('include/navbar.php'); ?>
|
||||
<?php include('include/topbar.php'); ?>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<div class="page-content">
|
||||
|
||||
<div class="card radius-10 mb-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<h5 class="mb-0">Admin Subscriptions</h5>
|
||||
<div class="text-muted small">Schools + Stripe subscription status overview</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="admin_billing_plans.php" class="btn btn-outline-primary">
|
||||
<i class="bx bx-list-ul"></i> Billing Plans
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($success_message): ?>
|
||||
<div class="alert alert-success"><?php echo h($success_message); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?php echo h($error); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="card radius-10 mb-4">
|
||||
<div class="card-body">
|
||||
<form method="GET" class="row g-2 align-items-end">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Search</label>
|
||||
<input type="text" class="form-control" name="q" value="<?php echo h($q); ?>" placeholder="School / email / owner">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Plan</label>
|
||||
<select class="form-control" name="plan_id">
|
||||
<option value="0">All plans</option>
|
||||
<?php foreach ($plans as $p): ?>
|
||||
<option value="<?php echo (int)$p['id']; ?>" <?php echo ((int)$p['id'] === $plan_id) ? 'selected' : ''; ?>>
|
||||
<?php
|
||||
$label = $p['name'] . ' (' . $p['code'] . ') - ' . moneyFmt($p['unit_amount'], $p['currency']);
|
||||
$label .= ' / ' . $p['interval_count'] . ' ' . $p['interval'];
|
||||
if (!(int)$p['is_active']) $label .= ' [inactive]';
|
||||
echo h($label);
|
||||
?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Subscription status</label>
|
||||
<select class="form-control" name="sub_status">
|
||||
<option value="">All</option>
|
||||
<?php foreach (['none', 'active', 'trialing', 'past_due', 'unpaid', 'incomplete', 'incomplete_expired', 'canceled', 'paused'] as $st): ?>
|
||||
<option value="<?php echo h($st); ?>" <?php echo ($sub_status === $st) ? 'selected' : ''; ?>>
|
||||
<?php echo h($st); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">School status</label>
|
||||
<select class="form-control" name="school_status">
|
||||
<option value="">All</option>
|
||||
<?php foreach (['active', 'inactive', 'suspended'] as $st): ?>
|
||||
<option value="<?php echo h($st); ?>" <?php echo ($school_status === $st) ? 'selected' : ''; ?>>
|
||||
<?php echo h($st); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-1">
|
||||
<label class="form-label">Has sub</label>
|
||||
<select class="form-control" name="has_sub">
|
||||
<option value="" <?php echo ($has_sub === '') ? 'selected' : ''; ?>>All</option>
|
||||
<option value="1" <?php echo ($has_sub === '1') ? 'selected' : ''; ?>>Yes</option>
|
||||
<option value="0" <?php echo ($has_sub === '0') ? 'selected' : ''; ?>>No</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 d-flex gap-2 mt-2">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<i class="bx bx-filter"></i> Apply
|
||||
</button>
|
||||
<a class="btn btn-outline-secondary" href="admin_subscriptions.php">Reset</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results -->
|
||||
<div class="card radius-10">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||
<div>
|
||||
<h6 class="mb-0">Schools</h6>
|
||||
<div class="text-muted small"><?php echo (int)count($rows); ?> rows</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="min-width:260px;">School</th>
|
||||
<th style="min-width:220px;">Owner</th>
|
||||
<th style="min-width:190px;">School status</th>
|
||||
<th style="min-width:160px;">Subscription</th>
|
||||
<th style="min-width:220px;">Plan</th>
|
||||
<th style="min-width:200px;">Period</th>
|
||||
<th style="min-width:200px;">Trial</th>
|
||||
<th style="min-width:140px;">Cancel at period end</th>
|
||||
<th style="min-width:260px;">Stripe IDs</th>
|
||||
<th style="min-width:160px;">Updated</th>
|
||||
<th style="min-width:180px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $row): ?>
|
||||
<?php
|
||||
$hasSubscription = !empty($row['subscription_row_id']);
|
||||
$subStatus = $row['subscription_status'] ?? 'none';
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-bold"><?php echo h($row['school_name']); ?></div>
|
||||
<div class="text-muted small">
|
||||
ID: <?php echo (int)$row['school_id']; ?> · <?php echo h($row['school_email']); ?>
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
Created: <?php echo h(dateFmt($row['school_created_at'])); ?>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div class="fw-bold"><?php echo h(trim(($row['owner_first_name'] ?? '') . ' ' . ($row['owner_last_name'] ?? ''))); ?></div>
|
||||
<div class="text-muted small"><?php echo h($row['owner_email']); ?></div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<form method="POST" class="d-flex gap-2 align-items-center">
|
||||
<input type="hidden" name="action" value="update_school_status">
|
||||
<input type="hidden" name="school_id" value="<?php echo (int)$row['school_id']; ?>">
|
||||
|
||||
<select name="status" class="form-control form-control-sm" style="min-width:140px;">
|
||||
<?php foreach (['active', 'inactive', 'suspended'] as $st): ?>
|
||||
<option value="<?php echo h($st); ?>" <?php echo ($row['school_status'] === $st) ? 'selected' : ''; ?>>
|
||||
<?php echo h($st); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary">
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-2">
|
||||
<span class="badge <?php echo h(badgeClassForSchool($row['school_status'])); ?>">
|
||||
<?php echo h($row['school_status']); ?>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span class="badge <?php echo h(badgeClassForSub($subStatus)); ?>">
|
||||
<?php echo h($subStatus); ?>
|
||||
</span>
|
||||
|
||||
<?php if ($hasSubscription): ?>
|
||||
<div class="text-muted small mt-2">
|
||||
Qty: <?php echo (int)($row['quantity'] ?? 1); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<?php if ($hasSubscription && !empty($row['plan_id'])): ?>
|
||||
<div class="fw-bold"><?php echo h($row['plan_name']); ?></div>
|
||||
<div class="text-muted small">
|
||||
<?php echo h($row['plan_code']); ?> · <?php echo h(moneyFmt($row['unit_amount'], $row['currency'])); ?>
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
Every <?php echo (int)$row['interval_count']; ?> <?php echo h($row['interval']); ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
—
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<?php if ($hasSubscription): ?>
|
||||
<div class="text-muted small">
|
||||
Start: <?php echo h(dateFmt($row['current_period_start'])); ?>
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
End: <?php echo h(dateFmt($row['current_period_end'])); ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
—
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<?php if ($hasSubscription): ?>
|
||||
<div class="text-muted small">
|
||||
Start: <?php echo h(dateFmt($row['trial_start'])); ?>
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
End: <?php echo h(dateFmt($row['trial_end'])); ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
—
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<?php if ($hasSubscription): ?>
|
||||
<?php if ((int)$row['cancel_at_period_end'] === 1): ?>
|
||||
<span class="badge bg-warning">Yes</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary">No</span>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
—
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<?php if ($hasSubscription): ?>
|
||||
<div class="text-muted small">
|
||||
cust: <?php echo h($row['stripe_customer_id'] ?: '—'); ?>
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
sub: <?php echo h($row['stripe_subscription_id'] ?: '—'); ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
—
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<?php echo h(dateFmt($row['subscription_updated_at'])); ?>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<?php if ($hasSubscription): ?>
|
||||
<form method="POST" class="d-inline">
|
||||
<input type="hidden" name="action" value="flag_cancel_at_period_end">
|
||||
<input type="hidden" name="subscription_row_id" value="<?php echo (int)$row['subscription_row_id']; ?>">
|
||||
<input type="hidden" name="school_id" value="<?php echo (int)$row['school_id']; ?>">
|
||||
<input type="hidden" name="flag" value="<?php echo ((int)$row['cancel_at_period_end'] === 1) ? 0 : 1; ?>">
|
||||
|
||||
<?php if ((int)$row['cancel_at_period_end'] === 1): ?>
|
||||
<button type="submit" class="btn btn-sm btn-outline-success">
|
||||
Unmark
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<button type="submit" class="btn btn-sm btn-outline-warning">
|
||||
Mark
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">—</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if (empty($rows)): ?>
|
||||
<tr>
|
||||
<td colspan="11" class="text-center text-muted py-4">No results</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- /page-content -->
|
||||
</div> <!-- /page-wrapper -->
|
||||
|
||||
<?php if (file_exists('include/footer.php')) include('include/footer.php'); ?>
|
||||
</div> <!-- /wrapper -->
|
||||
|
||||
<?php if (file_exists('jsinclude.php')) include('jsinclude.php'); ?>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -145,11 +145,12 @@ if (!empty($_SESSION['school_id'])) {
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
<li class="menu-label">Insegnanti</li>
|
||||
<?php
|
||||
//menù teacher
|
||||
if ((Auth::user()->hasRole('school_owner')) || (Auth::user()->hasRole('Admin')) || (Auth::user()->hasRole('teacher'))) : ?>
|
||||
<li class="menu-label">Insegnanti</li>
|
||||
|
||||
if ((Auth::user()->hasRole('school_owner')) || (Auth::user()->hasRole('Admin'))) : ?>
|
||||
|
||||
<li>
|
||||
<a href="teacher_list.php">
|
||||
<div class="parent-icon"><i class="bx bx-chalkboard"></i></div>
|
||||
@ -159,12 +160,38 @@ if (!empty($_SESSION['school_id'])) {
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
if ((Auth::user()->hasRole('school_owner')) || (Auth::user()->hasRole('Admin')) || (Auth::user()->hasRole('teacher'))) : ?>
|
||||
|
||||
<li>
|
||||
<a href="teacher_page.php">
|
||||
<div class="parent-icon"><i class="bx bx-chalkboard"></i></div>
|
||||
<div class="menu-title">Il mio profilo</div>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
<?php
|
||||
//menù admin only
|
||||
if ((Auth::user()->hasRole('Admin'))) : ?>
|
||||
<li class="menu-label">Subscription Area</li>
|
||||
<li>
|
||||
<a href="admin_subscriptions.php" target="">
|
||||
<div class="parent-icon"><i class="bx bx-layout"></i></div>
|
||||
<div class="menu-title">Subscription</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="admin_subscription_plans.php" target="">
|
||||
<div class="parent-icon"><i class="bx bx-layout"></i></div>
|
||||
<div class="menu-title">Subscription Plan</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="menu-label">Others</li>
|
||||
<li>
|
||||
<a href="emplate/index.html" target="_blank">
|
||||
<a href="template/index.html" target="_blank">
|
||||
<div class="parent-icon"><i class="bx bx-layout"></i></div>
|
||||
<div class="menu-title">Template</div>
|
||||
</a>
|
||||
|
||||
BIN
public/userarea/photoschool/2-1769006694-logo.jpg
Normal file
BIN
public/userarea/photoschool/2-1769006694-logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 242 KiB After Width: | Height: | Size: 242 KiB |
BIN
public/userarea/phototeachers/2-1769005707-profile.jpg
Normal file
BIN
public/userarea/phototeachers/2-1769005707-profile.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 242 KiB |
@ -35,9 +35,22 @@ $stmt->execute();
|
||||
$categories = $stmt->fetchAll();
|
||||
|
||||
// Recupera tutti gli insegnanti della scuola
|
||||
$stmt = $pdo->prepare("SELECT id, first_name, last_name FROM teachers WHERE user_id = ? AND status = 'active' ORDER BY first_name, last_name");
|
||||
$stmt->execute([$iduserlogin]);
|
||||
$teachers = $stmt->fetchAll();
|
||||
// Teachers linked to this school (active/pending)
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
t.id,
|
||||
u.first_name,
|
||||
u.last_name
|
||||
FROM teacher_schools ts
|
||||
JOIN teachers t ON ts.teacher_id = t.id
|
||||
JOIN auth_users u ON t.user_id = u.id
|
||||
WHERE ts.school_id = ?
|
||||
AND ts.status IN ('active','pending')
|
||||
ORDER BY u.first_name, u.last_name
|
||||
");
|
||||
$stmt->execute([$school_id]);
|
||||
$teachers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
|
||||
// Funzione per ridimensionare l'immagine
|
||||
function resizeImage($source_path, $dest_path, $max_width = 800)
|
||||
|
||||
@ -120,6 +120,7 @@ $teachers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
<title>Insegnanti - <?= htmlspecialchars($school_name) ?></title>
|
||||
<?php include('cssinclude.php'); ?>
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script>
|
||||
<style>
|
||||
.quill-wrapper {
|
||||
min-height: 260px;
|
||||
@ -238,7 +239,9 @@ $teachers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
<div class="col-12">
|
||||
<label>Descrizione</label>
|
||||
<div id="quill-add-editor" style="min-height:260px;"></div>
|
||||
<div class="quill-wrapper">
|
||||
<div id="quill-add-editor"></div>
|
||||
</div>
|
||||
<input type="hidden" name="description" id="add-desc-hidden">
|
||||
</div>
|
||||
|
||||
|
||||
486
public/userarea/teacher_page.php
Normal file
486
public/userarea/teacher_page.php
Normal file
@ -0,0 +1,486 @@
|
||||
<?php
|
||||
// teacher_page.php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
include('include/headscript.php');
|
||||
|
||||
// QR Code library
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use Endroid\QrCode\QrCode;
|
||||
use Endroid\QrCode\Writer\PngWriter;
|
||||
|
||||
$dbHandler = DBHandlerSelect::getInstance();
|
||||
$pdo = $dbHandler->getConnection();
|
||||
|
||||
if (!isset($iduserlogin)) {
|
||||
die("Errore: ID utente non definito.");
|
||||
}
|
||||
|
||||
/**
|
||||
* QR helper compatible with older/newer Endroid versions (best-effort)
|
||||
*/
|
||||
function writeQrPng($text, $filename, $size = 150, $margin = 10)
|
||||
{
|
||||
// Your installed version seems to require text in constructor
|
||||
$qrCode = new \Endroid\QrCode\QrCode($text);
|
||||
|
||||
if (method_exists($qrCode, 'setSize')) {
|
||||
$qrCode->setSize($size);
|
||||
} elseif (method_exists($qrCode, 'setModuleSize')) {
|
||||
$module = max(3, (int)round($size / 25));
|
||||
$qrCode->setModuleSize($module);
|
||||
}
|
||||
|
||||
if (method_exists($qrCode, 'setMargin')) {
|
||||
$qrCode->setMargin($margin);
|
||||
} elseif (method_exists($qrCode, 'setPadding')) {
|
||||
$qrCode->setPadding($margin);
|
||||
}
|
||||
|
||||
$writer = new \Endroid\QrCode\Writer\PngWriter();
|
||||
|
||||
if (method_exists($writer, 'writeFile')) {
|
||||
$writer->writeFile($qrCode, $filename);
|
||||
} else {
|
||||
$result = $writer->write($qrCode);
|
||||
if (is_object($result) && method_exists($result, 'saveToFile')) {
|
||||
$result->saveToFile($filename);
|
||||
} else {
|
||||
file_put_contents($filename, (string)$result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateUniqueCode($pdo, $length = 16)
|
||||
{
|
||||
do {
|
||||
$code = bin2hex(random_bytes($length / 2));
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM teachers WHERE unique_code = ?");
|
||||
$stmt->execute([$code]);
|
||||
} while ($stmt->fetchColumn() > 0);
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect view mode
|
||||
* - Owner view: teacher_page.php?id=TEACHER_ID (teachers.id)
|
||||
* - Teacher self view: teacher_page.php
|
||||
*/
|
||||
$teacher_id = (int)($_GET['id'] ?? 0);
|
||||
$is_owner_view = ($teacher_id > 0);
|
||||
|
||||
$success_message = $error = null;
|
||||
|
||||
/**
|
||||
* 1) LOAD teacher row
|
||||
*/
|
||||
if ($is_owner_view) {
|
||||
// OWNER VIEW: load teacher by teachers.id only if owner has rights
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
t.*,
|
||||
u.first_name, u.last_name, u.email
|
||||
FROM teachers t
|
||||
JOIN auth_users u ON t.user_id = u.id
|
||||
JOIN teacher_schools ts ON ts.teacher_id = t.id
|
||||
JOIN schools s ON s.id = ts.school_id
|
||||
WHERE t.id = ?
|
||||
AND s.owner_id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$teacher_id, $iduserlogin]);
|
||||
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$teacher) {
|
||||
die("Errore: insegnante non trovata o non hai permessi.");
|
||||
}
|
||||
} else {
|
||||
// TEACHER SELF VIEW: load by logged user
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
t.*,
|
||||
u.first_name, u.last_name, u.email
|
||||
FROM auth_users u
|
||||
LEFT JOIN teachers t ON t.user_id = u.id
|
||||
WHERE u.id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$iduserlogin]);
|
||||
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// If not exists in teachers, CREATE IT before showing the form (auto-create)
|
||||
if (empty($teacher['id'])) {
|
||||
// ✅ SOLO QUI: quando auto-crei il profilo teacher (SELF VIEW), aggiungi created_by = iduserlogin
|
||||
|
||||
$unique_code = generateUniqueCode($pdo);
|
||||
|
||||
$stmtIns = $pdo->prepare("
|
||||
INSERT INTO teachers (user_id, unique_code, phone, description, specializations, profile_picture, status, created_by)
|
||||
VALUES (?, ?, NULL, '', '', '', 'active', ?)
|
||||
");
|
||||
$ok = $stmtIns->execute([$iduserlogin, $unique_code, $iduserlogin]);
|
||||
|
||||
|
||||
if (!$ok) {
|
||||
die("Errore: impossibile creare il profilo insegnante.");
|
||||
}
|
||||
|
||||
// Reload teacher after insert
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
t.*,
|
||||
u.first_name, u.last_name, u.email
|
||||
FROM teachers t
|
||||
JOIN auth_users u ON t.user_id = u.id
|
||||
WHERE t.user_id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$iduserlogin]);
|
||||
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Now teacher MUST exist (in owner view and in self view due to auto-create)
|
||||
*/
|
||||
$is_new = empty($teacher['id']); // should be false at this point
|
||||
|
||||
/**
|
||||
* 2) HANDLE POST (save)
|
||||
*/
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
// target user is the teacher being edited
|
||||
$target_user_id = $is_owner_view ? (int)$teacher['user_id'] : (int)$iduserlogin;
|
||||
|
||||
$first_name = trim($_POST['first_name'] ?? '');
|
||||
$last_name = trim($_POST['last_name'] ?? '');
|
||||
$phone = trim($_POST['phone'] ?? '');
|
||||
$description = trim($_POST['description'] ?? '');
|
||||
$specializations = trim($_POST['specializations'] ?? '');
|
||||
$status = (($_POST['status'] ?? 'active') === 'active') ? 'active' : 'inactive';
|
||||
|
||||
// Update auth_users names for the target teacher
|
||||
$stmt = $pdo->prepare("UPDATE auth_users SET first_name = ?, last_name = ? WHERE id = ?");
|
||||
$stmt->execute([$first_name, $last_name, $target_user_id]);
|
||||
|
||||
// Photo upload (use target user id in filename)
|
||||
$profile_picture = $teacher['profile_picture'] ?? '';
|
||||
if (!empty($_FILES['profile_picture']['name']) && $_FILES['profile_picture']['error'] === UPLOAD_ERR_OK) {
|
||||
|
||||
$ext = strtolower(pathinfo($_FILES['profile_picture']['name'], PATHINFO_EXTENSION));
|
||||
|
||||
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) {
|
||||
$new_name = "phototeachers/{$target_user_id}-" . time() . "-profile.$ext";
|
||||
|
||||
if (move_uploaded_file($_FILES['profile_picture']['tmp_name'], $new_name)) {
|
||||
if ($profile_picture && file_exists($profile_picture)) {
|
||||
@unlink($profile_picture);
|
||||
}
|
||||
$profile_picture = $new_name;
|
||||
} else {
|
||||
$error = "Errore caricamento foto.";
|
||||
}
|
||||
} else {
|
||||
$error = "Solo JPG, PNG, GIF ammessi.";
|
||||
}
|
||||
}
|
||||
|
||||
if (!$error) {
|
||||
// Update teachers row (always exists at this point)
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE teachers
|
||||
SET phone = ?, description = ?, specializations = ?, profile_picture = ?, status = ?
|
||||
WHERE user_id = ?
|
||||
");
|
||||
$success = $stmt->execute([
|
||||
$phone ?: null,
|
||||
$description,
|
||||
$specializations,
|
||||
$profile_picture,
|
||||
$status,
|
||||
$target_user_id
|
||||
]);
|
||||
|
||||
if ($success) {
|
||||
$success_message = "Dati aggiornati!";
|
||||
|
||||
// Reload teacher (with correct target user)
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT t.*, u.first_name, u.last_name, u.email
|
||||
FROM teachers t
|
||||
JOIN auth_users u ON t.user_id = u.id
|
||||
WHERE t.user_id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$target_user_id]);
|
||||
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} else {
|
||||
$error = "Errore aggiornamento.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 3) QR generation (after teacher is loaded)
|
||||
*/
|
||||
$qr_code_path = null;
|
||||
if (!empty($teacher['unique_code'])) {
|
||||
try {
|
||||
$unique_code = $teacher['unique_code'];
|
||||
|
||||
// IMPORTANT: file name uses target user_id (teacher user), not owner id
|
||||
$qr_user_id = (int)$teacher['user_id'];
|
||||
|
||||
$base_dir = __DIR__ . '/../../public/phototeachers/qrcodes/';
|
||||
$qr_filename = "{$base_dir}{$qr_user_id}-{$unique_code}.png";
|
||||
$qr_code_path = "phototeachers/qrcodes/{$qr_user_id}-{$unique_code}.png";
|
||||
|
||||
if (!file_exists($qr_filename)) {
|
||||
if (!is_dir($base_dir)) mkdir($base_dir, 0755, true);
|
||||
writeQrPng($unique_code, $qr_filename, 150, 10);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Errore QR: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!doctype html>
|
||||
<html lang="it">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Profilo Insegnante</title>
|
||||
<?php include('cssinclude.php'); ?>
|
||||
<?php include('siteinfo.php'); ?>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet" />
|
||||
|
||||
<style>
|
||||
.teacher-photo {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
max-height: 260px;
|
||||
object-fit: contain;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.quill-wrapper {
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
flex: 1;
|
||||
font-size: 15px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0 0 0.375rem 0.375rem;
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
min-height: 220px;
|
||||
}
|
||||
|
||||
.ql-toolbar {
|
||||
border-radius: 0.375rem 0.375rem 0 0;
|
||||
border-color: #ced4da;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<?php include('include/navbar.php'); ?>
|
||||
<?php include('include/topbar.php'); ?>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<div class="page-content">
|
||||
<div class="card radius-10">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Profilo Insegnante</h6>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<?php if (!empty($success_message)): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show">
|
||||
<?php echo htmlspecialchars($success_message); ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($error)): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show">
|
||||
<?php echo htmlspecialchars($error); ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST" enctype="multipart/form-data" id="teacherForm">
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-4 text-center">
|
||||
<img src="<?php echo $teacher['profile_picture'] ? htmlspecialchars($teacher['profile_picture']) : 'phototeachers/ndphoto.png'; ?>"
|
||||
alt="Foto Profilo" class="teacher-photo">
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="profile_picture" class="form-label">Carica nuova foto</label>
|
||||
<input type="file" class="form-control" id="profile_picture" name="profile_picture" accept="image/jpeg,image/png,image/gif">
|
||||
<small class="text-muted d-block mt-1">Max 2MB – JPG, PNG, GIF</small>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($teacher['unique_code']) && $qr_code_path): ?>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Codice Univoco</label>
|
||||
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['unique_code']); ?>" readonly>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">QR Code</label><br>
|
||||
<img src="<?php echo htmlspecialchars($qr_code_path); ?>" alt="QR Code" class="img-fluid shadow-sm" style="max-width: 180px;">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-8">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="first_name" class="form-label">Nome</label>
|
||||
<input type="text" class="form-control" id="first_name" name="first_name"
|
||||
value="<?php echo htmlspecialchars($teacher['first_name'] ?? ''); ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="last_name" class="form-label">Cognome</label>
|
||||
<input type="text" class="form-control" id="last_name" name="last_name"
|
||||
value="<?php echo htmlspecialchars($teacher['last_name'] ?? ''); ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" class="form-control" id="email" name="email"
|
||||
value="<?php echo htmlspecialchars($teacher['email'] ?? ''); ?>" readonly>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="phone" class="form-label">Telefono</label>
|
||||
<input type="tel" class="form-control" id="phone" name="phone"
|
||||
value="<?php echo htmlspecialchars($teacher['phone'] ?? ''); ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-12 form-section">
|
||||
<label class="form-label">Descrizione insegnante</label>
|
||||
<div class="quill-wrapper">
|
||||
<div id="quill-editor"></div>
|
||||
</div>
|
||||
<input type="hidden" name="description" id="description-hidden">
|
||||
</div>
|
||||
|
||||
<div class="col-12 form-section">
|
||||
<label for="specializations" class="form-label">Specializzazioni</label>
|
||||
<textarea class="form-control" id="specializations" name="specializations" rows="3"><?php echo htmlspecialchars($teacher['specializations'] ?? ''); ?></textarea>
|
||||
<small class="text-muted">Es: Hatha Yoga, Vinyasa, Yin, Restorative...</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Stato</label>
|
||||
<div class="form-check form-switch mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="status" name="status" value="active"
|
||||
<?php echo ($teacher['status'] ?? 'active') === 'active' ? 'checked' : ''; ?>>
|
||||
<label class="form-check-label" for="status">
|
||||
<?php echo ($teacher['status'] ?? 'active') === 'active' ? 'Attivo' : 'Inattivo'; ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Creato</label>
|
||||
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['created_at'] ?? ''); ?>" readonly>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Aggiornato</label>
|
||||
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['updated_at'] ?? ''); ?>" readonly>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mt-5">
|
||||
<button type="submit" class="btn btn-primary btn-lg px-5">
|
||||
Salva Modifiche
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overlay toggle-icon"></div>
|
||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||
<?php include('include/footer.php'); ?>
|
||||
</div>
|
||||
|
||||
<?php include('jsinclude.php'); ?>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script>
|
||||
<script>
|
||||
const quill = new Quill('#quill-editor', {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: [
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
['blockquote', 'code-block'],
|
||||
[{
|
||||
'header': [1, 2, 3, false]
|
||||
}],
|
||||
[{
|
||||
'color': ['#000000', '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff', '#808080', '#c0c0c0']
|
||||
}, {
|
||||
'background': []
|
||||
}],
|
||||
[{
|
||||
'list': 'ordered'
|
||||
}, {
|
||||
'list': 'bullet'
|
||||
}],
|
||||
[{
|
||||
'align': []
|
||||
}],
|
||||
['link', 'clean']
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
quill.root.innerHTML = `<?php echo addslashes($teacher['description'] ?? ''); ?>`;
|
||||
|
||||
document.getElementById('teacherForm').addEventListener('submit', function() {
|
||||
document.getElementById('description-hidden').value = quill.root.innerHTML;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user