#!/usr/bin/env bash # STEP044 - Porting Presenze 215.6 # # Diagnostica reale del salvataggio calendario Silvelox. # NON modifica database o logica SQL. # # Al prossimo salvataggio mostra: # - errore reale restituito dal backend/MariaDB; # - valori vuoti presenti nel payload; # - log server tramite error_log(). # # Server atteso: 215.6 / ax-accessi set -Eeuo pipefail umask 077 STEP_NAME="STEP044_2156_SILVELOX_CALENDAR_REAL_ERROR_DIAGNOSTIC_PATCH_VERIFIED_20260731" EXPECTED_HOST="${EXPECTED_HOST:-ax-accessi}" EXPECTED_IP_FRAGMENT="${EXPECTED_IP_FRAGMENT:-192.168.215.6}" TARGET_ROOT="${TARGET_ROOT:-/var/www/html/presenze.silvelox}" CAL_ROOT="${CAL_ROOT:-$TARGET_ROOT/components/templates/custom_templates/ax_attendance/ax_calendar}" CONTROLLER="${CONTROLLER:-$CAL_ROOT/ctrl/controllerCtrl.php}" APP_JS="${APP_JS:-$CAL_ROOT/app/appCtrl.js}" INDEX_HTML="${INDEX_HTML:-$CAL_ROOT/index.html}" GRID_HTML="${GRID_HTML:-$CAL_ROOT/grid_table.tpl.html}" TARGET_HOST="${TARGET_HOST:-presenze.silvelox.axuno.it}" ALLOW_TEST="${ALLOW_TEST:-0}" SKIP_RELOAD="${SKIP_RELOAD:-0}" SKIP_HTTP="${SKIP_HTTP:-0}" FAIL_AFTER_PATCH="${FAIL_AFTER_PATCH:-0}" STAMP="$(date +%Y%m%d_%H%M%S_%N)_$$" OUTPUT_ZIP="$PWD/${STEP_NAME}_${STAMP}.zip" WORKDIR="$(mktemp -d "$PWD/.${STEP_NAME}.XXXXXX")" REPORT="$WORKDIR/${STEP_NAME}_REPORT.txt" PATCHER="$WORKDIR/patch.php" CHANGED=0 declare -a FILES=( "$CONTROLLER" "$APP_JS" "$INDEX_HTML" "$GRID_HTML" ) cleanup() { rm -rf -- "$WORKDIR" } reload_runtime() { if [[ "$SKIP_RELOAD" == "1" ]]; then return 0 fi if command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files 2>/dev/null | grep -q '^php5\.6-fpm\.service'; then systemctl reload php5.6-fpm elif command -v service >/dev/null 2>&1; then service php5.6-fpm reload fi apache2ctl configtest apache2ctl graceful } restore_files() { local file base [[ "$CHANGED" == "1" ]] || return 0 echo "ROLLBACK: ripristino dei quattro file originali." >&2 for file in "${FILES[@]}"; do case "$file" in "$CONTROLLER") base="controllerCtrl.php" ;; "$APP_JS") base="appCtrl.js" ;; "$INDEX_HTML") base="index.html" ;; "$GRID_HTML") base="grid_table.tpl.html" ;; *) continue ;; esac if [[ -f "$WORKDIR/before/$base" ]]; then cp -a -- "$WORKDIR/before/$base" "$file" fi done reload_runtime >/dev/null 2>&1 || true CHANGED=0 } on_error() { local rc=$? local line="${1:-?}" trap - ERR restore_files printf 'ERRORE linea %s, codice %s\n' "$line" "$rc" >&2 exit "$rc" } on_interrupt() { trap - ERR restore_files exit 130 } trap cleanup EXIT trap 'on_error "$LINENO"' ERR trap on_interrupt INT TERM section() { printf '\n\n================================================================\n%s\n================================================================\n' "$1" >> "$REPORT" } HOST_NOW="$(hostname 2>/dev/null || true)" IPS_NOW="$(hostname -I 2>/dev/null || true)" { echo "$STEP_NAME" echo "DATA: $(date --iso-8601=seconds 2>/dev/null || date)" echo "SERVER: $HOST_NOW" echo "IP: $IPS_NOW" echo "PORTALE: $TARGET_ROOT" echo "MODIFICA DATABASE: NO" echo "MODIFICA QUERY SQL: NO" echo "SCOPO: mostrare l'errore reale al prossimo salvataggio" } > "$REPORT" if [[ "$ALLOW_TEST" != "1" ]]; then if [[ "$HOST_NOW" != "$EXPECTED_HOST" && "$IPS_NOW" != *"$EXPECTED_IP_FRAGMENT"* ]]; then echo "ERRORE: server errato; atteso 215.6 / ax-accessi." >&2 exit 10 fi fi for file in "${FILES[@]}"; do [[ -f "$file" ]] || { echo "ERRORE: file non trovato: $file" >&2 exit 11 } done PHP_BIN="$(command -v php5.6 || command -v php || true)" [[ -n "$PHP_BIN" ]] || { echo "ERRORE: PHP CLI non disponibile." >&2 exit 12 } NODE_BIN="$(command -v node || command -v nodejs || true)" mkdir -p "$WORKDIR/before" "$WORKDIR/after" cp -a -- "$CONTROLLER" "$WORKDIR/before/controllerCtrl.php" cp -a -- "$APP_JS" "$WORKDIR/before/appCtrl.js" cp -a -- "$INDEX_HTML" "$WORKDIR/before/index.html" cp -a -- "$GRID_HTML" "$WORKDIR/before/grid_table.tpl.html" section "Pre-check" { "$PHP_BIN" -l "$CONTROLLER" if [[ -n "$NODE_BIN" ]]; then "$NODE_BIN" --check "$APP_JS" else echo "Node non presente sul server; JS gia' verificato sul sorgente reale STEP042B." fi for file in "${FILES[@]}"; do stat -c '%A %a %U:%G %s %y %n' "$file" sha256sum "$file" done } >> "$REPORT" 2>&1 cat > "$PATCHER" <<'PHP' getTraceAsString\(\);\s*)?' . '\$msg = "error (?:INSERT|UPDATE) >> " \. \$sql;\s*' . '\$result = \'0\';\s*\}~'; $fieldCode = "array("; foreach ($fields as $field) { $fieldCode .= "'" . addslashes($field) . "',"; } $fieldCode = rtrim($fieldCode, ",") . ")"; $replacement = "} catch (Exception \$exc) {\n" . " \$msg = \$this->diagnosticaErroreCalendario(\n" . " '" . addslashes($operationLabel) . "',\n" . " \$dati,\n" . " \$exc,\n" . " " . $fieldCode . "\n" . " );\n" . " \$result = '0';\n" . " }"; $replacement = convertEol($replacement, detectEol($section)); $patchedSection = preg_replace( $pattern, $replacement, $section, 1, $count ); if ($patchedSection === null || $count !== 1) { throw new Exception( "Catch non trovato o ambiguo nella funzione " . $functionName ); } $data = substr($data, 0, $start) . $patchedSection . substr($data, $end); return array($data, true); } $controller = readFileStrict($controllerFile); $controllerEol = detectEol($controller); $helperMarker = "private function diagnosticaErroreCalendario("; $changedController = false; if (strpos($controller, $helperMarker) === false) { $propertyNeedle = " public \$conn;"; $propertyPosition = strpos($controller, $propertyNeedle); if ($propertyPosition === false) { throw new Exception("Punto di inserimento helper non trovato"); } $insertPosition = $propertyPosition + strlen($propertyNeedle); $helper = <<<'PHPHELPER' private function diagnosticaErroreCalendario( $operazione, $dati, $exc, $campi ) { $vuoti = array(); foreach ($campi as $campo) { if ( !isset($dati->$campo) || $dati->$campo === '' || $dati->$campo === null ) { $vuoti[] = $campo; } } $messaggio = trim($exc->getMessage()); if ($messaggio === '') { $messaggio = get_class($exc); } if (count($vuoti) > 0) { $messaggio .= ' | valori vuoti: ' . implode(', ', $vuoti); } error_log( 'SILVELOX_CALENDAR ' . $operazione . ' | ' . $messaggio . ' | payload=' . json_encode($dati) ); return $messaggio; } PHPHELPER; $helper = convertEol($helper, $controllerEol); $controller = substr($controller, 0, $insertPosition) . $helper . substr($controller, $insertPosition); $changedController = true; } list($controller, $changed) = patchFunctionCatch( $controller, "inserisciEventoUtente", "inserisciEvento", "inserisciEventoUtente", array( "inizio", "fine", "causale_id", "causale_type", "causale_lav_id", "causale_lav_type", "titolo", "descrizione", "note", "cal_color", "interessato_id" ) ); $changedController = $changedController || $changed; list($controller, $changed) = patchFunctionCatch( $controller, "inserisciEvento", "modificaEvento", "inserisciEvento", array( "inizio", "fine", "causale_id", "causale_type", "causale_lav_id", "causale_lav_type", "titolo", "descrizione", "note", "cal_color", "interessato_id" ) ); $changedController = $changedController || $changed; list($controller, $changed) = patchFunctionCatch( $controller, "modificaEvento", "eliminaEvento", "modificaEvento", array( "id", "start", "end", "causale_id", "causale_type", "causale_lav_id", "causale_lav_type", "title", "description", "backgroundColor", "note", "cal_stato", "interessato_id" ) ); $changedController = $changedController || $changed; if ($changedController) { writeFileStrict($controllerFile, $controller); } $app = readFileStrict($appFile); $appEol = detectEol($app); $changedApp = false; list($app, $changed) = replaceOnceLiteral( $app, " }, 2000);", " }, tipo == 'danger' ? 15000 : 2000);", "durata alert" ); $changedApp = $changedApp || $changed; if (strpos($app, "\$scope.MostraErroreCalendario") === false) { $helperNeedle = " //INIT"; $helperPosition = strpos($app, $helperNeedle); if ($helperPosition === false) { throw new Exception("Punto helper JS non trovato"); } $helperCode = <<<'JSHELPER' $scope.MostraErroreCalendario = function (data, fallback) { var testo = fallback; if (data && data.data && data.data.msg) { testo = data.data.msg; } console.error('SILVELOX CALENDAR ERROR', data); $scope.GestisciAlert('danger', testo); }; JSHELPER; $helperCode = convertEol($helperCode, $appEol); $app = substr($app, 0, $helperPosition) . $helperCode . substr($app, $helperPosition); $changedApp = true; } $replacements = array( '$scope.GestisciAlert(\'danger\', "Controlla di aver inserito correttamente tutti i dati richiesti.")' => '$scope.MostraErroreCalendario(data, "Errore durante il salvataggio dell\'evento.")', '$scope.GestisciAlert(\'danger\', "1.Controlla di aver inserito correttamente tutti i dati richiesti.")' => '$scope.MostraErroreCalendario(data, "Errore durante la creazione dell\'evento.")' ); foreach ($replacements as $old => $new) { $oldCount = substr_count($app, $old); $newCount = substr_count($app, $new); if ($oldCount === 0 && $newCount > 0) { continue; } if ($oldCount < 1) { throw new Exception("Messaggio JS atteso non trovato"); } $app = str_replace($old, $new, $app, $count); if ($count < 1) { throw new Exception("Sostituzione JS non eseguita"); } $changedApp = true; } if ($changedApp) { writeFileStrict($appFile, $app); } $cacheVersion = "STEP044_20260731"; foreach (array($indexFile, $gridFile) as $htmlFile) { $html = readFileStrict($htmlFile); if (strpos($html, "app/appCtrl.js?v=" . $cacheVersion) !== false) { continue; } $pattern = '~app/appCtrl\.js(?:\?v=[^"\']*)?~'; $patched = preg_replace( $pattern, "app/appCtrl.js?v=" . $cacheVersion, $html, 1, $count ); if ($patched === null || $count !== 1) { throw new Exception( "Cache-buster non applicato a " . basename($htmlFile) ); } writeFileStrict($htmlFile, $patched); } echo "PATCH_OK\n"; PHP section "Applicazione patch diagnostica" # Da qui ogni errore deve ripristinare anche una patch parziale. CHANGED=1 if PATCH_RESULT="$( "$PHP_BIN" "$PATCHER" \ "$CONTROLLER" \ "$APP_JS" \ "$INDEX_HTML" \ "$GRID_HTML" 2>&1 )"; then : else rc=$? echo "$PATCH_RESULT" >&2 exit "$rc" fi echo "$PATCH_RESULT" | tee -a "$REPORT" [[ "$PATCH_RESULT" == "PATCH_OK" ]] if [[ "$FAIL_AFTER_PATCH" == "1" ]]; then echo "ERRORE DI TEST DOPO PATCH" >&2 false fi section "Verifiche complete dopo patch" { "$PHP_BIN" -l "$CONTROLLER" if [[ -n "$NODE_BIN" ]]; then "$NODE_BIN" --check "$APP_JS" fi grep -Fq "private function diagnosticaErroreCalendario(" "$CONTROLLER" [[ "$(grep -F -c "diagnosticaErroreCalendario(" "$CONTROLLER")" -eq 4 ]] [[ "$(grep -F -c '$scope.MostraErroreCalendario(data' "$APP_JS")" -eq 3 ]] grep -Fq "tipo == 'danger' ? 15000 : 2000" "$APP_JS" grep -Fq "app/appCtrl.js?v=STEP044_20260731" "$INDEX_HTML" grep -Fq "app/appCtrl.js?v=STEP044_20260731" "$GRID_HTML" echo "PHP lint: OK" if [[ -n "$NODE_BIN" ]]; then echo "JS syntax: OK" else echo "JS syntax server: Node assente; verifica reale pre-consegna eseguita." fi echo "Tre operazioni diagnosticate: inserimento utente, inserimento admin, modifica" echo "Cache-buster JS: OK" } >> "$REPORT" 2>&1 cp -a -- "$CONTROLLER" "$WORKDIR/after/controllerCtrl.php" cp -a -- "$APP_JS" "$WORKDIR/after/appCtrl.js" cp -a -- "$INDEX_HTML" "$WORKDIR/after/index.html" cp -a -- "$GRID_HTML" "$WORKDIR/after/grid_table.tpl.html" section "Reload" reload_runtime >> "$REPORT" 2>&1 section "Test HTTP" if [[ "$SKIP_HTTP" == "1" || "$ALLOW_TEST" == "1" ]]; then echo "Test HTTP saltato in ambiente simulato." >> "$REPORT" else HTTP_CODE="$( curl -sS --max-time 20 \ -H "Host: $TARGET_HOST" \ -o "$WORKDIR/http_body.html" \ -w '%{http_code}' \ "http://127.0.0.1/ax_calendar.php" )" echo "HTTP_CODE=$HTTP_CODE" >> "$REPORT" [[ "$HTTP_CODE" =~ ^(200|302)$ ]] fi section "Esito" { echo "DATABASE MODIFICATO: NO" echo "QUERY SQL MODIFICATE: NO" echo "DIAGNOSTICA BACKEND: ATTIVA" echo "MESSAGGIO REALE NEL BROWSER: ATTIVO" echo "DURATA ERRORE A VIDEO: 15 secondi" echo "ESITO: COMPLETATO" } >> "$REPORT" ( cd "$WORKDIR" zip -q -9 -r "$OUTPUT_ZIP" \ "$(basename "$REPORT")" \ before \ after ) zip -T "$OUTPUT_ZIP" >/dev/null CHANGED=0 echo "STEP044 COMPLETATO" echo "ZIP: $OUTPUT_ZIP"