get_clienti.php, get_fixed_field_data.php: simulate mode support CustomField dropdown values mock added (get_customfield_values.php) exportUnsavedModal: prompt save before export, MutationObserver waits for save, then proceeds to confirm Removed old jQuery .export-lims-btn handler that bypassed confirm modal Fix false "Unsaved changes" on page load: data-restoring guard in all programmatic trigger/dispatchEvent calls (populateSelect, populateClientDropdowns, populateDropdowns) Fix ConsegnaRichiesta not shown on refresh: add to PHP $fixedAliasMap Add step5_2_photos, step9_1_importa
261 lines
9.1 KiB
PHP
261 lines
9.1 KiB
PHP
<?php
|
|
require_once dirname(__DIR__, 3) . '/vendor/autoload.php'; // Torna al livello di public
|
|
|
|
use Dotenv\Dotenv;
|
|
|
|
class VisualLimsApiClient
|
|
{
|
|
private static $instance = null;
|
|
private $baseUrl;
|
|
private $username;
|
|
private $password;
|
|
private $token = null;
|
|
|
|
private function __construct()
|
|
{
|
|
$dotenv = Dotenv::createImmutable(dirname(__DIR__, 3)); // Torna al livello di public
|
|
$dotenv->load();
|
|
|
|
$this->baseUrl = $_ENV['API_BASE_URL'];
|
|
$this->username = $_ENV['API_USERNAME'];
|
|
$this->password = $_ENV['API_PASSWORD'];
|
|
}
|
|
|
|
public static function getInstance()
|
|
{
|
|
if (self::$instance === null) {
|
|
$dotenv = Dotenv::createImmutable(dirname(__DIR__, 3));
|
|
$dotenv->load();
|
|
|
|
$simulate = ($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true';
|
|
|
|
if ($simulate) {
|
|
require_once __DIR__ . '/VisualLimsApiClientMock.class.php';
|
|
self::$instance = new VisualLimsApiClientMock();
|
|
} else {
|
|
self::$instance = new VisualLimsApiClient();
|
|
}
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
private function authenticate($retryCount = 0, $maxRetries = 3)
|
|
{
|
|
$ch = curl_init("{$this->baseUrl}/api/authentication/authenticate");
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
|
|
'Username' => $this->username,
|
|
'Password' => $this->password
|
|
]));
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
'Content-Type: application/json',
|
|
'Accept: application/json'
|
|
]);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
|
$log = fopen(__DIR__ . '/curl_auth_debug.log', 'a') ?: fopen('php://stderr', 'w');
|
|
curl_setopt($ch, CURLOPT_STDERR, $log);
|
|
|
|
$response = curl_exec($ch);
|
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$curl_error = curl_error($ch);
|
|
$log_message = date('Y-m-d H:i:s') . " - Auth attempt {$retryCount}: HTTP {$http_code}, Error: {$curl_error}, Response: " . substr($response, 0, 1000) . "\n";
|
|
fwrite($log, $log_message);
|
|
fclose($log);
|
|
curl_close($ch);
|
|
|
|
if ($response === false || $http_code != 200) {
|
|
if ($http_code === 400 && strpos($response, 'Cannot persist the object') !== false && $retryCount < $maxRetries) {
|
|
usleep(500000); // Ritardo di 500ms
|
|
return $this->authenticate($retryCount + 1, $maxRetries); // Riprova
|
|
}
|
|
throw new Exception("Autenticazione fallita: HTTP {$http_code}, Errore cURL: {$curl_error}, Risposta: " . substr($response, 0, 1000));
|
|
}
|
|
|
|
$token_data = json_decode($response, true);
|
|
$this->token = null;
|
|
|
|
if (is_array($token_data) && isset($token_data['token'])) {
|
|
$this->token = $token_data['token'];
|
|
} elseif (is_string($token_data) && !empty($token_data)) {
|
|
$this->token = trim($token_data, '"');
|
|
} elseif (is_string($response) && !empty($response)) {
|
|
$this->token = trim($response, '"');
|
|
}
|
|
|
|
if (empty($this->token)) {
|
|
throw new Exception("Token non ricevuto: " . substr($response, 0, 1000));
|
|
}
|
|
}
|
|
|
|
private function getToken()
|
|
{
|
|
if ($this->token === null) {
|
|
$this->authenticate();
|
|
}
|
|
return $this->token;
|
|
}
|
|
|
|
public function get($endpoint)
|
|
{
|
|
$token = $this->getToken();
|
|
$url = "{$this->baseUrl}/api/odata/{$endpoint}";
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
"Authorization: Bearer {$token}",
|
|
"Accept: application/json"
|
|
]);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
|
$log = fopen(__DIR__ . '/curl_request_debug.log', 'w') ?: fopen('php://stderr', 'w');
|
|
curl_setopt($ch, CURLOPT_STDERR, $log);
|
|
|
|
$response = curl_exec($ch);
|
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$curl_error = curl_error($ch);
|
|
fclose($log);
|
|
curl_close($ch);
|
|
|
|
if ($response === false) {
|
|
throw new Exception("Errore nella richiesta: {$curl_error}");
|
|
}
|
|
|
|
if ($http_code !== 200) {
|
|
throw new Exception("Errore nel recupero dati: HTTP {$http_code}, Risposta: " . substr($response, 0, 1000));
|
|
}
|
|
|
|
$data = json_decode($response, true);
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
throw new Exception("Risposta non JSON valida: " . substr($response, 0, 1000));
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
public function post($endpoint, $payload)
|
|
{
|
|
$token = $this->getToken();
|
|
$url = "{$this->baseUrl}/api/odata/{$endpoint}";
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
"Authorization: Bearer {$token}",
|
|
"Content-Type: application/json",
|
|
"Accept: application/json"
|
|
]);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
|
|
$response = curl_exec($ch);
|
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$curl_error = curl_error($ch);
|
|
curl_close($ch);
|
|
|
|
if ($response === false) {
|
|
throw new Exception("Errore nella richiesta POST: {$curl_error}");
|
|
}
|
|
|
|
if ($http_code < 200 || $http_code >= 300) {
|
|
throw new Exception("POST fallito: HTTP {$http_code}, Risposta: " . substr($response, 0, 1000));
|
|
}
|
|
|
|
return json_decode($response, true);
|
|
}
|
|
|
|
public function patch($endpoint, $payload)
|
|
{
|
|
$token = $this->getToken();
|
|
$url = "{$this->baseUrl}/api/odata/{$endpoint}";
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
"Authorization: Bearer {$token}",
|
|
"Content-Type: application/json",
|
|
"Accept: application/json"
|
|
]);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
|
|
$response = curl_exec($ch);
|
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$curl_error = curl_error($ch);
|
|
curl_close($ch);
|
|
|
|
if ($response === false) {
|
|
throw new Exception("Errore nella richiesta PATCH: {$curl_error}");
|
|
}
|
|
|
|
if ($http_code < 200 || $http_code >= 300) {
|
|
throw new Exception("PATCH fallito: HTTP {$http_code}, Risposta: " . substr($response, 0, 1000));
|
|
}
|
|
|
|
return json_decode($response, true);
|
|
}
|
|
|
|
/**
|
|
* POST a file as multipart/form-data (used for photo/attachment uploads).
|
|
*
|
|
* @param string $endpoint OData endpoint, e.g. "AllegatoCommessaWeb"
|
|
* @param string $filePath Absolute path to the file on disk
|
|
* @param string $fileName Original file name to send
|
|
* @param int $commessaId CommessaWeb ID to link the attachment to
|
|
* @return array|null Decoded JSON response
|
|
*/
|
|
public function postMultipart($endpoint, $filePath, $fileName, $commessaId)
|
|
{
|
|
$token = $this->getToken();
|
|
$url = "{$this->baseUrl}/api/odata/{$endpoint}";
|
|
|
|
$cfile = new CURLFile($filePath, mime_content_type($filePath) ?: 'application/octet-stream', $fileName);
|
|
|
|
$payload = [
|
|
'IdCommessa' => $commessaId,
|
|
'NomeFile' => $fileName,
|
|
'file' => $cfile,
|
|
];
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
"Authorization: Bearer {$token}",
|
|
"Accept: application/json",
|
|
// Content-Type is set automatically to multipart/form-data by cURL
|
|
]);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
|
|
$response = curl_exec($ch);
|
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$curl_error = curl_error($ch);
|
|
curl_close($ch);
|
|
|
|
if ($response === false) {
|
|
throw new Exception("Errore nella richiesta POST multipart: {$curl_error}");
|
|
}
|
|
|
|
if ($http_code < 200 || $http_code >= 300) {
|
|
throw new Exception("POST multipart fallito: HTTP {$http_code}, Risposta: " . substr($response, 0, 1000));
|
|
}
|
|
|
|
return json_decode($response, true);
|
|
}
|
|
|
|
public function getBaseUrl()
|
|
{
|
|
return $this->baseUrl;
|
|
}
|
|
}
|