hasRole('Admin') && !$user->hasRole('SuperUser')) { header('Location: import_dashboard.php'); exit; } $db = DBHandlerSelect::getInstance(); $pdo = $db->getConnection(); /* * CSRF token. */ if (empty($_SESSION['routine_steps_csrf'])) { $_SESSION['routine_steps_csrf'] = bin2hex(random_bytes(32)); } $csrfToken = $_SESSION['routine_steps_csrf']; function cleanInput($value) { return trim((string)($value ?? '')); } /* * Determina l'id routine dalla querystring (GET) o dal POST. */ $idroutine = 0; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $idroutine = (int)($_POST['idroutine'] ?? 0); } else { $idroutine = (int)($_GET['idroutine'] ?? 0); } function redirectToSteps($idroutine, $type, $message) { $_SESSION['routine_steps_flash'] = [ 'type' => $type, 'message' => $message ]; header('Location: routine_steps.php?idroutine=' . (int)$idroutine); exit; } /* * Converte lettera colonna Excel in indice zero-based. Accetta anche numeri. * A=0, B=1, ... Z=25, AA=26, ... */ function colToIndex($ref) { $ref = strtoupper(trim((string)$ref)); if ($ref === '') { return null; } if (ctype_digit($ref)) { return (int)$ref; } $index = 0; $len = strlen($ref); for ($i = 0; $i < $len; $i++) { $c = $ref[$i]; if ($c < 'A' || $c > 'Z') { return null; } $index = $index * 26 + (ord($c) - ord('A') + 1); } return $index - 1; } /* * Converte indice zero-based in lettera Excel (per mostrare gli step salvati). */ function indexToCol($index) { $index = (int)$index; if ($index < 0) { return ''; } $letters = ''; $index += 1; while ($index > 0) { $rem = ($index - 1) % 26; $letters = chr(ord('A') + $rem) . $letters; $index = intdiv($index - 1, 26); } return $letters; } /* * Normalizza una stringa di colonne separate da virgola ("D, F, H") in * un array di indici zero-based, validando ogni riferimento. */ function parseColumnList($raw) { $raw = trim((string)$raw); if ($raw === '') { return []; } $parts = preg_split('/[\s,;]+/', $raw); $out = []; foreach ($parts as $p) { if ($p === '') { continue; } $idx = colToIndex($p); if ($idx === null) { throw new Exception('Riferimento colonna non valido: "' . $p . '". Usa lettere come D, F, H.'); } $out[] = $idx; } return $out; } /* * Verifica esistenza routine ed engine = 1. */ $routine = null; if ($idroutine > 0) { $stmt = $pdo->prepare("SELECT idroutine, name, description, engine, headerrow FROM routine WHERE idroutine = :id LIMIT 1"); $stmt->execute([':id' => $idroutine]); $routine = $stmt->fetch(PDO::FETCH_ASSOC); } if (!$routine) { $_SESSION['routine_admin_flash'] = ['type' => 'error', 'message' => 'Routine non trovata.']; header('Location: routine_admin.php'); exit; } if ((int)$routine['engine'] !== 1) { $_SESSION['routine_admin_flash'] = ['type' => 'error', 'message' => 'Questa routine è di tipo File: gli step non sono configurabili da qui.']; header('Location: routine_admin.php'); exit; } /* * Vocabolario operazioni disponibili (deve combaciare con gli handler di _engine.php). */ $operations = ['merge', 'copy', 'clear', 'trim', 'replace', 'uppercase', 'lowercase']; /* * 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') { $operation = strtolower(cleanInput($_POST['operation'] ?? '')); $active = (int)($_POST['active'] ?? 1) === 1 ? 1 : 0; if (!in_array($operation, $operations, true)) { throw new Exception('Operazione non valida.'); } /* * Costruzione params in base all'operazione. * Le colonne arrivano come lettere e vengono convertite in indici. */ $params = []; if ($operation === 'merge') { $sources = parseColumnList($_POST['source_columns'] ?? ''); $target = colToIndex($_POST['target_column'] ?? ''); if (empty($sources)) { throw new Exception('Indica almeno una colonna sorgente (es. D, F, H).'); } if ($target === null) { throw new Exception('Indica la colonna di destinazione (es. J).'); } $params = [ 'source_columns' => $sources, 'target_column' => $target, 'separator' => (string)($_POST['separator'] ?? ' '), 'skip_empty' => isset($_POST['skip_empty']) ? true : false, ]; } elseif ($operation === 'copy') { $src = colToIndex($_POST['source_column'] ?? ''); $dst = colToIndex($_POST['target_column'] ?? ''); if ($src === null || $dst === null) { throw new Exception('Indica colonna sorgente e destinazione (es. D e J).'); } $params = ['source_column' => $src, 'target_column' => $dst]; } elseif ($operation === 'clear' || $operation === 'trim' || $operation === 'uppercase' || $operation === 'lowercase') { $cols = parseColumnList($_POST['columns'] ?? ''); if (empty($cols)) { throw new Exception('Indica almeno una colonna (es. D, F).'); } $params = ['columns' => $cols]; if ($operation === 'trim') { $chars = (string)($_POST['chars'] ?? ''); if ($chars !== '') { $params['chars'] = $chars; } } } elseif ($operation === 'replace') { $cols = parseColumnList($_POST['columns'] ?? ''); if (empty($cols)) { throw new Exception('Indica almeno una colonna (es. D, F).'); } $params = [ 'columns' => $cols, 'search' => (string)($_POST['search'] ?? ''), 'replace' => (string)($_POST['replace'] ?? ''), 'regex' => isset($_POST['regex']) ? true : false, ]; } $paramsJson = json_encode($params, JSON_UNESCAPED_UNICODE); if ($action === 'create') { // step_order = max + 1 $stmt = $pdo->prepare("SELECT COALESCE(MAX(step_order), 0) + 1 AS next_order FROM routine_step WHERE idroutine = :id"); $stmt->execute([':id' => $idroutine]); $nextOrder = (int)$stmt->fetchColumn(); $stmt = $pdo->prepare(" INSERT INTO routine_step (idroutine, step_order, operation, params, active) VALUES (:id, :ord, :op, :params, :active) "); $stmt->execute([ ':id' => $idroutine, ':ord' => $nextOrder, ':op' => $operation, ':params' => $paramsJson, ':active' => $active, ]); redirectToSteps($idroutine, 'success', 'Step aggiunto correttamente.'); } else { $idstep = (int)($_POST['idstep'] ?? 0); if ($idstep <= 0) { throw new Exception('ID step non valido.'); } $stmt = $pdo->prepare(" UPDATE routine_step SET operation = :op, params = :params, active = :active WHERE idstep = :idstep AND idroutine = :id "); $stmt->execute([ ':op' => $operation, ':params' => $paramsJson, ':active' => $active, ':idstep' => $idstep, ':id' => $idroutine, ]); redirectToSteps($idroutine, 'success', 'Step aggiornato correttamente.'); } } if ($action === 'delete') { $idstep = (int)($_POST['idstep'] ?? 0); if ($idstep <= 0) { throw new Exception('ID step non valido.'); } $stmt = $pdo->prepare("DELETE FROM routine_step WHERE idstep = :idstep AND idroutine = :id"); $stmt->execute([':idstep' => $idstep, ':id' => $idroutine]); redirectToSteps($idroutine, 'success', 'Step eliminato.'); } if ($action === 'move') { $idstep = (int)($_POST['idstep'] ?? 0); $dir = cleanInput($_POST['direction'] ?? ''); if ($idstep <= 0 || !in_array($dir, ['up', 'down'], true)) { throw new Exception('Parametri di spostamento non validi.'); } // Carica step correnti ordinati. $stmt = $pdo->prepare("SELECT idstep, step_order FROM routine_step WHERE idroutine = :id ORDER BY step_order ASC, idstep ASC"); $stmt->execute([':id' => $idroutine]); $all = $stmt->fetchAll(PDO::FETCH_ASSOC); // Trova posizione corrente. $pos = null; foreach ($all as $i => $s) { if ((int)$s['idstep'] === $idstep) { $pos = $i; break; } } if ($pos !== null) { $swapWith = $dir === 'up' ? $pos - 1 : $pos + 1; if ($swapWith >= 0 && $swapWith < count($all)) { $a = $all[$pos]; $b = $all[$swapWith]; // Scambia gli step_order dei due record. $upd = $pdo->prepare("UPDATE routine_step SET step_order = :ord WHERE idstep = :idstep AND idroutine = :id"); $upd->execute([':ord' => (int)$b['step_order'], ':idstep' => (int)$a['idstep'], ':id' => $idroutine]); $upd->execute([':ord' => (int)$a['step_order'], ':idstep' => (int)$b['idstep'], ':id' => $idroutine]); } } redirectToSteps($idroutine, 'success', 'Ordine aggiornato.'); } throw new Exception('Azione non valida.'); } catch (Exception $e) { redirectToSteps($idroutine, 'error', $e->getMessage()); } } /* * Caricamento step della routine. */ $stmt = $pdo->prepare("SELECT idstep, step_order, operation, params, active FROM routine_step WHERE idroutine = :id ORDER BY step_order ASC, idstep ASC"); $stmt->execute([':id' => $idroutine]); $steps = $stmt->fetchAll(PDO::FETCH_ASSOC); /* * Costruisce una descrizione leggibile dei params per la tabella. */ function describeStep($operation, array $p) { $colsToLetters = function ($arr) { return implode(', ', array_map('indexToCol', $arr)); }; switch ($operation) { case 'merge': return 'Unisce ' . $colsToLetters($p['source_columns'] ?? []) . ' → ' . indexToCol($p['target_column'] ?? 0) . ' (sep: "' . ($p['separator'] ?? ' ') . '")'; case 'copy': return 'Copia ' . indexToCol($p['source_column'] ?? 0) . ' → ' . indexToCol($p['target_column'] ?? 0); case 'clear': return 'Svuota ' . $colsToLetters($p['columns'] ?? []); case 'trim': return 'Trim ' . $colsToLetters($p['columns'] ?? []) . (isset($p['chars']) ? ' (chars: "' . $p['chars'] . '")' : ''); case 'uppercase': return 'Maiuscolo ' . $colsToLetters($p['columns'] ?? []); case 'lowercase': return 'Minuscolo ' . $colsToLetters($p['columns'] ?? []); case 'replace': return 'Sostituisci in ' . $colsToLetters($p['columns'] ?? []) . ': "' . ($p['search'] ?? '') . '" → "' . ($p['replace'] ?? '') . '"' . (!empty($p['regex']) ? ' (regex)' : ''); default: return $operation; } } /* * Prepara i dati step per il JS (params già in lettere per il form di modifica). */ $stepsForJs = []; foreach ($steps as $s) { $p = json_decode($s['params'] ?? '[]', true); if (!is_array($p)) { $p = []; } // Converte indici -> lettere per il form. $letters = function ($arr) { return implode(', ', array_map('indexToCol', $arr)); }; $formData = ['operation' => $s['operation'], 'active' => (int)$s['active']]; if ($s['operation'] === 'merge') { $formData['source_columns'] = $letters($p['source_columns'] ?? []); $formData['target_column'] = indexToCol($p['target_column'] ?? 0); $formData['separator'] = $p['separator'] ?? ' '; $formData['skip_empty'] = !empty($p['skip_empty']); } elseif ($s['operation'] === 'copy') { $formData['source_column'] = indexToCol($p['source_column'] ?? 0); $formData['target_column'] = indexToCol($p['target_column'] ?? 0); } elseif (in_array($s['operation'], ['clear', 'trim', 'uppercase', 'lowercase'], true)) { $formData['columns'] = $letters($p['columns'] ?? []); if ($s['operation'] === 'trim') { $formData['chars'] = $p['chars'] ?? ''; } } elseif ($s['operation'] === 'replace') { $formData['columns'] = $letters($p['columns'] ?? []); $formData['search'] = $p['search'] ?? ''; $formData['replace'] = $p['replace'] ?? ''; $formData['regex'] = !empty($p['regex']); } $stepsForJs[(int)$s['idstep']] = $formData; } $flash = $_SESSION['routine_steps_flash'] ?? null; unset($_SESSION['routine_steps_flash']); ?> Step Routine - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?>
Step routine:
Configura le operazioni applicate all'XLS. Vengono eseguite dall'alto verso il basso.
Torna alle routine
Le colonne si indicano con le lettere dell'Excel (A, B, C, …). Per più colonne separale con la virgola: D, F, H.
$s): ?>
Ordine Operazione Dettaglio Stato Azioni
Nessuno step configurato. Usa "Aggiungi step" per iniziare.
Attivo Disattivo

Prova la routine
Carica un XLS di esempio: viene mostrata la prima riga dati dopo l'intestazione, elaborata con gli step qui sopra. Il file non viene salvato.