getInfo() : []; // Prendiamo il CF dell'utente $cf = $userInfo["cf"] ?? null; if (!$cf) { swalDie("Utente non valido.", "Attenzione", "reload", "error", false); } $delega = $this->getFullDelegaInfo($cf); // L'utente ha già una collaborazione if(!empty($delega["sa_cf"]) && !empty($delega["sa_ausa"])) { return; } // STEP 1. Cerchiamo di recuperare la collaobrazione dall'ultimo JWT valido // TODO Implementare // STEP 2. Chiediamo all'utente di stabilire la collaborazione if(!empty($this->fascicolo["stazioni_appaltanti"])) { $sceltaSA = json_decode($this->fascicolo["stazioni_appaltanti"], true); } if(empty($sceltaSA) && !empty($this->scheda["dati"]) && valid_json($this->scheda["dati"], $dati, true)) { $sceltaSA = DotPath::get($dati, "anacForm.stazioniAppaltanti"); if(!empty($sceltaSA)) $sceltaSA = array_values($sceltaSA); } if(empty($sceltaSA)) { swalDie("Impossibile trovare Stazioni Appaltanti per la collaborazione.", "Attenzione", "reload", "error", false); } // Ora che abbiamo le scelte if(count($sceltaSA) > 1) { $sceltaUtente = $_REQUEST["scelta_sa"] ?? null; if($sceltaUtente === null || !is_numeric($sceltaUtente)) { $requestType = $_SERVER['REQUEST_METHOD']; $requestUrl = $_SERVER['REQUEST_URI']; $params = $requestType === 'POST' ? $_POST : $_GET; // Convertiamo i dati in query string per passarli nell'URL $paramsQuery = http_build_query(['params' => $params]); $choicesQuery = http_build_query(['stazioni_appaltanti' => $sceltaSA]); $modalUrl = "/backend/npa/ajax/request_collaborazione.php?requestType={$requestType}&requestUrl=" . urlencode($requestUrl) . "&{$paramsQuery}&{$choicesQuery}"; // Interrompiamo l'esecuzione dello script PHP e mostriamo la modale die("showModalWithURL('$modalUrl');"); } } else { $sceltaUtente = 0; } $stazioneAppaltante = $sceltaSA[$sceltaUtente] ?? null; if(empty($stazioneAppaltante)) { swalDie("Impossibile trovare la Stazione Appaltante selezionata.", "Attenzione", "reload", "error", false); } $this->overrideStazioneAppaltante = $stazioneAppaltante; } /** * Interroga il database per ottenere la delega utente in base al suo CF e al codice NPA (o il CF a partire dalla delega) * * @param ?string $cf Codice fiscale per cui cercare. Non indicare se si vuole cercare un CF in base al ruolo. * @param ?string $role Ruolo per cui cercare. Non indicare se si vuole cercare un ruolo in base al CF * @param integer $codice Codice NPA * @return array|null Ritorna un array contenente l'intera row trovata o null se non c'è */ public static function fullEntryFromDB(?string $cf = null, ?string $role = null, int $codice) { if (($cf === null && $role === null) || ($cf !== null && $role !== null)) { throw new Exception("searchFromDB va chiamato con SOLO cf o role"); } /** * @var \myPDO $pdo */ global $pdo; if ($role === null) { return $pdo->go( "SELECT * FROM b_npa_deleghe d WHERE d.codice_npa = :codice AND cf = :cf", [":cf" => $cf, ":codice" => $codice] )->fetch(PDO::FETCH_ASSOC); } if (!in_array($role, DelegheRoles::VALID_ROLES)) return null; return $pdo->go( "SELECT * FROM b_npa_deleghe d WHERE d.codice_npa = :codice AND role = :role", [":role" => $role, ":codice" => $codice] )->fetch(PDO::FETCH_ASSOC) ?? null; } /** * Interroga il database per ottenere la delega utente in base al suo CF e al codice NPA (o il CF a partire dalla delega) * * @param ?string $cf Codice fiscale per cui cercare. Non indicare se si vuole cercare un CF in base al ruolo. * @param ?string $role Ruolo per cui cercare. Non indicare se si vuole cercare un ruolo in base al CF * @param integer $codice Codice NPA * @return string|null ritorna RP, DRP1, DRP2, DRP3 o null o un codice fiscale */ public static function searchFromDB(?string $cf = null, ?string $role = null, int $codice) { if (($cf === null && $role === null) || ($cf !== null && $role !== null)) { throw new Exception("searchFromDB va chiamato con SOLO cf o role"); } /** * @var \myPDO $pdo */ global $pdo; // Ritorniamo un role if ($role === null) { $role = $pdo->go( "SELECT d.role FROM b_npa_deleghe d WHERE d.codice_npa = :codice AND cf = :cf", [":cf" => $cf, ":codice" => $codice] )->fetch(PDO::FETCH_ASSOC)["role"] ?? null; // Verifichiamo la validità del ruolo if (!$role || !in_array($role, DelegheRoles::VALID_ROLES)) return null; return $role; } // Ritorniamo un cf if (!in_array($role, DelegheRoles::VALID_ROLES)) return null; return $pdo->go( "SELECT d.cf FROM b_npa_deleghe d WHERE d.codice_npa = :codice AND role = :role", [":role" => $role, ":codice" => $codice] )->fetch(PDO::FETCH_ASSOC)["cf"] ?? null; } /** * Metodo per rimuovre una delega da un fascicolo anac sul DB * * @param string|null $role Ruolo da rimuovere * @param integer $codice Codice NPA * @return void */ private static function deleteUserRoleFromDB(string $role, int $codice) { /** * @var \myPDO $pdo */ global $pdo; $pdo->go( "DELETE FROM `b_npa_deleghe` WHERE `role` = :role AND `codice_npa` = :codice", [":role" => $role, ":codice" => $codice] ); } /** * Metodo per aggiornare il ruolo di un utente sul DB. Non chiede ad ANAC * * @param string $cf Codice fiscale del soggetto * @param string|null $role Ruolo da assegnare * @param integer $codice Codice NPA * @return bool True se tutto va bene */ private static function setUserRoleToDB(string $cf, string $role, int $codice, ?string $sa_cf = null, ?string $sa_ausa = null) { /** * @var \myPDO $pdo */ global $pdo; if ($role === DelegheRoles::UNAUTHORIZED) return true; $data = [ "cf" => $cf, "role" => $role, "codice_npa" => $codice, ]; if(!empty($sa_cf)) { $data["sa_cf"] = $sa_cf; } if(!empty($sa_ausa)) { $data["sa_ausa"] = $sa_ausa; } $bind = []; $fields_str = $values_str = $update_str = ""; $i = 0; foreach($data as $key => $value) { $bind[":{$key}"] = $value; $comma = ($i > 0 ? ", " : ""); $fields_str .= "{$comma} {$key}"; $values_str .= "{$comma} :{$key}"; $update_str .= "{$comma} {$key} = :{$key}"; $i++; } $query = <<go($query, $bind); return $exec->rowCount() > 0; } /** * Interroga i server anac per ottenere il ruolo dell'utente attuale * * @param ?string $cf Codice fiscale per cui cercare. Non indicare se si vuole cercare un CF in base al ruolo. * @param ?string $role Ruolo per cui cercare. Non indicare se si vuole cercare un ruolo in base al CF * @return string|null ritorna RP, DRP1, DRP2, DRP3, UNAUTHORIZED o null */ public function searchFromANAC(?string $cf = null, ?string $role = null) { date_default_timezone_set('Europe/Rome'); if (($cf === null && $role === null) || ($cf !== null && $role !== null)) { throw new Exception("searchFromDB va chiamato con SOLO cf o role"); } // Vediamo se abbiamo un idAppalto if (empty($this->fascicolo) || empty($this->fascicolo["id_appalto"])) { return null; } $idAppalto = $this->fascicolo["id_appalto"]; $searchTerms = [ "idAppalto" => $idAppalto, //"dataInizioA" => (new \DateTime("-15 seconds"))->format('Y-m-d\TH:i:s') . "+00:00", // Non ancora scaduto "dataFineDa" => (new \DateTime("+15 seconds"))->format('Y-m-d\TH:i:s') . "+00:00", // Non ancora scaduto ]; if ($cf !== null) { $cf = strtoupper(trim($cf)); if (empty($cf)) return null; $searchTerms["codiceFiscale"] = $cf; } if ($role !== null) { $role = strtoupper(trim($role)); if (empty($role)) return null; } $result_str = $this->apiCall( "gestioneUtenti", "ricerca-soggetti", $searchTerms, ); // Verifichiamo la risposta if (empty($result_str) || !valid_json($result_str, $result_list, TRUE)) { return null; }; // Se non ci sono risultati ritorniamo null per la ricerca del ruolo o UNAUTHORIZED per la ricerca per CF if (empty($result_list["result"])) { return $cf === null ? null : DelegheRoles::UNAUTHORIZED; } foreach ($result_list["result"] as $result) { if ($cf !== null && trim(strtoupper($result["codiceFiscale"])) === $cf) { // Ritorniamo un ruolo $role = strtoupper($result["ruolo"]["codice"]); // Verifichiamo sia valido if (!$role || !in_array($role, DelegheRoles::VALID_ROLES)) return null; return $role; } elseif ($role !== null && $result["ruolo"]["codice"] === $role) { return trim(strtoupper($result["codiceFiscale"])); } } return null; } /** * Imposta il ruolo di un utente * * @param ?string $cf Codice fiscale dell'utente in questione. Non usato per remove * @param string $role Ruolo da assegnare/rimuovere (vedi DelegheRoles) * @param string $remove Se true, rimuove il ruolo dal soggetto * @param string|null $message Eventuale messaggio di fallimento * @param bool $recursive Per uso interno * @return bool Ritorna true se tutto va bene */ public function setUserRole(?string $cf, string $role, ?string $sa_cf, ?string $sa_ausa, bool $remove = false, ?string &$message = null, bool $recursive = false) { date_default_timezone_set('Europe/Rome'); // Vediamo se abbiamo un idAppalto if (empty($this->fascicolo) || empty($this->fascicolo["id_appalto"])) { $message = "Questo appalto non è pronto ad accettare deleghe."; return false; } $idAppalto = $this->fascicolo["id_appalto"]; $endpoint = "gestioneUtenti"; if ($cf !== null) $cf = strtoupper(trim($cf)); if ($role == DelegheRoles::RP) { // Per settare un nuovo RP possiamo farlo solo per l'utente corrente $utente = $_SESSION["utente"] ?? null; // Verifichiamo il livello di autenticazione if (!$utente || $utente->authenticationLevel < 2) { $message = "Impossibile prendere carico della gara. Permessi insufficienti."; return false; } if(!$this->canCurrentUserTakeOver()) { $message = "Impossibile prendere il carico della gara. Non sei nominato come RUP."; return false; } // Otteniamo le info utente $userInfo = method_exists($utente, "getInfo") ? $utente->getInfo() : []; // Prendiamo il CF dell'utente $current_cf = $userInfo["cf"] ?? null; if (!$current_cf) { $message = "Impossibile prendere carico della gara. Codice Fiscale non impostato."; return false; } $cf = strtoupper(trim($current_cf)); // Creiamo l'authorization personalizzata $apiConfig = $this->api_config[$endpoint]; $method = "presa-carico"; $claims = $this->getAnacJwsCustomClaims( $apiConfig["endpoint"], $this->getUUID(), $cf, DelegheRoles::RP, $sa_cf, $sa_ausa ); // Effettuiamo la presa carico $apiMessage = null; $response_str = $this->apiCall($endpoint, $method, ["idAppalto" => $idAppalto], $claims, [], $apiMessage); // Verifichiamo ci sia una risposta if (empty($response_str) || !valid_json($response_str, $response, TRUE)) { $message = empty($apiMessage) ? __("Errore durante la richiesta di presa carico.") : __($apiMessage); return false; } // Verifichiamo ci sia un errore if ($response["status"] !== 200) { if (isset($response["errori"][0]["codice"])) { $dictionary = $this->getErrorsDictionary(); $codice_errore = $response["errori"][0]["codice"]; $message = $dictionary[$response["errori"][0]["codice"]]["descrizione"]["it"] ?? null; } $message = $message == null ? __("Errore durante nella risposta per la richiesta di delega.") : $message; return false; } // Aggiorniamo il RUP nel fascicolo if(isset($this->fascicolo["data"]) ) { $data = unserialize($this->fascicolo["data"]); $data["rup"]["codice_fiscale"] = $cf; $salva = new Salva(); $salva->debug = false; $salva->operazione = "UPDATE"; $salva->nome_tabella = "b_npa"; $salva->ignore = ["timestamp"]; $salva->ignore_versione = true; $salva->oggetto = ["codice" => $this->fascicolo["codice"], "data" => serialize($data)]; if(!$salva->save()) { $message = __("Errore durante l'aggiornamento del RUP"); return false; } } return self::setUserRoleToDB($cf, $role, $this->fascicolo["codice"], $sa_cf, $sa_ausa); } $method = $remove ? "elimina-soggetto" : "aggiungi-soggetto"; // Campo in comune per entrambe le richieste $query = [ "idAppalto" => $idAppalto, "ruolo" => [ "idTipologica" => "tipoRuolo", "codice" => $role, ], ]; // La delega richiede una data di inizio if ($method === "aggiungi-soggetto") { $query["dataInizio"] = (new \DateTime())->format(\DateTime::ATOM); $query["codiceFiscale"] = $cf; } $apiMessage = null; $response_str = $this->apiCall($endpoint, $method, $query, null, [], $apiMessage); // Verifichiamo ci sia una risposta if (empty($response_str) || !valid_json($response_str, $response, TRUE)) { $message = empty($apiMessage) ? __("Errore durante la richiesta di delega.") : __($apiMessage); return false; } // Verifichiamo ci sia un errore if ($response["status"] !== 200) { if (isset($response["errori"][0]["codice"])) { $dictionary = $this->getErrorsDictionary(); $codice_errore = $response["errori"][0]["codice"]; $message = $dictionary[$response["errori"][0]["codice"]]["descrizione"]["it"] ?? null; // Gestione situazioni di desync if (!$recursive) { // In questa situazione c'è già un delegato che non era sincronizzato con il DB if ($codice_errore === "ERR80" && !$remove) { // Eliminiamo il precedente e ricreiamo if ($this->setUserRole(null, $role, $sa_cf, $sa_ausa, true, $message, true)) { if ($this->setUserRole($cf, $role, $sa_cf, $sa_ausa, false, $message, true)) { return self::setUserRoleToDB($cf, $role, $this->fascicolo["codice"], $sa_cf, $sa_ausa); } } } // Tentativo di rimozione di un delegato già rimosso elseif ($codice_errore == "AUT10" && $remove) { // Rimuoviamo dal db e basta $this->deleteUserRoleFromDB($role, $this->fascicolo["codice"]); return true; } } } $message = $message == null ? __("Errore durante nella risposta per la richiesta di delega.") : $message; return false; } // Le chiamate ricorsive non toccano il db if ($recursive) return true; // Aggiorniamo il db if ($remove) { $this->deleteUserRoleFromDB($role, $this->fascicolo["codice"]); return true; } return self::setUserRoleToDB($cf, $role, $this->fascicolo["codice"], $sa_cf, $sa_ausa); } public function getFullDelegaInfo(string $cf) { return self::fullEntryFromDB($cf, null, $this->fascicolo["codice"]); } /** * Ottiene un codice fiscale in base al ruolo specificato * * @param string|array $role * @param int $syncBehaviour * @return string|null|array Ritorna un CF o null */ public function getUserFromRole($role, int $syncBehaviour = RoleSyncBehaviour::NEVER_SYNC) { if(is_array($role)) { foreach ($role as $r) { $retVal[] = $this->getUserFromRole($r, $syncBehaviour); } return $retVal; } $retVal = null; // Determiniamo se dobbiamo chiedere ad anac $syncFromAnac = $syncBehaviour == RoleSyncBehaviour::ALWAYS_SYNC; // Vediamo se abbiamo un codice if (empty($this->fascicolo)) { return null; } // Verifichiamo di avere un ruolo nel DB if(empty($this->fascicolo["codice"])) $retVal = $db_cf = null; else $retVal = $db_cf = self::searchFromDB(null, $role, $this->fascicolo["codice"]); if ($db_cf == null) { // Il RUP al primo caricamento viene preso dal fascicolo if ($role == DelegheRoles::RP && !empty($this->fascicolo["data"])) { // Prendiamo il CF del RUP nel caso stiamo cercando un RP $cf_npa = unserialize($this->fascicolo["data"])["rup"]["codice_fiscale"] ?? null; if (!empty($cf_npa)) { $retVal = $cf_npa; } } // Determiniamo se ora dobbiamo sincronizzare da ANAC $syncFromAnac = $syncFromAnac || $syncBehaviour == RoleSyncBehaviour::SYNC_ON_NULL; } // Se dobbiamo sincronizzare scarichiamo anac if ($syncFromAnac) { $anac_cf = $this->searchFromANAC(null, $role); // Se è diverso, sincronizziamo il db if ($anac_cf !== null) { $retVal = $anac_cf; } } // Se il risultato è diverso, sincronizziamo sul db if ($retVal !== $db_cf && !empty($this->fascicolo["codice"])) { self::setUserRoleToDB($retVal, $role, $this->fascicolo["codice"]); } return empty($retVal) ? $retVal : trim(strtoupper($retVal)); } /** * Determina se l'utente corrente può diventare il nuovo RP essendo RUP dell'oggetto * * @return boolean */ public function canCurrentUserTakeOver() { $rup_cf = null; if(isset($this->oggetto) && method_exists($this->oggetto, "getRup")) { $rup_cf = $this->oggetto->getRup()["codice_fiscale"] ?? null; } if($rup_cf === null) return false; $utente = $_SESSION["utente"] ?? null; // Verifichiamo il livello di autenticazione if (!$utente || $utente->authenticationLevel < 2) return false; // Otteniamo le info utente $userInfo = method_exists($utente, "getInfo") ? $utente->getInfo() : []; // Prendiamo il CF dell'utente $cf = $userInfo["cf"] ?? null; if($cf === null) return false; return trim(strtoupper($rup_cf)) === trim(strtoupper($cf)); } public function getCurrentUserRole(int $syncBehaviour = RoleSyncBehaviour::NEVER_SYNC) { $utente = $_SESSION["utente"] ?? null; // Verifichiamo il livello di autenticazione if (!$utente || $utente->authenticationLevel < 2) { return DelegheRoles::UNAUTHORIZED; } // Otteniamo le info utente $userInfo = method_exists($utente, "getInfo") ? $utente->getInfo() : []; // Prendiamo il CF dell'utente $cf = trim(strtoupper($userInfo["cf"])) ?? null; if (!$cf) { return DelegheRoles::UNAUTHORIZED; } return $this->getUserRole($cf, null, $syncBehaviour); } /** * Verifica il ruolo dell'utente corrente nell'ambito della scheda attule * * @param string $cf Codice fiscale da verificare * @param array|null $preferredRoles Se impostato, il sync viene ignorato e viene cercato sulla base dei ruoli scelti * @param int $syncBehaviour Definisce il comportamento per sincronizzarsi dai server ANAC * * @return string|null ritorna RP, DRP1, DRP2, DRP3 o null */ public function getUserRole(string $cf, ?array $preferredRoles = null, int $syncBehaviour = RoleSyncBehaviour::NEVER_SYNC) { $cf = trim(strtoupper($cf)); $retVal = DelegheRoles::UNAUTHORIZED; // Determiniamo se dobbiamo chiedere ad anac $syncFromAnac = $syncBehaviour == RoleSyncBehaviour::ALWAYS_SYNC; // Vediamo se abbiamo un codice if (empty($this->fascicolo)) { return null; } // Se abbiamo dei ruoli preferiti, cerchiamo per quelli if (!empty($preferredRoles)) { if ($syncBehaviour !== RoleSyncBehaviour::NEVER_SYNC) { throw new Exception("Se \$preferredRoles !== null, \$syncBehaviour deve essere NEVER_SYNC"); } foreach ($preferredRoles as $preferredRole) { if (in_array($preferredRole, DelegheRoles::ANY)) { if(empty($this->fascicolo["codice"])) $preferredRoleOwner = null; else $preferredRoleOwner = self::searchFromDB(null, $preferredRole, $this->fascicolo["codice"]); // Se lo abbiamo trovato if ($preferredRoleOwner === $cf) { return $preferredRole; } } } } // Verifichiamo di avere un ruolo nel DB if(empty($this->fascicolo["codice"])) $retVal = $db_role = null; else $retVal = $db_role = self::searchFromDB($cf, null, $this->fascicolo["codice"]); if ($db_role == null) { // Prendiamo il CF del RUP, se è uguale al CF passato forziamo il ruolo RP $cf_npa = unserialize($this->fascicolo["data"])["rup"]["codice_fiscale"] ?? null; if (!empty($cf_npa)) { if (trim(strtoupper($cf)) === trim(strtoupper($cf_npa))) { $retVal = DelegheRoles::RP; } } // Determiniamo se ora dobbiamo sincronizzare da ANAC $syncFromAnac = $syncFromAnac || $syncBehaviour == RoleSyncBehaviour::SYNC_ON_NULL; } // Se dobbiamo sincronizzare if ($syncFromAnac) { $anac_role = $this->searchFromANAC($cf, null); if ($anac_role !== null) { $retVal = $anac_role; } } // Se il risultato è diverso sincronizziamo if ($retVal !== $db_role && !empty($this->fascicolo["codice"])) { self::setUserRoleToDB($cf, $retVal, $this->fascicolo["codice"]); } return $retVal; } } class DelegheRoles { // Ruoli /** * Responsabile del progetto */ const RP = "RP"; /** * Delegato responsabile Programmazione */ const DRP1 = "DRP1"; /** * Delegato responsabile Affidamento */ const DRP2 = "DRP2"; /** * Delegato responsabile Esecuzione (viva la france) */ const DRP3 = "DRP3"; /** * Nessun permesso trovato */ const UNAUTHORIZED = "UNAUTHORIZED"; /** * Permesso speciale che viene usato per la presa-carico */ const NO_AUTH = ["NO_AUTH"]; /** * Ruoli validi */ const VALID_ROLES = [self::RP, self::DRP1, self::DRP2, self::DRP3, self::UNAUTHORIZED]; const DRPs = [self::DRP1, self::DRP2, self::DRP3]; /** * Qualunque ruolo */ const ANY = [self::RP, self::DRP1, self::DRP2, self::DRP3]; const Nomi = [ self::RP => "Responsabile Progetto", self::DRP1 => "Delegato Pubblicazione", self::DRP2 => "Delegato Affidamento", self::DRP3 => "Delegato Esecuzione" ]; } /** * Comportamenti di richiesta della delega */ class RoleSyncBehaviour { /** * Usa solo il risultato del DB */ const NEVER_SYNC = 0; /** * Se il risultato del db è null, chiedi ad ANAC e sincronizza il risultato su DB */ const SYNC_ON_NULL = 1; /** * Verifica sempre il risultato su ANAC e sincronizzalo sul DB se diverso da quello attuale */ const ALWAYS_SYNC = 2; }