297 lines
11 KiB
PHP
297 lines
11 KiB
PHP
<?php
|
||
|
||
/**
|
||
* Routine motore generico (_engine.php)
|
||
* -------------------------------------
|
||
* File UNICO condiviso da tutte le routine con engine = 1.
|
||
*
|
||
* Espone la stessa firma delle routine legacy:
|
||
*
|
||
* applyRoutine(&$excelData, $routineData)
|
||
*
|
||
* così apply_routine.php lo include allo stesso modo. La differenza è che qui
|
||
* NON c'è logica hardcoded: gli step vengono letti dalla tabella routine_step
|
||
* (in base a routineData['idroutine']) ed eseguiti in ordine.
|
||
*
|
||
* Ogni step ha una `operation` (verbo del vocabolario) e `params` JSON.
|
||
* Le colonne nei params possono essere indici zero-based (3) o lettere Excel ("D").
|
||
*/
|
||
|
||
if (!function_exists('applyRoutine')) {
|
||
|
||
/**
|
||
* Converte un riferimento colonna in indice zero-based.
|
||
* Accetta int/indici numerici (3) o lettere Excel ("D", "AA", ...).
|
||
*/
|
||
function routineEngineColToIndex($ref): int
|
||
{
|
||
if (is_int($ref)) {
|
||
return $ref;
|
||
}
|
||
$ref = trim((string)$ref);
|
||
if ($ref === '') {
|
||
return 0;
|
||
}
|
||
// Numerico puro -> indice diretto
|
||
if (ctype_digit($ref)) {
|
||
return (int)$ref;
|
||
}
|
||
// Lettere Excel -> indice zero-based (A=0, B=1, ..., Z=25, AA=26, ...)
|
||
$ref = strtoupper($ref);
|
||
$index = 0;
|
||
$len = strlen($ref);
|
||
for ($i = 0; $i < $len; $i++) {
|
||
$c = $ref[$i];
|
||
if ($c < 'A' || $c > 'Z') {
|
||
continue;
|
||
}
|
||
$index = $index * 26 + (ord($c) - ord('A') + 1);
|
||
}
|
||
return $index - 1;
|
||
}
|
||
|
||
/** Normalizza una lista di riferimenti colonna in indici zero-based. */
|
||
function routineEngineColsToIndexes(array $refs): array
|
||
{
|
||
return array_map('routineEngineColToIndex', $refs);
|
||
}
|
||
|
||
/**
|
||
* Carica gli step attivi di una routine dal DB, ordinati per step_order.
|
||
* Ritorna array di ['operation' => string, 'params' => array].
|
||
*/
|
||
function routineEngineLoadSteps(int $idroutine): array
|
||
{
|
||
// Riusa la connessione PDO del progetto.
|
||
// db-functions.php sta in .../class/ rispetto alla cartella routines/.
|
||
// Aggiusta il require se nel tuo albero il path differisce.
|
||
$dbFile = dirname(__DIR__) . '/class/db-functions.php';
|
||
if (!class_exists('DBHandlerSelect') && file_exists($dbFile)) {
|
||
require_once $dbFile;
|
||
}
|
||
|
||
if (!class_exists('DBHandlerSelect')) {
|
||
throw new Exception("Motore routine: classe DBHandlerSelect non trovata (path db-functions.php da verificare).");
|
||
}
|
||
|
||
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||
|
||
$stmt = $pdo->prepare(
|
||
"SELECT operation, params
|
||
FROM routine_step
|
||
WHERE idroutine = :id AND active = 1
|
||
ORDER BY step_order ASC, idstep ASC"
|
||
);
|
||
$stmt->execute([':id' => $idroutine]);
|
||
$rows = $stmt->fetchAll();
|
||
|
||
$steps = [];
|
||
foreach ($rows as $r) {
|
||
$params = [];
|
||
if (!empty($r['params'])) {
|
||
$decoded = json_decode($r['params'], true);
|
||
if (is_array($decoded)) {
|
||
$params = $decoded;
|
||
} else {
|
||
error_log("Motore routine: params JSON non valido per operazione {$r['operation']} (routine {$idroutine}).");
|
||
}
|
||
}
|
||
$steps[] = ['operation' => strtolower(trim($r['operation'])), 'params' => $params];
|
||
}
|
||
return $steps;
|
||
}
|
||
|
||
/* ------------------------------------------------------------------ *
|
||
* HANDLERS – ogni operazione modifica $excelData in place.
|
||
* Aggiungere una capacità nuova = aggiungere un handler qui, una volta.
|
||
* ------------------------------------------------------------------ */
|
||
|
||
/**
|
||
* merge: unisce N colonne sorgente in una destinazione.
|
||
* params: source_columns [], target_column, separator=" ", skip_empty=true
|
||
*/
|
||
function routineOpMerge(array &$excelData, array $params): void
|
||
{
|
||
$sources = routineEngineColsToIndexes($params['source_columns'] ?? []);
|
||
$target = routineEngineColToIndex($params['target_column'] ?? 0);
|
||
$sep = array_key_exists('separator', $params) ? (string)$params['separator'] : ' ';
|
||
$skip = $params['skip_empty'] ?? true;
|
||
|
||
foreach ($excelData as $rowIndex => &$row) {
|
||
if (!isset($row['data']) || !is_array($row['data'])) {
|
||
error_log("merge: struttura riga non valida all'indice {$rowIndex}.");
|
||
continue;
|
||
}
|
||
$parts = [];
|
||
foreach ($sources as $col) {
|
||
$val = trim((string)($row['data'][$col] ?? ''));
|
||
if ($skip && $val === '') {
|
||
continue;
|
||
}
|
||
$parts[] = $val;
|
||
}
|
||
$row['data'][$target] = implode($sep, $parts);
|
||
}
|
||
unset($row);
|
||
}
|
||
|
||
/**
|
||
* copy: copia una colonna sorgente in una destinazione.
|
||
* params: source_column, target_column
|
||
*/
|
||
function routineOpCopy(array &$excelData, array $params): void
|
||
{
|
||
$src = routineEngineColToIndex($params['source_column'] ?? 0);
|
||
$dst = routineEngineColToIndex($params['target_column'] ?? 0);
|
||
foreach ($excelData as &$row) {
|
||
if (!isset($row['data']) || !is_array($row['data'])) {
|
||
continue;
|
||
}
|
||
$row['data'][$dst] = $row['data'][$src] ?? '';
|
||
}
|
||
unset($row);
|
||
}
|
||
|
||
/**
|
||
* clear: svuota una o più colonne.
|
||
* params: columns [] (oppure column singola)
|
||
*/
|
||
function routineOpClear(array &$excelData, array $params): void
|
||
{
|
||
$cols = $params['columns'] ?? (isset($params['column']) ? [$params['column']] : []);
|
||
$cols = routineEngineColsToIndexes($cols);
|
||
foreach ($excelData as &$row) {
|
||
if (!isset($row['data']) || !is_array($row['data'])) {
|
||
continue;
|
||
}
|
||
foreach ($cols as $c) {
|
||
$row['data'][$c] = '';
|
||
}
|
||
}
|
||
unset($row);
|
||
}
|
||
|
||
/**
|
||
* trim: rimuove spazi (o caratteri indicati) da una o più colonne.
|
||
* params: columns [] (o column), chars (opzionale)
|
||
*/
|
||
function routineOpTrim(array &$excelData, array $params): void
|
||
{
|
||
$cols = $params['columns'] ?? (isset($params['column']) ? [$params['column']] : []);
|
||
$cols = routineEngineColsToIndexes($cols);
|
||
$chars = $params['chars'] ?? null;
|
||
foreach ($excelData as &$row) {
|
||
if (!isset($row['data']) || !is_array($row['data'])) {
|
||
continue;
|
||
}
|
||
foreach ($cols as $c) {
|
||
$v = (string)($row['data'][$c] ?? '');
|
||
$row['data'][$c] = $chars === null ? trim($v) : trim($v, $chars);
|
||
}
|
||
}
|
||
unset($row);
|
||
}
|
||
|
||
/**
|
||
* replace: sostituzione testo in una o più colonne.
|
||
* params: columns [] (o column), search, replace="", regex=false
|
||
*/
|
||
function routineOpReplace(array &$excelData, array $params): void
|
||
{
|
||
$cols = $params['columns'] ?? (isset($params['column']) ? [$params['column']] : []);
|
||
$cols = routineEngineColsToIndexes($cols);
|
||
$search = $params['search'] ?? '';
|
||
$replace = $params['replace'] ?? '';
|
||
$regex = $params['regex'] ?? false;
|
||
|
||
foreach ($excelData as &$row) {
|
||
if (!isset($row['data']) || !is_array($row['data'])) {
|
||
continue;
|
||
}
|
||
foreach ($cols as $c) {
|
||
$v = (string)($row['data'][$c] ?? '');
|
||
if ($regex) {
|
||
$out = @preg_replace($search, $replace, $v);
|
||
$row['data'][$c] = $out === null ? $v : $out;
|
||
} else {
|
||
$row['data'][$c] = str_replace($search, $replace, $v);
|
||
}
|
||
}
|
||
}
|
||
unset($row);
|
||
}
|
||
|
||
/** uppercase: maiuscolo su una o più colonne. params: columns [] (o column) */
|
||
function routineOpUppercase(array &$excelData, array $params): void
|
||
{
|
||
$cols = $params['columns'] ?? (isset($params['column']) ? [$params['column']] : []);
|
||
$cols = routineEngineColsToIndexes($cols);
|
||
foreach ($excelData as &$row) {
|
||
if (!isset($row['data']) || !is_array($row['data'])) {
|
||
continue;
|
||
}
|
||
foreach ($cols as $c) {
|
||
$row['data'][$c] = mb_strtoupper((string)($row['data'][$c] ?? ''), 'UTF-8');
|
||
}
|
||
}
|
||
unset($row);
|
||
}
|
||
|
||
/** lowercase: minuscolo su una o più colonne. params: columns [] (o column) */
|
||
function routineOpLowercase(array &$excelData, array $params): void
|
||
{
|
||
$cols = $params['columns'] ?? (isset($params['column']) ? [$params['column']] : []);
|
||
$cols = routineEngineColsToIndexes($cols);
|
||
foreach ($excelData as &$row) {
|
||
if (!isset($row['data']) || !is_array($row['data'])) {
|
||
continue;
|
||
}
|
||
foreach ($cols as $c) {
|
||
$row['data'][$c] = mb_strtolower((string)($row['data'][$c] ?? ''), 'UTF-8');
|
||
}
|
||
}
|
||
unset($row);
|
||
}
|
||
|
||
/**
|
||
* Entry point richiamato da apply_routine.php.
|
||
* Stessa firma delle routine legacy.
|
||
*/
|
||
function applyRoutine(&$excelData, $routineData = [])
|
||
{
|
||
$idroutine = isset($routineData['idroutine']) ? (int)$routineData['idroutine'] : 0;
|
||
if ($idroutine <= 0) {
|
||
throw new Exception("Motore routine: idroutine mancante in routineData.");
|
||
}
|
||
|
||
$steps = routineEngineLoadSteps($idroutine);
|
||
if (empty($steps)) {
|
||
error_log("Motore routine: nessuno step attivo per routine {$idroutine}.");
|
||
return;
|
||
}
|
||
|
||
// Dispatch table verbo -> handler
|
||
$handlers = [
|
||
'merge' => 'routineOpMerge',
|
||
'copy' => 'routineOpCopy',
|
||
'clear' => 'routineOpClear',
|
||
'trim' => 'routineOpTrim',
|
||
'replace' => 'routineOpReplace',
|
||
'uppercase' => 'routineOpUppercase',
|
||
'lowercase' => 'routineOpLowercase',
|
||
];
|
||
|
||
foreach ($steps as $i => $step) {
|
||
$op = $step['operation'];
|
||
if (!isset($handlers[$op])) {
|
||
error_log("Motore routine: operazione sconosciuta '{$op}' (routine {$idroutine}, step " . ($i + 1) . ") — saltata.");
|
||
continue;
|
||
}
|
||
$handlers[$op]($excelData, $step['params']);
|
||
error_log("Motore routine: applicata operazione '{$op}' (routine {$idroutine}, step " . ($i + 1) . ").");
|
||
}
|
||
|
||
error_log("Motore routine {$idroutine} completato (" . count($steps) . " step).");
|
||
}
|
||
}
|