Files
2026-09-05 13:41:26 +02:00

549 lines
23 KiB
PHP

<?php
include('include/headscript.php');
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (!$user->hasRole('Admin') && !$user->hasRole('SuperUser')) {
header('Location: import_dashboard.php');
exit;
}
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
/*
* Nome file del motore generico condiviso da tutte le routine engine.
* Deve corrispondere al file presente in routines/.
*/
$engineFilename = '_engine.php';
/*
* CSRF token.
*/
if (empty($_SESSION['routine_admin_csrf'])) {
$_SESSION['routine_admin_csrf'] = bin2hex(random_bytes(32));
}
$csrfToken = $_SESSION['routine_admin_csrf'];
function cleanInput($value)
{
return trim((string)($value ?? ''));
}
function redirectWithMessage($type, $message)
{
$_SESSION['routine_admin_flash'] = [
'type' => $type,
'message' => $message
];
header('Location: ' . $_SERVER['PHP_SELF']);
exit;
}
/*
* Gestione azioni POST.
*/
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$postedToken = $_POST['csrf_token'] ?? '';
if (!hash_equals($csrfToken, $postedToken)) {
throw new Exception('Token di sicurezza non valido.');
}
$action = cleanInput($_POST['action'] ?? '');
if ($action === 'create' || $action === 'update') {
$name = cleanInput($_POST['name'] ?? '');
$description = cleanInput($_POST['description'] ?? '');
$engine = (int)($_POST['engine'] ?? 0) === 1 ? 1 : 0;
$headerrow = (int)($_POST['headerrow'] ?? 1);
$filename = cleanInput($_POST['filename'] ?? '');
if ($name === '') {
throw new Exception('Il nome della routine è obbligatorio.');
}
if ($headerrow < 1) {
$headerrow = 1;
}
/*
* Se è una routine a motore, il filename è forzato al file del motore.
* Se è legacy, serve un filename di file reale.
*/
if ($engine === 1) {
$filename = $GLOBALS['engineFilename'];
} else {
if ($filename === '') {
throw new Exception('Per una routine di tipo File indica il nome del file (es. fendi.php).');
}
}
if ($action === 'create') {
// instruction è NOT NULL nello schema: valorizziamo sempre.
$instruction = $engine === 1
? 'Routine gestita dal motore generico (routine_step).'
: ('Routine su file: ' . $filename);
$stmt = $pdo->prepare("
INSERT INTO routine (name, description, instruction, filename, engine, headerrow)
VALUES (:name, :descr, :instr, :fn, :engine, :hr)
");
$stmt->execute([
':name' => $name,
':descr' => ($description !== '' ? $description : null),
':instr' => $instruction,
':fn' => $filename,
':engine' => $engine,
':hr' => $headerrow,
]);
redirectWithMessage('success', 'Routine creata correttamente.');
} else {
$idroutine = (int)($_POST['idroutine'] ?? 0);
if ($idroutine <= 0) {
throw new Exception('ID routine non valido.');
}
$stmt = $pdo->prepare("
UPDATE routine
SET name = :name,
description = :descr,
filename = :fn,
engine = :engine,
headerrow = :hr
WHERE idroutine = :id
");
$stmt->execute([
':name' => $name,
':descr' => ($description !== '' ? $description : null),
':fn' => $filename,
':engine' => $engine,
':hr' => $headerrow,
':id' => $idroutine,
]);
redirectWithMessage('success', 'Routine aggiornata correttamente.');
}
}
if ($action === 'delete') {
$idroutine = (int)($_POST['idroutine'] ?? 0);
if ($idroutine <= 0) {
throw new Exception('ID routine non valido.');
}
// Rimuove prima gli eventuali step collegati, poi la routine.
$stmt = $pdo->prepare("DELETE FROM routine_step WHERE idroutine = :id");
$stmt->execute([':id' => $idroutine]);
$stmt = $pdo->prepare("DELETE FROM routine WHERE idroutine = :id");
$stmt->execute([':id' => $idroutine]);
redirectWithMessage('success', 'Routine eliminata correttamente.');
}
throw new Exception('Azione non valida.');
} catch (Exception $e) {
redirectWithMessage('error', $e->getMessage());
}
}
/*
* Caricamento routine + conteggio step.
*/
$stmt = $pdo->query("
SELECT
r.idroutine,
r.name,
r.description,
r.filename,
r.engine,
r.headerrow,
(SELECT COUNT(*) FROM routine_step s WHERE s.idroutine = r.idroutine) AS step_count
FROM routine r
ORDER BY r.name ASC
");
$routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
$flash = $_SESSION['routine_admin_flash'] ?? null;
unset($_SESSION['routine_admin_flash']);
?>
<!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'); ?>
<title>Gestione Routine - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
<style>
.small-muted {
font-size: 12px;
color: #6c757d;
}
/* La tabella non deve sforare la pagina */
#routinesTable {
table-layout: fixed;
width: 100%;
}
#routinesTable td,
#routinesTable th {
overflow-wrap: break-word;
word-break: break-word;
white-space: normal;
vertical-align: middle;
}
/* Colonna descrizione: va a capo invece di allargare la tabella */
#routinesTable td.desc-cell {
white-space: normal;
overflow-wrap: break-word;
word-break: break-word;
max-width: 320px;
}
/* Le azioni restano su una riga */
#routinesTable td.table-actions {
white-space: nowrap;
}
.badge-engine {
background: #6f42c1;
color: #fff;
}
.badge-file {
background: #6c757d;
color: #fff;
}
.table-actions {
white-space: nowrap;
}
.filename-chip {
font-family: Consolas, Monaco, monospace;
font-size: 12px;
background: #f1f5f9;
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 2px 8px;
color: #334155;
}
.step-count-pill {
display: inline-block;
min-width: 26px;
text-align: center;
font-weight: 600;
border-radius: 999px;
padding: 2px 10px;
background: #eef2ff;
color: #4338ca;
border: 1px solid #e0e7ff;
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<?php include('top_stat_widget.php'); ?>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center justify-content-between">
<div>
<h6 class="mb-0">Gestione Routine</h6>
<small class="text-muted">Crea e configura le routine di importazione. Le routine di tipo Motore sono configurabili a step senza codice.</small>
</div>
<button type="button" class="btn btn-primary" id="btnAddRoutine">
<i class="bx bx-plus"></i> Nuova Routine
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="routinesTable" class="table table-striped table-bordered align-middle">
<thead>
<tr>
<th>Nome</th>
<th>Descrizione</th>
<th style="width: 120px;">Tipo</th>
<th style="width: 160px;">File</th>
<th style="width: 90px;">Header row</th>
<th style="width: 80px;">Step</th>
<th style="width: 130px;">Azioni</th>
</tr>
</thead>
<tbody>
<?php foreach ($routines as $row): ?>
<?php $isEngine = ((int)$row['engine'] === 1); ?>
<tr>
<td><?= htmlspecialchars($row['name'] ?? '', ENT_QUOTES, 'UTF-8'); ?></td>
<td class="small-muted desc-cell"><?= htmlspecialchars($row['description'] ?? '', ENT_QUOTES, 'UTF-8'); ?></td>
<td>
<?php if ($isEngine): ?>
<span class="badge badge-engine">Motore</span>
<?php else: ?>
<span class="badge badge-file">File</span>
<?php endif; ?>
</td>
<td><span class="filename-chip"><?= htmlspecialchars($row['filename'] ?? '', ENT_QUOTES, 'UTF-8'); ?></span></td>
<td class="text-center"><?= (int)$row['headerrow']; ?></td>
<td class="text-center">
<?php if ($isEngine): ?>
<span class="step-count-pill"><?= (int)$row['step_count']; ?></span>
<?php else: ?>
<span class="small-muted">—</span>
<?php endif; ?>
</td>
<td class="table-actions">
<?php if ($isEngine): ?>
<a class="btn btn-sm btn-outline-success"
href="routine_steps.php?idroutine=<?= (int)$row['idroutine']; ?>"
title="Gestisci step" data-bs-toggle="tooltip">
<i class="bx bx-list-ul"></i>
</a>
<?php endif; ?>
<button
type="button"
class="btn btn-sm btn-outline-primary btnEditRoutine"
data-id="<?= (int)$row['idroutine']; ?>"
data-name="<?= htmlspecialchars($row['name'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
data-description="<?= htmlspecialchars($row['description'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
data-engine="<?= (int)$row['engine']; ?>"
data-filename="<?= htmlspecialchars($row['filename'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
data-headerrow="<?= (int)$row['headerrow']; ?>"
title="Modifica" data-bs-toggle="tooltip">
<i class="bx bx-edit"></i>
</button>
<button
type="button"
class="btn btn-sm btn-outline-danger btnDeleteRoutine"
data-id="<?= (int)$row['idroutine']; ?>"
data-name="<?= htmlspecialchars($row['name'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
title="Elimina" data-bs-toggle="tooltip">
<i class="bx bx-trash"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</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>
<!-- Modal Routine -->
<div class="modal fade" id="routineModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<form method="POST" class="modal-content" id="routineForm">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="action" id="formAction" value="create">
<input type="hidden" name="idroutine" id="idroutine" value="">
<div class="modal-header">
<h5 class="modal-title" id="routineModalTitle">Nuova Routine</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
</div>
<div class="modal-body">
<div class="row g-3">
<div class="col-md-12">
<label for="name" class="form-label">Nome *</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="col-md-12">
<label for="description" class="form-label">Descrizione</label>
<input type="text" class="form-control" id="description" name="description" maxlength="150">
</div>
<div class="col-md-6">
<label for="engine" class="form-label">Tipo routine *</label>
<select class="form-select" id="engine" name="engine" required>
<option value="1">Motore (step configurabili)</option>
<option value="0">File (legacy)</option>
</select>
<div class="small-muted mt-1">
Motore: configurabile a step da questa interfaccia. File: logica in un file PHP dedicato.
</div>
</div>
<div class="col-md-6">
<label for="headerrow" class="form-label">Riga intestazione (header row) *</label>
<input type="number" class="form-control" id="headerrow" name="headerrow" min="1" value="1" required>
</div>
<div class="col-md-12" id="filenameWrapper">
<label for="filename" class="form-label">Nome file</label>
<input type="text" class="form-control" id="filename" name="filename" placeholder="es. fendi.php">
<div class="small-muted mt-1" id="filenameHelp">
Per le routine di tipo File indica il nome del file PHP presente nella cartella routines/.
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Annulla</button>
<button type="submit" class="btn btn-primary" id="saveRoutineButton">Salva Routine</button>
</div>
</form>
</div>
</div>
<!-- Delete Form -->
<form method="POST" id="deleteRoutineForm" style="display:none;">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="idroutine" id="deleteIdRoutine" value="">
</form>
<?php include('jsinclude.php'); ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const routineModal = new bootstrap.Modal(document.getElementById('routineModal'));
const formAction = document.getElementById('formAction');
const idroutine = document.getElementById('idroutine');
const routineModalTitle = document.getElementById('routineModalTitle');
const saveRoutineButton = document.getElementById('saveRoutineButton');
const name = document.getElementById('name');
const description = document.getElementById('description');
const engine = document.getElementById('engine');
const headerrow = document.getElementById('headerrow');
const filename = document.getElementById('filename');
const filenameWrapper = document.getElementById('filenameWrapper');
const filenameHelp = document.getElementById('filenameHelp');
const engineFilename = "<?= htmlspecialchars($engineFilename, ENT_QUOTES, 'UTF-8'); ?>";
/*
* Mostra/nasconde il campo filename in base al tipo di routine.
* Motore -> filename forzato al file del motore e campo di sola lettura.
*/
function applyEngineUi() {
if (engine.value === '1') {
filename.value = engineFilename;
filename.readOnly = true;
filenameHelp.textContent = 'Le routine di tipo Motore usano automaticamente il file ' + engineFilename + '.';
} else {
if (filename.value === engineFilename) {
filename.value = '';
}
filename.readOnly = false;
filenameHelp.textContent = 'Per le routine di tipo File indica il nome del file PHP presente nella cartella routines/.';
}
}
engine.addEventListener('change', applyEngineUi);
function resetModalForCreate() {
formAction.value = 'create';
idroutine.value = '';
routineModalTitle.textContent = 'Nuova Routine';
saveRoutineButton.textContent = 'Crea Routine';
name.value = '';
description.value = '';
engine.value = '1';
headerrow.value = '1';
filename.value = '';
applyEngineUi();
}
function setModalForEdit(button) {
formAction.value = 'update';
idroutine.value = button.dataset.id || '';
routineModalTitle.textContent = 'Modifica Routine';
saveRoutineButton.textContent = 'Aggiorna Routine';
name.value = button.dataset.name || '';
description.value = button.dataset.description || '';
engine.value = button.dataset.engine === '1' ? '1' : '0';
headerrow.value = button.dataset.headerrow || '1';
filename.value = button.dataset.filename || '';
applyEngineUi();
}
document.getElementById('btnAddRoutine').addEventListener('click', function() {
resetModalForCreate();
routineModal.show();
});
document.querySelectorAll('.btnEditRoutine').forEach(function(button) {
button.addEventListener('click', function() {
setModalForEdit(button);
routineModal.show();
});
});
document.querySelectorAll('.btnDeleteRoutine').forEach(function(button) {
button.addEventListener('click', function() {
const id = button.dataset.id;
const nm = button.dataset.name || 'questa routine';
Swal.fire({
title: 'Eliminare la routine?',
text: 'Vuoi eliminare "' + nm + '"? Verranno rimossi anche gli step collegati. Operazione irreversibile.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Sì, elimina',
cancelButtonText: 'Annulla',
confirmButtonColor: '#d33'
}).then(function(result) {
if (result.isConfirmed) {
document.getElementById('deleteIdRoutine').value = id;
document.getElementById('deleteRoutineForm').submit();
}
});
});
});
if (typeof $ !== 'undefined' && $.fn.DataTable) {
$('#routinesTable').DataTable({
pageLength: 25,
order: [
[0, 'asc']
]
});
}
<?php if ($flash): ?>
Swal.fire({
icon: '<?= htmlspecialchars($flash['type'], ENT_QUOTES, 'UTF-8'); ?>',
title: '<?= $flash['type'] === 'success' ? 'Fatto' : ($flash['type'] === 'warning' ? 'Attenzione' : 'Errore'); ?>',
text: '<?= htmlspecialchars($flash['message'], ENT_QUOTES, 'UTF-8'); ?>'
});
<?php endif; ?>
});
</script>
</body>
</html>