Non puoi selezionare più di 25 argomenti
Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
3467 righe
113 KiB
3467 righe
113 KiB
<?php |
|
ini_set('xdebug.max_nesting_level', 500); |
|
|
|
|
|
include_once __DIR__ . '/XML_XPath20_Querier.class.php'; |
|
include_once __DIR__ . '/MyDOMDocument.class.php'; |
|
include_once __DIR__ . '/eFormsManager_xml_order.trait.php'; |
|
include_once __DIR__ . '/BS4FormGenerator.trait.php'; |
|
include_once __DIR__ . '/eFormsManager_render.trait.php'; |
|
include_once __DIR__ . '/eFormsManager_suggestions.trait.php'; |
|
include_once __DIR__ . '/TedEsenderApiManager.trait.php'; |
|
include_once __DIR__ . '/TedEsenderHelpersFuncions.trait.php'; |
|
/** |
|
* Questa classe gestisce la compilazione. validazione e trasmissione dei form eNotice ( https://docs.ted.europa.eu/eforms/latest/index.html ) |
|
*/ |
|
class eFormsManager |
|
{ |
|
use eFormsManager_suggestions, eFormsManager_render, eFormsManager_xml_order, TedEsenderHelpersFuncions, TedEsenderApiManager; |
|
/** |
|
* Riferimento a NuovaPiattaformaAppaltio |
|
* |
|
* @var NuovaPiattaformaAppalti|null |
|
*/ |
|
public $npa = null; |
|
/** |
|
* La modalità attuale in cui eFormsManager sta girando |
|
* |
|
* @var string |
|
*/ |
|
public $mode = ""; |
|
/** |
|
* Stabilisce se eFormsManager è in modalità di debug |
|
* |
|
* @var boolean |
|
*/ |
|
public $debug = false; |
|
/** |
|
* Stabilisce se è attivo il debug avanzato |
|
* |
|
* @var boolean |
|
*/ |
|
public $advanced_debug = false; |
|
|
|
/** |
|
* Tutti i dati relativi al form correntemente caricato |
|
* |
|
* @var array |
|
*/ |
|
public $info = []; |
|
|
|
/** |
|
* ID della notice correntemente caricata |
|
* |
|
* @var ?int |
|
*/ |
|
public $notice = null; |
|
|
|
/** |
|
* Titolo della notice correntemente caricata |
|
* |
|
* @var string |
|
*/ |
|
public $title = ""; |
|
|
|
/** |
|
* Sezioni della notice caricata |
|
* |
|
* @var array |
|
*/ |
|
public $sections = []; |
|
|
|
/** |
|
* Sezione correntemente caricata |
|
* |
|
* @var array|null |
|
*/ |
|
public $current_section; |
|
|
|
|
|
/** |
|
* ID della sezione correntemente caricata |
|
* |
|
* @var string|null |
|
*/ |
|
public $current_section_id; |
|
|
|
|
|
|
|
/** |
|
* Indice della sottosezione correntemente caricata |
|
* |
|
* @var int|null |
|
*/ |
|
public $current_subsection; |
|
|
|
|
|
/** |
|
* Deserializzazione di notice-types.json |
|
* |
|
* @var array |
|
*/ |
|
protected $notice_types; |
|
|
|
/** |
|
* Metadata dei campi fornito da fields.json |
|
* |
|
* @var array |
|
*/ |
|
private $fields_metadata; |
|
/** |
|
* Metadata della strutture XML fornito da fields.json |
|
* |
|
* @var array |
|
*/ |
|
private $xml_structure; |
|
|
|
/** |
|
* Dati temporanei per la gestione corretta dei campi unpublish |
|
* |
|
* @var array |
|
*/ |
|
private $unpublish_temp_data = []; |
|
|
|
/** |
|
* Path dell'SDK di eForms2 caricato |
|
* |
|
* @var string |
|
*/ |
|
private $current_sdk_path; |
|
|
|
/** |
|
* Deserializzazione delle codelist |
|
* |
|
* @var array |
|
*/ |
|
private $codelist; |
|
|
|
/** |
|
* Lingua correntemente attiva. |
|
* |
|
* @var string |
|
*/ |
|
private $language; |
|
|
|
/** |
|
* Traduzioni caricate di eForms |
|
* |
|
* @var array |
|
*/ |
|
private $eforms_translations; |
|
|
|
/** |
|
* Campi attuali del form |
|
* |
|
* @var array |
|
*/ |
|
private $fields; |
|
|
|
/** |
|
* Impostazioni attuali del form |
|
* |
|
* @var array |
|
*/ |
|
private $settings; |
|
|
|
/** |
|
* La struttura XML attuale |
|
* |
|
* @var DOMDocument|string |
|
*/ |
|
private $xml; |
|
|
|
/** |
|
* Fornisce esecuzione di query XPath 2.0 per la generazione |
|
* |
|
* @var XML_XPath20_Querier $gen_query_provider |
|
*/ |
|
private $gen_query_provider; |
|
|
|
/** |
|
* Fornisce esecuzione di query XPath 2.0 per la validazione |
|
* |
|
* @var XML_XPath20_Querier $efx_query_provider |
|
*/ |
|
private $efx_query_provider; |
|
|
|
/** |
|
* Counter interni per le ripetizioni dei campi |
|
* |
|
* @var array |
|
*/ |
|
private $field_counters = []; |
|
|
|
/** |
|
* Archivio dei namespace XML conosciuti |
|
* |
|
* @var array |
|
*/ |
|
private $namespaces = []; |
|
|
|
/** |
|
* Stabilisce se il render attuale di XML è finale |
|
* |
|
* @var bool |
|
*/ |
|
private $final_xml_render = false; |
|
|
|
/** |
|
* Quando è falso, la valutazione di EFX viene disattivata |
|
* |
|
* @var boolean |
|
*/ |
|
public $enable_efx_evaluation = true; |
|
|
|
/** |
|
* Tiene traccia delle sezioni correntemente complete. |
|
* Viene popolato chiamando $this->completed |
|
* |
|
* @var array |
|
*/ |
|
public $completed_subsections = []; |
|
|
|
/** |
|
* Quando è true, vengono nascoste le label interne delle sezioni |
|
* |
|
* @var boolean |
|
*/ |
|
public $hide_labels = true; |
|
|
|
/** |
|
* Rappresenta un override temporaneo del notice_id |
|
* |
|
* @var string|null |
|
*/ |
|
private $override_notice_id = null; |
|
/** |
|
* Rappresenta la versione attualmente caricata del SDK eForms2 |
|
* |
|
* @var string |
|
*/ |
|
private $loaded_version; |
|
/** |
|
* Rappresenta la versione di default dell'SDK da caricare |
|
*/ |
|
const SDK_VERSION = "1.9.1"; |
|
|
|
/** |
|
* Costante usata per indicare al costruttore che va bene qualunque tipo di notice al caricamento |
|
*/ |
|
const ANY_NOTICE_TYPE = "ANY"; |
|
/** |
|
* Rappresenta il path di default per l'SDK eForms |
|
*/ |
|
const STATIC_PATH = __DIR__ . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR . "eforms" . DIRECTORY_SEPARATOR . self::SDK_VERSION; |
|
/** |
|
* Viene aggregato ai valori di tempo o date per aggiungere il fuso orario TODO: In futuro andrebbe integrato meglio |
|
*/ |
|
protected const TIMEZONE_SUFFIX = "+02:00"; |
|
|
|
|
|
/** |
|
* Questo array contrassegna le sezioni escluse dalla regola di auto not-mandatory quando una sezione è vuota |
|
*/ |
|
const EXCLUDED_FROM_AUTO_NOT_MANDATORY_SECTIONS = [ |
|
"GR-Part" => true, |
|
"GR-Lot" => true, |
|
"GR-Organisations" => true, |
|
"GR-Procedure-TenderingTerms" => true, |
|
"GR-LotResult-Contract-Id-Ref" => true |
|
]; |
|
/** |
|
* Questo array serve a riconosce i campi per la pubblicazione successiva |
|
*/ |
|
const UNPUBLISH_FIELDS = [ |
|
"BT-195" => true, |
|
"BT-196" => true, |
|
"BT-197" => true, |
|
"BT-198" => true, |
|
]; |
|
|
|
/** |
|
* Riferimenti degli schema per ricavare i nomi |
|
* |
|
* @var array |
|
*/ |
|
private const SCHEMA_REFERENCES = [ |
|
"LOT" => "ND-Lot", |
|
"GLO" => "ND-LotsGroup", |
|
"ORG" => "ND-Organization", |
|
"TPO" => "ND-Touchpoint", |
|
"UBO" => "ND-UBO", |
|
"RES" => "ND-LotResult", |
|
"TEN" => "ND-LotTender", |
|
"CON" => "ND-SettledContract", |
|
"TPO" => "ND-Touchpoint", |
|
"PAR" => "ND-Part", |
|
]; |
|
|
|
|
|
/** |
|
* Questo array collega uno schema ID ad una sezione, per poter reindirizzare correttamente l'utente |
|
*/ |
|
const SCHEMA_ID_TO_SECTION = [ |
|
"ORG" => [ |
|
"label" => "Organizzazioni", |
|
"section" => "GR-Organisations-Section", |
|
], |
|
"TPO" => [ |
|
"label" => "Organizzazioni", |
|
"section" => "GR-Organisations-Section", |
|
], |
|
"UBO" => [ |
|
"label" => "Organizzazioni", |
|
"section" => "GR-Organisations-Section", |
|
], |
|
"LOT" => [ |
|
"label" => "Lotti", |
|
"section" => "GR-Lot", |
|
], |
|
"PAR" => [ |
|
"label" => "Parti", |
|
"section" => "GR-Part", |
|
], |
|
"GLO" => [ |
|
"label" => "Gruppi di Lotti", |
|
"section" => "GR-LotsGroup", |
|
], |
|
"RES" => [ |
|
"label" => "Risultati", |
|
"section" => "GR-Result", |
|
], |
|
"TPA" => [ |
|
"label" => "Risultati", |
|
"section" => "GR-Result", |
|
], |
|
"TEN" => [ |
|
"label" => "Risultati", |
|
"section" => "GR-Result", |
|
], |
|
"CON" => [ |
|
"label" => "Risultati", |
|
"section" => "GR-Result", |
|
], |
|
]; |
|
|
|
// Dizionari di workaround |
|
|
|
/** |
|
* Quando EFX è off, mandatory viene sovrascritto con queste regole |
|
*/ |
|
const EFX_OFF_DEFAULT_MANDATORY = [ |
|
"BT-536" => false, |
|
"BT-5141" => false, |
|
"BT-5421" => false, |
|
"OPP-050" => false, |
|
]; |
|
/** |
|
* Quando un constraint fallisce, queste regole vengono valutate per FORBIDDEN |
|
*/ |
|
const CONSTRAINT_FAILED_DEFAULT_FORBIDDEN = [ |
|
"BT-5421" => true, |
|
"BT-5422" => true, |
|
"BT-5423" => true, |
|
"BT-5141" => true, |
|
]; |
|
/** |
|
* Quando un constraint fallisce, queste regole vengono valutate per MANDATORY |
|
*/ |
|
const CONSTRAINT_FAILED_DEFAULT_MANDATORY = []; |
|
|
|
/** |
|
* Queste regole vengono sempre applicate per rosvrascrivere MANDATORY |
|
*/ |
|
const MANDATORY_OVERRIDE = []; |
|
|
|
/** |
|
* Connessione al database |
|
* |
|
* @var \myPDO |
|
*/ |
|
private $pdo; |
|
|
|
/** |
|
* Imposta la modalità di operazione attuale. Serve per definire alcuni comportamenti speciali |
|
* |
|
* @param string $mode |
|
* @return void |
|
*/ |
|
public function setMode(string $mode) |
|
{ |
|
$this->mode = $mode; |
|
} |
|
/** |
|
* Aggiorna il query provider per EFX quando viene aggiornata la strutture XML |
|
* |
|
* @return void |
|
*/ |
|
protected function onUpdateXml(): void |
|
{ |
|
$this->efx_query_provider = new XML_XPath20_Querier($this->xml, true, true); |
|
} |
|
|
|
/** |
|
* Popola il dizionario di eForms2 per le traduzioni |
|
* |
|
* @return Array|Null |
|
*/ |
|
static private function getTranslator(): ?array |
|
{ |
|
|
|
$path = static::STATIC_PATH; |
|
|
|
$language = strtolower($_SESSION["language"] ?? "it"); |
|
|
|
if (!file_exists("{$path}/translations/{$language}.json")) { |
|
$vocabulary = []; |
|
$files = glob("{$path}/translations/*_{$language}.xml"); |
|
foreach ($files as $file) { |
|
|
|
$xml = simplexml_load_file($file); |
|
foreach ($xml->entry as $translation) { |
|
$vocabulary[(string) $translation->attributes()["key"]] = (string) $translation; |
|
} |
|
|
|
$xml = null; |
|
gc_collect_cycles(); |
|
} |
|
|
|
file_put_contents("{$path}/translations/{$language}.json", json_encode($vocabulary, JSON_PRETTY_PRINT)); |
|
} |
|
|
|
return json_decode(file_get_contents("{$path}/translations/{$language}.json"), TRUE); |
|
} |
|
|
|
/** |
|
* Ritorna uno scheletro di select contenente la lista di notice types disponibili |
|
* |
|
* @return array|null |
|
*/ |
|
static public function listAvailableNoticeForms(): ?array |
|
{ |
|
|
|
$path = static::STATIC_PATH; |
|
|
|
if (file_exists("{$path}/notice-types/notice-types.json")) { |
|
|
|
$notices = file_get_contents("{$path}/notice-types/notice-types.json"); |
|
$settings = json_decode(file_get_contents("{$path}/../../config/eforms/settings.json"), true); |
|
|
|
if (is_json($notices)) { |
|
|
|
$select = [ |
|
'name' => 'form', |
|
'title' => "Tipologia di avviso", |
|
'attrs' => [ |
|
'rel' => 'S;12;12;A' |
|
], |
|
'options' => [] |
|
]; |
|
|
|
$notices = json_decode($notices, TRUE); |
|
$vocabulary = self::getTranslator(); |
|
foreach ($notices["noticeSubTypes"] as $value) { |
|
|
|
if (DEVELOP_ENV || empty($settings["forms"]) || in_array($value["subTypeId"], $settings["forms"])) { |
|
|
|
$select['options'][$value["subTypeId"]] = "{$value["subTypeId"]}. {$vocabulary[$value["_label"]]}"; |
|
} |
|
} |
|
|
|
return $select; |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
/** |
|
* Carica/salva le impostazioni dell'editor passate dal'utente |
|
* |
|
* @return void |
|
*/ |
|
protected function handleUserSessionSettings() |
|
{ |
|
// ENABLE EFX |
|
$enable_efx = $_SESSION["ted_enable_efx"] ?? true; |
|
if (isset($_GET["efx_mode"])) { |
|
if ($_GET["efx_mode"] !== "0") { |
|
$enable_efx = true; |
|
} else { |
|
$enable_efx = false; |
|
} |
|
$_SESSION["ted_enable_efx"] = $enable_efx; |
|
} |
|
$this->enable_efx_evaluation = $enable_efx; |
|
|
|
// HIDE LABELS |
|
$hide_labels = $_SESSION["ted_hide_labels"] ?? true; |
|
if (isset($_GET["hide_labels"])) { |
|
if ($_GET["hide_labels"] !== "0") { |
|
$hide_labels = true; |
|
} else { |
|
$hide_labels = false; |
|
} |
|
$_SESSION["ted_hide_labels"] = $hide_labels; |
|
} |
|
$this->hide_labels = $hide_labels; |
|
|
|
|
|
if(defined("__CMDS_JOB_EXEC")) { |
|
$canAccessDebug = false; |
|
} else { |
|
$canAccessDebug = ($_SESSION["utente"] ?? false) && $_SESSION["utente"]->isSupportoOrRoot(); |
|
} |
|
|
|
|
|
|
|
// ADVANCED DEBUG |
|
if ($canAccessDebug) { |
|
$advanced_debug = $_SESSION["ted_advanced_debug"] ?? false; |
|
if (isset($_GET["advanced_debug"])) { |
|
if ($_GET["advanced_debug"] !== "0") { |
|
$advanced_debug = true; |
|
} else { |
|
$advanced_debug = false; |
|
} |
|
$_SESSION["ted_advanced_debug"] = $advanced_debug; |
|
} |
|
$this->advanced_debug = $advanced_debug; |
|
|
|
$forced_suggestion = $_SESSION["ted_forced_suggestion"] ?? true; |
|
if (isset($_GET["forced_suggestion"])) { |
|
if ($_GET["forced_suggestion"] !== "1") { |
|
$forced_suggestion = false; |
|
} else { |
|
$forced_suggestion = true; |
|
} |
|
$_SESSION["ted_forced_suggestion"] = $forced_suggestion; |
|
} |
|
|
|
$this->enable_forced_suggestions = $forced_suggestion; |
|
|
|
// Nelle variazioni non possiamo avere suggerimenti forzati onde evitare conflitti |
|
if($this->rectification) { |
|
$this->enable_forced_suggestions = false; |
|
} |
|
$_SESSION["ted_forced_suggestion"] = $this->enable_forced_suggestions ? "1" : "0"; |
|
} |
|
} |
|
/** |
|
* Istanzia la classe partendo da un notice type e l'id della notice |
|
* |
|
* @param String $notice |
|
* @param integer $codice |
|
* @param NuovaPiattaformaAppalti|null $npa |
|
*/ |
|
public function __construct(String $notice, Int $codice = 0, ?NuovaPiattaformaAppalti $npa = null) |
|
{ |
|
// Prevent directory trasversal from notice |
|
$notice = str_replace(".", "", $notice); |
|
$notice = str_replace("/", "", $notice); |
|
global $root, $pdo; |
|
|
|
$this->notice = $notice; |
|
$this->language = strtolower($_SESSION["language"] ?? "it"); |
|
$this->version = $this->__get("version"); |
|
|
|
$this->pdo = $pdo; |
|
|
|
|
|
|
|
$this->init($codice); |
|
if($npa !== null) { |
|
$this->npa = $npa; |
|
} else { |
|
// Recuperiamo la scheda ANAC collegata |
|
$schedaAnacCollegata = NuovaPiattaformaAppalti::fetchSchede( |
|
["s.codice", "s.codice_npa"], |
|
["codice_scheda_guue" => $this->info["codice"]] |
|
,true, false |
|
); |
|
|
|
if(!empty($schedaAnacCollegata)) { |
|
$this->npa = NuovaPiattaformaAppalti::init($schedaAnacCollegata["codice_npa"], $schedaAnacCollegata["codice"], null, null, null, NuovaPiattaformaAppalti::AUTOMATICALLY_GET_SDK_VERSION, $this); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Set version value |
|
* |
|
* @param mixed $name |
|
* @param mixed $value |
|
* @return void |
|
*/ |
|
public function __set($name, $value) |
|
{ |
|
// TODO: Picchierò chiunque decida di usare __get e __set in un futuro progetto. |
|
// Per delle proprietà dinamiche è meglio avere dei getter e setter espliciti. |
|
// - Luca Corigliano |
|
|
|
if ($name == "version") { |
|
|
|
$path = dirname(__DIR__) . DIRECTORY_SEPARATOR . "eforms" . DIRECTORY_SEPARATOR . $value; |
|
if (file_exists($path)) { |
|
|
|
$this->version = $value; |
|
} else { |
|
|
|
$this->version = self::SDK_VERSION; |
|
} |
|
$this->initializeSDK(); |
|
} |
|
} |
|
|
|
/** |
|
* Obtain version varible |
|
* |
|
* @return mixed |
|
*/ |
|
public function __get(String $name) |
|
{ |
|
|
|
if ($name == "version") { |
|
|
|
if (empty($this->version)) { |
|
|
|
$this->version = self::SDK_VERSION; |
|
} |
|
|
|
return $this->version; |
|
} |
|
} |
|
|
|
/** |
|
* Check if all sections have been completed |
|
* |
|
* @return bool |
|
*/ |
|
public function ready(): bool |
|
{ |
|
|
|
return $this->completed(); |
|
} |
|
|
|
/** |
|
* Check if once this section is completed, the form will be ready for submission. |
|
* |
|
* @param string $section |
|
* @param string $subsection |
|
* @return bool |
|
*/ |
|
public function willFormBeReadyAfterSectionCompletion(String $section, String $subsection = null): Bool |
|
{ |
|
// Se è già tutto completo direi proprio di sì |
|
if ($this->completed()) return true; |
|
|
|
foreach ($this->completed_subsections as $key => $value) { |
|
// Ripetibile |
|
if (is_array($value)) { |
|
foreach ($value as $iteration => $complete) { |
|
// Ci sono altre sezioni non complete |
|
if (!$value && $key !== $section) |
|
return false; |
|
} |
|
} |
|
// Non ripetibile |
|
else if (!$value && $key !== $section) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
// Dizionari alternativi |
|
// Nota: |
|
// Questi dizionari vanno mossi in dei JSON legati alla versione SDK |
|
|
|
// Questo sovrascrive a priori |
|
public const OVERRIDE_VOCABULARY = [ |
|
"rule|text|SA-OPP-030-T02-ALL-REV-TIC" => "Il campo non può riferirsi alla distribuzione di ricavi", |
|
]; |
|
// Questo sovrascrive qualora non venisse trovata la voce |
|
public const FALLBACK_VOCABULARY = [ |
|
"group|name|ND-LotAwardWeightCriterionParameter" => "Parametro Pesato", |
|
"group|name|ND-LotAwardFixedCriterionParameter" => "Parametro Fisso", |
|
"group|name|ND-LotAwardThresholdCriterionParameter" => "Parametro di Soglia", |
|
"group|name|ND-LotsGroupAwardWeightCriterionParameter" => "Parametro Pesato", |
|
"group|name|ND-LotsGroupAwardFixedCriterionParameter" => "Parametro Fisso", |
|
"group|name|ND-LotsGroupAwardThresholdCriterionParameter" => "Parametro di Soglia", |
|
"group|name|ND-LotContractAdditionalNature" => "Natura Aggiuntiva", |
|
"group|name|ND-LotEnvironmentalImpactType" => "Riduzione impatto ambientale", |
|
"group|name|ND-LotSocialObjectiveType" => "Obiettivo sociale", |
|
"group|name|ND-LotInnovativeAcquisitionType" => "Obiettivo innovativo", |
|
"group|name|ND-SecondStageCriterionParameter" => "Criteri di secondo stadio", |
|
"group|name|ND-ChangedSection" => "Sezione cambiata" |
|
]; |
|
|
|
/** |
|
* Traduce una voce relativa ad eForms 2 ed applica correzioni per l'usabilità |
|
* |
|
* @param String $key |
|
* @return String|null |
|
*/ |
|
public function translate(String $key): ?String |
|
{ |
|
|
|
$retval = $key; |
|
// Check override dictionary |
|
if (!empty(self::OVERRIDE_VOCABULARY[$key])) { |
|
$retval = self::OVERRIDE_VOCABULARY[$key]; |
|
} |
|
// Check eForms vocabulary |
|
elseif (!empty($this->eforms_translations[$key])) { |
|
$retval = $this->eforms_translations[$key]; |
|
} |
|
// Check fallback dictionary |
|
elseif (!empty(self::FALLBACK_VOCABULARY[$key])) { |
|
$retval = self::FALLBACK_VOCABULARY[$key]; |
|
} else { |
|
if ($this->debug) { |
|
return "Traduzione mancante: \"{$key}\""; |
|
} |
|
return null; |
|
} |
|
|
|
// Handle (d) (t) correctly |
|
if (str_contains($key, "(t)")) { |
|
$retval .= " (Orario)"; |
|
} |
|
// Handle (a) (b) (c) correctly |
|
elseif (str_contains($key, "BT-510(a)")) { |
|
$retval .= " (Linea 1)"; |
|
} elseif (str_contains($key, "BT-510(b)")) { |
|
$retval .= " (Linea 2)"; |
|
} elseif (str_contains($key, "BT-510(c)")) { |
|
$retval .= " (Linea 3)"; |
|
} |
|
return $retval; |
|
} |
|
|
|
/** |
|
* Genera la struttura intera del form, includendo tutto ciò che viene specificato in $all_sections. |
|
* |
|
* @param boolean $metadata Se false, non vengono ritornati i metadati per il rendering |
|
* @param array $all_sections Se specificato la creazione si limita alle sezioni specificate (incluse ripetizioni). Altrimenti genera tutto |
|
* @return array |
|
*/ |
|
public function createFullFormStructure(Bool $metadata = true, array $all_sections = []): array |
|
{ |
|
$this->generateXMLNotice('utf-8', false); |
|
|
|
if (empty($all_sections)) { |
|
$all_sections = $this->sections; |
|
} |
|
|
|
$data_source = $this->info["valori"]["eforms2"] ?? []; |
|
$retVal = []; |
|
foreach ($all_sections as $section) { |
|
$data_source_for_section = $data_source[$section["id"]]["values"] ?? []; |
|
$integration_values_for_section = $data_source[$section["id"]]["integration_values"] ?? []; |
|
$section_elements = $this->fields["content"][$section["index"]]; |
|
$this->current_section = $section_elements; |
|
$this->current_section_id = $section["id"]; |
|
|
|
if ($section["_repeatable"] ?? false) { |
|
foreach ($data_source_for_section as $iteration_index => $iteration_data_source) { |
|
$integration_values = $integration_values_for_section[$iteration_index] ?? []; |
|
$iteration_structure = $this->createSection($section_elements, $metadata, [], $iteration_data_source, $integration_values, (int) $iteration_index); |
|
foreach ($iteration_structure[$section["id"]] as $key => $value) { |
|
$retVal[$section["id"]][$iteration_index][$key] = $value; |
|
} |
|
} |
|
} else { |
|
|
|
$section_structure = $this->createSection($section_elements, $metadata, [], $data_source_for_section, $integration_values_for_section, 0); |
|
foreach ($section_structure[$section["id"]] as $key => $value) { |
|
$retVal[$section["id"]][$key] = $value; |
|
} |
|
} |
|
} |
|
|
|
return $retVal; |
|
} |
|
|
|
/** |
|
* createFormStructure Crea la struttura di un form partendo da una sezione e l'eventuale indice di sottosezione. |
|
* |
|
* @param array $section Sezione da generare |
|
* @param int $subsection_index Indice di sottosezione |
|
* @param bool $metadata Se false, non vengono ritornati i metadati per il rendering |
|
* @param ?array $full_structure Se passato, viene popolato con la struttura intera generata includendo eventuali elementi generati per coerenza EFX |
|
* @return array Struttura del form (modello concettuale) |
|
*/ |
|
public function createFormStructure(array $section, Int $subsection_index, Bool $metadata = true, ?array &$full_structure = null): array |
|
{ |
|
// Generiamo la struttura interna a partire dai dati |
|
// Ottenendo il modello concettuale |
|
// È importante sia intera per ottenere gli indici di ogni elemento ripetibile |
|
|
|
// Generazione intera |
|
if (false) { |
|
$full_structure = $this->createFullFormStructure($metadata); |
|
// Generazione di tutti i ripetibili di sezione (più veloce, forse meno precisa) |
|
} else { |
|
$full_structure = $this->createFullFormStructure($metadata, [$section]); |
|
} |
|
$this->current_subsection = $subsection_index; |
|
// Verifichiamo che sia stata creata la sezione, potrebbe non esserlo |
|
// qualora non sia ancora popolato il JSON sul Database |
|
$section_structure = $full_structure[$section["id"]] ?? []; |
|
if ($section["_repeatable"] ?? false) { |
|
$section_structure = $section_structure[$subsection_index] ?? []; |
|
} |
|
// Se non c'è una stuttura, ne creiamo uno scheletro basato puramente sui metadati |
|
if (empty($section_structure)) { |
|
$elements = $this->fields["content"][$section["index"]]; |
|
if ($elements["id"] == $section["id"]) { |
|
$this->current_section = $elements; |
|
$this->current_section_id = $section["id"]; |
|
|
|
$section_structure = $this->createSection($elements, $metadata, [], [], [], $subsection_index); |
|
} |
|
} |
|
|
|
return $section_structure; |
|
} |
|
|
|
/** |
|
* Crea il modello concettuale/struttura di una singola sezione |
|
* |
|
* @param Array $section La sezione da processare |
|
* @param boolean $metadata Se false, non vengono ritornati i metadati per il rendering |
|
* @param array $prefix Indica la gerarchia attuale della sezione. Viene popolato durante le iterazioni. |
|
* @param array $values Indica i valori attuali da usare per riempire i fields |
|
* @param array $integration_values Indica i valori forzati passati dalle integrazioni |
|
* @param integer $index L'indice attuale di ripetizione/iterazione |
|
* @param boolean $repetition Indica se il rendering attuale è una ripetizione/iterazione |
|
* @param boolean $section_forbidden Quando diventa true, l'itera sezione sarà forbidden |
|
* @param boolean $section_not_mandatory Se null, viene controllato che la sezione ripetibile abbia valori. Quando diventa true decide se tutta la sezione non è mandatory. Quando diventa falsa invece previene ulteriori check in profondità |
|
* @return Array Struttura della sezione (modello concettuale) |
|
*/ |
|
public function createSection(array $section, Bool $metadata = true, array $prefix = [], array $values = [], array $integration_values = [], Int $index = 0, Bool $repetition = false, Bool $section_forbidden = false, $section_not_mandatory = null): array |
|
{ |
|
// Qui vengono salvati i valori di di ritorno |
|
$retval = []; |
|
|
|
// Rintracciamo i valori relativi a questa sezione e teniamo conto della profondità |
|
$depth = 0; |
|
foreach ($prefix as $key) { |
|
$depth++; |
|
if (!empty($values[$key])) $values = $values[$key]; |
|
if (!empty($integration_values[$key])) $integration_values = $integration_values[$key]; |
|
} |
|
|
|
|
|
switch ($section["contentType"] ?? null) { |
|
|
|
// Gruppi di campi |
|
case 'group': |
|
if (!empty($section["content"])) { |
|
$repetitions = 1; |
|
|
|
// Regole specifiche per sezioni ripetibili |
|
if ($section["_repeatable"] ?? false) { |
|
// Se l'identifier di una ripetibile è forbidden, vuol dire che è ripetibile anche tutta la sua sezione |
|
if (isset($section["_identifierFieldId"])) { |
|
$identifierField = $this->fields_metadata[$section["_identifierFieldId"]] ?? null; |
|
if ($identifierField !== null) { |
|
$section_forbidden_property = $this->checkFieldProperty($identifierField, "forbidden", 0); |
|
|
|
if ($section_forbidden_property["value"] === true) { |
|
$section_forbidden = true; |
|
} |
|
} |
|
} |
|
// Teniamo conto della quantità di ripetizioni |
|
$groups = $this->get($values, $section["id"]); |
|
if (!empty($groups)) { |
|
$repetitions = count($groups); |
|
} |
|
} |
|
|
|
// Id della sezione ("GR-Ecc") |
|
$id = $section["id"]; |
|
|
|
// Per ogni ripetizione |
|
for ($i = 0; $i < $repetitions; $i++) { |
|
|
|
// Se una sezione è vuota, togliamo i vincoli di obbligatorietà dall'utente in modo da |
|
// non obbligarlo a compilare una sezione che non serve |
|
if(self::EXCLUDED_FROM_AUTO_NOT_MANDATORY_SECTIONS[$section["id"]] ?? false) { |
|
$section_not_mandatory = false; |
|
} |
|
if ($section_not_mandatory === null && ($section["_repeatable"] ?? false)) { |
|
|
|
$section_not_mandatory = $this->countValues($values, $section["id"], $i) <= 0; |
|
} |
|
|
|
// New section data è un puntatore a questa sezione all'interno del valore di ritorno. |
|
// Viene implementato in questo modo per poter gestire sveltamente le ripetizioni |
|
$new_section_data = &$retval[$id]; |
|
$new_prefix = $prefix; |
|
// Creazione del nuovo prefisso |
|
if (empty($prefix)) { |
|
// Root, non può essere ripetibile (e non deve.) |
|
$new_prefix[] = $id; |
|
} else { |
|
$new_prefix[] = $id; |
|
|
|
// Se è ripetibile, puntiamo new_section_data all'iterazione corrente |
|
if (($section["_repeatable"] ?? false)) { |
|
$new_section_data = &$new_section_data[$i]; |
|
// Eccezione per add2guue, forziamo la ripetizione attuale |
|
if ($repetition) { |
|
$i = $index; |
|
} |
|
// Aggiungiamo l'id della ripetizione al prefisso |
|
$new_prefix[] = $i; |
|
} |
|
} |
|
|
|
// Per tutti i contenuti di questa sezione, ci chiamiamo ricorsivamente |
|
foreach ($section["content"] as $content) { |
|
foreach ($this->createSection($content, $metadata, $new_prefix, $values, $integration_values, $i, false, $section_forbidden, $section_not_mandatory) as $key => $val) { |
|
$new_section_data[$key] = $val; |
|
} |
|
} |
|
|
|
// Ai fini del codice di rendering, ci salviamo anche dei metadati di sezione |
|
if ($metadata) { |
|
$new_section_data["__meta"]["display_type"] = $section["displayType"]; |
|
$new_section_data["__meta"]["repeatable"] = $section["_repeatable"] ?? false; |
|
$new_section_data["__meta"]["label"] = $section["_label"] ?? ""; |
|
$new_section_data["__meta"]["id"] = $section["id"] ?? ""; |
|
$new_section_data["__meta"]["unique_identifier"] = implode("-", $new_prefix); |
|
$new_section_data["__meta"]["iteration"] = $i; |
|
$new_section_data["__meta"]["total_iterations"] = $repetitions; |
|
$new_section_data["__meta"]["section_object"] = $section; |
|
$new_section_data["__meta"]["prefix"] = $prefix; |
|
$new_section_data["__meta"]["hasAnyValue"] = $this->hasAnyValue($values, $section["id"], $i); |
|
$new_section_data["__meta"]["preventDelete"] = $this->countForcedIntegrationValues($integration_values, $section["id"], $i) > 0; |
|
$identifierFieldID = $section["_identifierFieldId"] ?? null; |
|
$captionFieldID = $section["_captionFieldId"] ?? null; |
|
|
|
$tabnameProvider = []; |
|
|
|
if ($captionFieldID) { |
|
$tabnameProvider[] = "#" . $captionFieldID; |
|
} |
|
if ($identifierFieldID) { |
|
$tabnameProvider[] = "#" . $identifierFieldID; |
|
} |
|
if (!empty($tabnameProvider)) { |
|
$new_section_data["__meta"]["tabNameProvider"] = implode("|", $tabnameProvider); |
|
} |
|
} |
|
} |
|
} |
|
break; |
|
// Campi |
|
case 'field': |
|
// Prendiamo le info di questo field da fields.json |
|
$field = $this->fields_metadata[$section["id"]]; |
|
|
|
// Andiamo a salvare (ed ottenere) l'indice attuale del campo. |
|
// Questo è utile qualora lo stesso campo si ripeta più volte per poi associarlo ai suoi risultati EFX |
|
if (!isset($this->field_counters[$section['id']])) |
|
$this->field_counters[$section['id']] = -1; |
|
$current_index = ++$this->field_counters[$section['id']]; |
|
|
|
|
|
// Proprietà da verificare |
|
|
|
// TODO Nota: |
|
// Repeatable non è considerato in quanto viene usato solo da "BT-702(b)-notice", che è la lingua della notice, che è sempre una per noi. |
|
// In futuro sarà possile che sia necessario implementarlo. |
|
|
|
// Assert invece viene considerato in un altro punto del codice |
|
$properties = [ |
|
"forbidden", |
|
"mandatory", |
|
]; |
|
|
|
if ($this->advanced_debug) { |
|
// Teniamo traccia di info utili per il debug |
|
$has_efx = []; |
|
$error_state = []; |
|
} |
|
|
|
// Per ogni proprietà da considerare |
|
foreach ($properties as $property) { |
|
$$property = ["value" => false, "severity" => ""]; // Questo assegnerà i valori di default alle variabili $forbidden, $mandatory |
|
|
|
// Se l'intera sezione è forbidden, siamo forbidden a priori |
|
if ($section_forbidden === true && $property === "forbidden") { |
|
$$property["value"] = true; |
|
continue; // Le altre proprietà sono irrilevanti se siamo forbidden |
|
} |
|
// Se l'intera sezione è non-obbligatoria, inutile controllare |
|
if ($section_not_mandatory === true && $property === "mandatory") { |
|
$$property["value"] = false; |
|
continue; |
|
} |
|
|
|
|
|
if (!empty($field[$property])) { |
|
|
|
// Controlliamo i campi, considerando anche i constraint |
|
$$property = $this->checkFieldProperty($field, $property, $current_index); |
|
|
|
// In debug lanciamo un eccezione se qualcosa va storto |
|
if ($this->debug) { |
|
if ($$property["value"] === null) { |
|
throw new Exception("Invalid value for {$property}"); |
|
} |
|
} |
|
|
|
// Salva valori di debug |
|
if ($this->advanced_debug) { |
|
if ($$property["has_efx"] === true) { |
|
$has_efx["any"] = true; |
|
$has_efx[$property] = true; |
|
} |
|
if ($$property["error_state"] === true) { |
|
$error_state["any"] = true; |
|
$error_state[$property] = true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// WORKAROUND: Se fallisce EFX per forbidden in mancanza di contesto, faccio override manuale su false e |
|
// rendo non obbligatorio |
|
if ($forbidden["error_state"] ?? false) { |
|
$forbidden["value"] = false; |
|
$mandatory["value"] = false; |
|
} |
|
|
|
|
|
// Tiriamo fuori il valore di questo field |
|
$retreived_value = $this->retrieveValue($values, $section["id"]); |
|
|
|
// Override per input, scaricati alla fine del metodo |
|
$input_overrides = []; |
|
if($this->info["stato"] === "BOZZA") { |
|
if (($integration_value = ($integration_values[$section["id"]] ?? false))) { |
|
if ($this->enable_forced_suggestions && $integration_value["forced"]) { |
|
$retreived_value = $integration_value["value"]; |
|
$input_overrides["attrs"]["readonly"] = 1; |
|
$input_overrides["title_before"] = "🪄 " ; |
|
} else { |
|
if (empty($retreived_value)) { |
|
$input_overrides["title_before"] = "💡 "; |
|
$retreived_value = $integration_value["value"]; |
|
} |
|
if(!is_array($integration_value["value"])) { |
|
$input_overrides["attrs"]["suggested_val"] = "{$integration_value["value"]}"; |
|
|
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
// Determiniamo che il valore sia esistente |
|
$has_retreived_value = !empty($retreived_value); |
|
if ($has_retreived_value && isset($retreived_value["value"])) { |
|
$has_retreived_value = !empty($retreived_value["value"]); |
|
} |
|
|
|
|
|
|
|
// Gestione di un campo che ha la possibilità di avere un Unpublished |
|
if (!empty($field["privacy"])) { |
|
// Otteniamo il nome del primo dei campi unpublished |
|
// es. BT-195(BT-156)-NoticeResult |
|
$privacyProviderFieldId = $field["privacy"]["unpublishedFieldId"]; |
|
// Otteniamo ora un ID generico che possiamo poi usare in futuro dagli unpublish stessi |
|
// per poter ricavare le info che stiamo salvando ora |
|
|
|
// es, (BT-156)-NoticeResult |
|
$genericPrivacyProviderId = substr($privacyProviderFieldId, strpos($privacyProviderFieldId, "(")); |
|
|
|
// Se c'è già (caso (a), (b)), aggiorniamo has_value nel caso fossimo popolati |
|
if ($has_retreived_value && isset($this->unpublish_temp_data[$genericPrivacyProviderId[$current_index]])) { |
|
$this->unpublish_temp_data[$genericPrivacyProviderId][$current_index]["has_value"] = true; |
|
} else { |
|
// Altrimenti creiamo i dati temporanei |
|
$this->unpublish_temp_data[$genericPrivacyProviderId][$current_index] = [ |
|
"targetId" => $field["id"], |
|
"has_value" => $has_retreived_value, |
|
"mandatory" => $mandatory["value"], |
|
"forbidden" => $forbidden["value"], |
|
"code" => $field["privacy"]["code"], |
|
]; |
|
} |
|
} |
|
|
|
if (self::UNPUBLISH_FIELDS[$field["btId"] ?? ""] ?? false) { |
|
// Verifichiamo se è parzialmente o completamente compilato |
|
$privacy_field_fully_compiled = false; |
|
$privacy_field_partially_compiled = false; |
|
$privacy_field_compiled_count = 0; |
|
foreach (self::UNPUBLISH_FIELDS as $btId => $dummy) { |
|
if ($btId === "BT-195") continue; // Non ci interessa di BT-195 |
|
// Verifichiamo le key di values |
|
foreach ($values as $key => $value) { |
|
|
|
// Match di BT-196(BT-XYZ)-AAAAA a BT-196 ecc |
|
if (str_starts_with($key, $btId)) { |
|
if (!empty($value)) { |
|
$privacy_field_compiled_count++; |
|
} |
|
} |
|
} |
|
$privacy_field_fully_compiled = $privacy_field_compiled_count >= (count(self::UNPUBLISH_FIELDS) - 1); |
|
$privacy_field_partially_compiled = $privacy_field_compiled_count > 0; |
|
} |
|
|
|
// Otteniamo i dati temporanei salvati in precedenza |
|
$genericPrivacyProviderId = substr($field["id"], strpos($field["id"], "(")); |
|
$tempUnpublishData = $this->unpublish_temp_data[$genericPrivacyProviderId][$current_index]; |
|
|
|
// Se il target è popolato o forbidden, tutto diventa forbidden |
|
if ($tempUnpublishData["forbidden"]) { |
|
$forbidden["value"] = true; |
|
} else { |
|
// Per il campo che seleziona l'elemento da non pubblicare |
|
// ce lo andiamo a pescare automaticamente dalla sua codelist in base |
|
// al campo al quale si riferisce. |
|
|
|
if ($field["btId"] === "BT-195") { |
|
if (!$privacy_field_fully_compiled) { |
|
$forbidden["value"] = true; |
|
} |
|
$input_overrides["type"] = "hidden_but_better"; |
|
$input_overrides["attrs"]["readonly"] = "1"; |
|
$input_overrides["value"] = $input_overrides["val"] = $forbidden["value"] ? "" : $tempUnpublishData["code"]; |
|
} |
|
// Se il campo di sorgente è obbligatorio o questa parte è almeno parzialmente compilata, tutto diventa obbligatorio |
|
if ($privacy_field_partially_compiled) { |
|
$mandatory["value"] = true; |
|
} else { |
|
$mandatory["value"] = false; |
|
} |
|
} |
|
} |
|
|
|
if (array_key_exists($field["btId"], self::MANDATORY_OVERRIDE)) { |
|
$mandatory["value"] = self::MANDATORY_OVERRIDE[$field["btId"]]; |
|
} |
|
$rel = ["N", "0", "0", "A"]; |
|
if (!empty($field["maxLength"])) { |
|
$rel[2] = $field["maxLength"]; |
|
} |
|
if ($mandatory["value"] === true) { |
|
$rel[0] = "S"; |
|
$rel[1] = "1"; |
|
} |
|
|
|
// Valori di base dell'input |
|
$input = [ |
|
"title" => $this->translate($section["_label"]), |
|
"required" => $rel[0] == "S" ? true : false, |
|
"mandatory" => $mandatory["value"] === true, |
|
"forbidden" => $forbidden["value"] === true, |
|
"name" => "eforms2[{$section["id"]}]", |
|
"rel" => $rel, |
|
"type" => null, |
|
"help" => "", |
|
"val" => $retreived_value, |
|
"attrs" => [ |
|
"data-internalname" => $field["id"] ?? "", |
|
] |
|
]; |
|
if ($forbidden["value"] === false && $has_retreived_value) { |
|
$assert = $this->checkFieldProperty($field, "assert", $current_index); |
|
if ($assert["value"] === false && $assert["error_state"] === false) { |
|
$input["assert_fail"] = $this->translate($assert["message"]); |
|
} |
|
} |
|
|
|
// Forbidden |
|
if ($forbidden["value"] === true) { |
|
$input["attrs"]["disabled"] = 1; |
|
} else { |
|
unset($input["attrs"]["disabled"]); |
|
} |
|
// Full name |
|
if (!empty($prefix)) { |
|
array_shift($prefix); |
|
if (empty($prefix)) { |
|
$input["name"] = "eforms2[{$field["id"]}]"; |
|
} else { |
|
$input["name"] = "eforms2[" . implode("][", $prefix) . "][{$field["id"]}]"; |
|
} |
|
} |
|
// Debug data |
|
if ($this->advanced_debug) { |
|
$input["attrs"]["has_efx"] = str_replace("\"", "'", json_encode($has_efx)); |
|
$input["attrs"]["error_state"] = str_replace("\"", "'", json_encode($error_state)); |
|
$input["attrs"]["current_index"] = $current_index; |
|
$input["attrs"]["section"] = $section["id"]; |
|
} |
|
//$input["title"] .= " ({$field["id"]})"; |
|
|
|
// Gestiamo CPV e NUTS |
|
if (!empty($field["codeList"])) { |
|
$codelist_id = $field["codeList"]["value"]["id"] ?? ""; |
|
if (str_contains($codelist_id, "nuts")) { |
|
$input["type"] = "nuts"; |
|
} elseif ($codelist_id == "cpv") { |
|
$input["type"] = "cpv"; |
|
} |
|
} |
|
|
|
if ($input["type"] === null) { |
|
$input["type"] = $this->getDisplayTypeFor($section["displayType"], $field); |
|
} |
|
|
|
// Fix for input overrides |
|
if (!empty($input_overrides["type"])) { |
|
$input["type"] = $input_overrides["type"]; |
|
} |
|
|
|
if ($input["type"] === "text" && ($field["pattern"] ?? false)) { |
|
$input["attrs"]["data-regex"] = $field["pattern"]["value"]; |
|
} |
|
|
|
if ($input["type"] === "date") { |
|
if ($input["val"]) { |
|
$input["val"] = self::FixDate($input["val"], "d/m/Y"); |
|
} |
|
} else if ($input["type"] === "radio") { |
|
$input["options"] = ["true" => __guue("Si"), "false" => __guue("No")]; |
|
} else if ($input["type"] == "cpv") { |
|
// Forse è per questo che si rompono i CPV |
|
$input["attrs"]["class"] = "row"; |
|
} |
|
|
|
if (in_array($input["type"], ["select", "radio"]) && !empty($field["codeList"])) { |
|
if (in_array($input["type"], ["select", "radio"]) && !empty($field["codeList"])) { |
|
$input["options"] = $this->getAvailableCodeListFor($field["codeList"]["value"]["id"]); |
|
} |
|
if (!empty($this->codelist[str_replace("eforms-", "", $field["codeList"]["value"]["id"]) . "-" . $this->notice])) { |
|
$input["options"] = $this->getAvailableCodeListFor($this->codelist[str_replace("eforms-", "", $field["codeList"]["value"]["id"]) . "-" . $this->notice]["id"]); |
|
} else { |
|
$input["options"] = $this->getAvailableCodeListFor($field["codeList"]["value"]["id"]); |
|
} |
|
} |
|
// Workaround per durata |
|
if ($section["id"] == "BT-538-Lot") { |
|
$input["options"] = ["__SPECIFY" => __guue("Come specificato")] + $input["options"]; |
|
} |
|
|
|
if ($field["type"] == "id-ref" && !empty($field["idSchemes"])) { |
|
$input["attrs"]["readonly"] = 1; |
|
foreach ($field["idSchemes"] as $id) { |
|
|
|
if (!empty($this->info["valori"]["settings"]["scheme"][$id])) { |
|
|
|
$settings = $this->info["valori"]["settings"]["scheme"][$id]; |
|
$ids = self::get($this->info["valori"], "eforms2.{$settings["xpath"]}"); |
|
unset($input["attrs"]["readonly"]); |
|
$input["type"] = "select"; |
|
if (!empty($ids)) { |
|
if (!is_array($ids)) $ids = [$ids]; |
|
|
|
//$input["attrs"]["multiple"] = ""; |
|
if (is_array($ids[0])) { |
|
foreach ($ids as $list) { |
|
foreach ($list as $identifier) { |
|
$input["options"][$identifier] = $identifier; |
|
} |
|
} |
|
} else { |
|
foreach ($ids as $identifier) { |
|
$input["options"][$identifier] = $identifier; |
|
} |
|
} |
|
} else { |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Campi di riferimento a ID di ripetibili |
|
if (in_array($field["type"], ["id", "id-ref"]) && !empty($field["idSchemes"])) { |
|
|
|
// Tentativo di popolazione automatica dei nomi dei ripetibili |
|
|
|
// Per ogni idSchema |
|
foreach ($field["idSchemes"] as $idSchema) { |
|
// Cerchiamo il nodo di riferimento |
|
$node = self::SCHEMA_REFERENCES[$idSchema] ?? null; |
|
|
|
if ($node) { |
|
$node_metadata = $this->xml_structure[$node] ?? null; |
|
if ($node_metadata) { |
|
// Cerchiamo l'id dei campi che lo conterranno |
|
$captionFieldId = $node_metadata["captionFieldId"]; |
|
$identifierFieldId = $node_metadata["identifierFieldId"]; |
|
// Peschiamo i campi in se |
|
$captionField = $this->fields_metadata[$captionFieldId] ?? null; |
|
$identifierField = $this->fields_metadata[$identifierFieldId] ?? null; |
|
|
|
if ($captionField && $identifierField) { |
|
// Cerchiamoli tramite XPath |
|
$captions = $this->gen_query_provider->runQuery($captionField["xpathAbsolute"]); |
|
$identifiers = $this->gen_query_provider->runQuery($identifierField["xpathAbsolute"]); |
|
|
|
// Verifichiamo che non siano spaiati |
|
if (count($captions) === count($identifiers)) { |
|
foreach ($captions as $index => $caption_element) { |
|
// Otteniamo i valori |
|
$caption = $caption_element->nodeValue; |
|
$identifier = $identifiers[$index]->nodeValue; |
|
// Buttiamoli nel select |
|
$input["options"][$identifier] = "$caption ($identifier)"; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Cerchiamo di creare un link di aiuto per l'utente |
|
$schema_ids = is_array($field["idSchemes"]) ? $field["idSchemes"] : explode(",", $field["idSchemes"]); |
|
|
|
$schema_id_link = ""; |
|
|
|
// Per ogni schema_id (ORG, TPO, LOT, GPO) |
|
$schema_id_link_data = []; |
|
foreach ($schema_ids as $schema_id) { |
|
// Vediamo se abbiamo un riferimento alla sezione |
|
if ($section_data = self::SCHEMA_ID_TO_SECTION[$schema_id] ?? false) { |
|
$schema_id_link_data[$section_data["section"]] = $section_data["label"]; |
|
} else { |
|
$schema_id_link_data[$schema_id] = ""; |
|
} |
|
} |
|
// Una volta raggruppati creiamo i link |
|
$i = 0; |
|
foreach ($schema_id_link_data as $schema_section => $schema_label) { |
|
if (empty($schema_label)) { |
|
$schema_id_link .= $schema_section; |
|
} else { |
|
$schema_id_link .= "<a href='#' onclick=\"switchSection('{$schema_section}', 0)\">{$schema_label}</a>"; |
|
} |
|
|
|
if ($i++ < count($schema_id_link_data) - 1) { |
|
$schema_id_link .= ", "; |
|
} |
|
} |
|
|
|
$input["help"] = __guue("Per completare queste informazioni è necessario compilare la relativa sezione: ") . "$schema_id_link" . " (<code class='text-dark'>{$schema_id}</code>-000X)"; |
|
} |
|
|
|
if ((in_array($field["type"], ["id"])) && !empty($field["idScheme"]) && is_string($field["idScheme"])) { |
|
$input["attrs"]["readonly"] = 1; |
|
$input["help"] = $input["help"] = __guue("Non è necessario compilare questo campo, sarà popolato automaticamente"); |
|
if ($this->current_section) { |
|
$dot_path_array = []; |
|
|
|
$dot_path_array[] = $this->current_section["id"]; |
|
$dot_path_array[] = "values"; |
|
if ($this->current_section["_repeatable"] ?? false) { |
|
$dot_path_array[] = "*"; |
|
} |
|
if (!empty($prefix)) { |
|
foreach ($prefix as $i => $p) { |
|
$dot_path_array[] = is_numeric($p) ? "*" : $p; |
|
} |
|
} |
|
|
|
$dot_path_array[] = $section["id"]; |
|
$dot_path = implode(".", $dot_path_array); |
|
|
|
$settings = []; |
|
$settings["xpath"] = $dot_path; |
|
|
|
$settings["section"] = $this->current_section["id"]; |
|
|
|
if (!empty($field["pattern"]["value"])) { |
|
|
|
$settings["regex"] = $field["pattern"]["value"]; |
|
$settings["schemeName"] = $field["schemeName"]; |
|
|
|
$settings["pad"]["length"] = 4; |
|
$settings["pad"]["string"] = "0"; |
|
$settings["pad"]["type"] = STR_PAD_LEFT; |
|
|
|
// Get prefix from supplied regex |
|
// eg: ^ORG-\d{4}$ -> ORG- |
|
$regex = $settings["regex"]; |
|
if (preg_match('/\^(\w+)\-.+/', $regex)) { |
|
$settings["pad"]["prefix"] = substr($regex, 1, strpos($regex, "-", 1)); |
|
} |
|
} |
|
$_SESSION["notice" . $this->notice]["settings"]["scheme"][$field["idScheme"]] = $this->info["valori"]["settings"]["scheme"][$field["idScheme"]] = $settings; |
|
if ($forbidden["value"] === true) { |
|
self::unset($this->info["valori"], $settings["xpath"]); |
|
} |
|
} |
|
} |
|
|
|
$value = $this->info["valori"][$section["id"]] ?? null; |
|
if (empty($value)) { |
|
$value = (string) $this->getSuggetion($section["id"]); |
|
} |
|
if (!empty($value)) { |
|
$field["val"] = $value; |
|
} |
|
|
|
if($input["type"] == "value") { |
|
$input["rel"][3] = "2D"; |
|
} |
|
if (!empty($input_overrides)) { |
|
if(isset($input_overrides["title_before"])) { |
|
$input["title"] = $input_overrides["title_before"] . $input["title"]; |
|
} |
|
$input = array_merge_recursive_distinct($input, $input_overrides); |
|
} |
|
|
|
$retval[$input["name"]] = $input; |
|
break; |
|
|
|
default: |
|
throw new Exception("Invalid type {$section['contentType']}"); |
|
break; |
|
} |
|
|
|
return $retval; |
|
} |
|
|
|
/** |
|
* Va a verificare che una sezione abbia qualunque valore |
|
* |
|
* @param array $values |
|
* @param string $section_id |
|
* @return boolean |
|
*/ |
|
private function hasAnyValue(array $values, string $section_id, int $iteration = -1): bool |
|
{ |
|
return $this->countValues($values, $section_id, $iteration) >= 1; |
|
} |
|
|
|
private function countForcedIntegrationValues(array $values, string $section_id, int $iteration = -1) |
|
{ |
|
if(!$this->enable_forced_suggestions) return 0; |
|
$value = $values[$section_id] ?? null; |
|
if (empty($value)) { |
|
foreach ($values as $v) { |
|
if (!empty($v[$section_id])) { |
|
$value = $v[$section_id]; |
|
break; |
|
} |
|
} |
|
} |
|
if ($iteration > -1) { |
|
if (!is_array($value)) return 0; |
|
$value = $value[$iteration] ?? null; |
|
} |
|
if (!is_array($value)) { |
|
return 0; |
|
} |
|
|
|
$count = 0; |
|
array_walk_recursive($value, function ($item, $key) use (&$count, $section_id, $iteration) { |
|
// Non contiamo currency o tipi di durata |
|
if ($key === "forced" && $item === true) $count++; |
|
}); |
|
return $count; |
|
} |
|
|
|
/** |
|
* Va a contare il numero di valori di una sezione |
|
* |
|
* @param array $values |
|
* @param string $section_id |
|
* @param int $iteration |
|
* @return int |
|
*/ |
|
private function countValues(array $values, string $section_id, int $iteration = -1): int |
|
{ |
|
|
|
$value = $values[$section_id] ?? null; |
|
if (empty($value)) { |
|
foreach ($values as $v) { |
|
if (!empty($v[$section_id])) { |
|
$value = $v[$section_id]; |
|
break; |
|
} |
|
} |
|
} |
|
if ($iteration > -1) { |
|
if (!is_array($value)) return 0; |
|
$value = $value[$iteration] ?? null; |
|
} |
|
if (!is_array($value)) { |
|
return empty($value) ? 0 : 1; |
|
} |
|
|
|
$count = 0; |
|
array_walk_recursive($value, function ($item, $key) use (&$count, $section_id, $iteration) { |
|
// Non contiamo currency o tipi di durata |
|
if ($key === "type") return; |
|
// Non contiamo elementi vuoti |
|
if (empty($item)) return; |
|
// Non contiamo fieldIdentifier |
|
if (preg_match('/[A-Z]{3}-[0-9]{4}/', $item) === 1) return; |
|
|
|
$count++; |
|
}); |
|
return $count; |
|
} |
|
|
|
/** |
|
* Va a cercarsi i valori di una sezione ricorsivamente |
|
* |
|
* @param array $values |
|
* @param string $section_id |
|
* @return mixed |
|
*/ |
|
private function retrieveValue(array $values, string $section_id) |
|
{ |
|
$value = $values[$section_id] ?? null; |
|
if (empty($value)) { |
|
foreach ($values as $v) { |
|
if (!empty($v[$section_id])) { |
|
return $v[$section_id]; |
|
} |
|
} |
|
} |
|
return $value; |
|
} |
|
|
|
/** |
|
* Nasconde/mostra la sezione GR-Change quando necessario |
|
* |
|
* @return void |
|
*/ |
|
public function manageChangeSection(): void |
|
{ |
|
$info = json_decode($this->info["info"] ?? "{}"); |
|
if (!($info->rectification ?? false)) { |
|
$this->sections = array_values(array_filter($this->sections, function ($section) { |
|
return $section["id"] != "GR-Change"; |
|
})); |
|
} |
|
} |
|
|
|
/** |
|
* Verifica che la sezione attuale sia completa. |
|
* |
|
* @param string $section_name |
|
* @param integer|null $subsection_index |
|
* @return boolean |
|
*/ |
|
public function isSectionComplete(string $section_name, ?int $subsection_index = null): bool |
|
{ |
|
|
|
// Forziamo una chiamata a completed se non è popolata l'origine dati |
|
if (empty($this->completed_subsections)) |
|
$this->completed(); |
|
|
|
$completed = $this->completed_subsections[$section_name] ?? null; |
|
// Nessuna entry vuol dire non completo |
|
if (empty($completed)) { |
|
return false; |
|
} |
|
|
|
return $subsection_index === null ? |
|
($this->completed_subsections[$section_name] ?? false) : ($this->completed_subsections[$section_name][$subsection_index] ?? false); |
|
} |
|
|
|
/** |
|
* Verify whether all sections have been completed. |
|
* |
|
* @return Bool |
|
*/ |
|
public function completed(): Bool |
|
{ |
|
|
|
$this->manageChangeSection(); |
|
|
|
$subsections = array_keys($this->getRepeatableSections()); |
|
|
|
$retval = true; |
|
|
|
foreach ($this->sections as $section) { |
|
|
|
if (in_array($section["id"], $subsections)) { |
|
|
|
$repetitions = isset($this->info["valori"]["settings"]["reps"][$section["id"]]) ? $this->info["valori"]["settings"]["reps"][$section["id"]] : 1; |
|
if ($repetitions > 0) { |
|
for ($i = 0; $i < $repetitions; $i++) { |
|
$draft = self::get($this->info, "valori.eforms2.{$section['id']}.info.{$i}.draft", true); |
|
$this->completed_subsections[$section["id"]][$i] = !$draft; |
|
if ($retval && $draft) { |
|
$retval = false; |
|
} |
|
} |
|
} |
|
} else { |
|
$draft = self::get($this->info, "valori.eforms2.{$section['id']}.info.draft", true); |
|
$this->completed_subsections[$section["id"]] = !$draft; |
|
if ($retval && $draft) { |
|
$retval = false; |
|
} |
|
} |
|
} |
|
return $retval; |
|
} |
|
|
|
/** |
|
* Get all repeteable sections |
|
* |
|
* @return Array |
|
*/ |
|
public function getRepeatableSections(): ?array |
|
{ |
|
|
|
$sections = $this->sections; |
|
foreach ($sections as $index => &$section) { |
|
|
|
if (empty($section["_repeatable"])) { |
|
unset($sections[$index]); |
|
continue; |
|
} |
|
|
|
$section["rel"] = "S;0;0;N"; |
|
$section["label"] = $this->translate($section["_label"]); |
|
$section["value"] = $this->info["valori"]["settings"]["reps"][$section["id"]] ?? 0; |
|
$section["reps_locked"] = $this->info["valori"]["settings"]["reps_locked"][$section["id"]] ?? false; |
|
|
|
if ($section["id"] == "GR-Lot") { |
|
|
|
$section["rel"] = "S;0;0;N"; |
|
$section["value"] = $this->info["valori"]["settings"]["reps"][$section["id"]] ?? 1; |
|
$section["label"] = __guue("Numero di lotti"); |
|
$section["reps_locked"] = $this->info["valori"]["settings"]["reps_locked"][$section["id"]] ?? false; |
|
} |
|
} |
|
|
|
return array_column($sections, null, "id"); |
|
} |
|
|
|
//TODO: Descrivere il metodo |
|
/** |
|
* getLastNumber |
|
* |
|
* @return int |
|
*/ |
|
public function getLastNumber(): int |
|
{ |
|
$number = 0; |
|
$latest = $this->pdo->go("SELECT MAX(numero) FROM b_guue WHERE anno = :anno", [":anno" => date('Y')])->fetch(PDO::FETCH_COLUMN, 0); |
|
if ($latest > 0) { |
|
$number = $latest; |
|
} |
|
return $number; |
|
} |
|
|
|
/** |
|
* Stores data for future use |
|
* |
|
* @return Array |
|
*/ |
|
public function save(array $data = []): ?array |
|
{ |
|
|
|
// Verifica blocco concorrenza |
|
$current_user = $_SESSION["utente"]->codice ?? null; |
|
if ($current_user !== null && ($this->info["codice"] ?? 0) > 0) { |
|
|
|
$bind = [ |
|
':codice' => $this->info["codice"], |
|
':codice_utente' => $current_user, |
|
]; |
|
$sql = <<<QUER |
|
SELECT DATE_ADD(data_aggiornamento, INTERVAL 60 SECOND) - NOW() AS delta, utente_modifica FROM b_guue |
|
WHERE |
|
codice = :codice AND |
|
utente_modifica > 0 AND |
|
utente_modifica != :codice_utente |
|
HAVING |
|
delta > 0 |
|
LIMIT 1 |
|
QUER; |
|
$ris = $this->pdo->go($sql, $bind); |
|
|
|
if ($ris->rowCount() == 1) { |
|
$lock_data = $ris->fetch(PDO::FETCH_ASSOC); |
|
return ["concurrence_lock" => $lock_data]; |
|
} |
|
} |
|
$record = $this->info; |
|
if(empty($record["notice_id"])) { |
|
$record["notice_id"] = uuidgen_guue(); |
|
} |
|
if(empty($record["notice_version"])) { |
|
$record["notice_version"] = 1; |
|
} |
|
|
|
|
|
$data["eforms2"] = $this->organizeRepeteableGroups($data["eforms2"] ?? []); |
|
|
|
$sections = array_column($this->sections, null, "id"); |
|
|
|
// Salviamoci i vecchi dati di draft |
|
$info = $record["valori"]["eforms2"][$data["current_section"]]["info"] ?? []; |
|
|
|
if ($sections[$data["current_section"]]["_repeatable"]) { |
|
$record["valori"]["eforms2"][$data["current_section"]] = []; |
|
$values = self::get($this->info["valori"], "eforms2.{$data["current_section"]}.values"); |
|
$values[$data["section-repetition"]] = $data["eforms2"]; |
|
ksort($values); |
|
} else { |
|
|
|
$values = $data["eforms2"]; |
|
} |
|
|
|
//dd($draft); |
|
self::set($record["valori"], "eforms2.{$data["current_section"]}.values", $values); |
|
$record = $this->setSchemaIDForeachRepeteableItems($record); |
|
|
|
if (!empty($data["reps"])) { |
|
$record["valori"]["settings"]["reps"] = $data["reps"]; |
|
|
|
foreach ($data["reps"] as $repetition_id => $repetitions) { |
|
if ($repetitions < 1 || empty($record["valori"]["eforms2"][$repetition_id])) { |
|
$record["valori"]["eforms2"][$repetition_id]["values"] = []; |
|
} else if ((int) $repetitions < count($record["valori"]["eforms2"][$repetition_id]["values"])) { |
|
$record["valori"]["eforms2"][$repetition_id]["values"] = array_slice($record["valori"]["eforms2"][$repetition_id]["values"], 0, (int) $repetitions); |
|
} |
|
|
|
//$record["valori"]["eforms2"][$data["current_section"]]["info"][$repetition_id] = |
|
} |
|
} |
|
|
|
if ($sections[$data["current_section"]]["_repeatable"]) { // Ripetibile |
|
$info[$data["current_subsection_index"]] = ["draft" => $data["draft"] == "S"]; |
|
} else { // Non ripetibile |
|
$info = ["draft" => $data["draft"] == "S"]; |
|
} |
|
// Aggiorniamo info |
|
$record["valori"]["eforms2"][$data["current_section"]]["info"] = $info; |
|
|
|
$record["valori"]["eforms2"] = array_filter($record["valori"]["eforms2"], fn ($key) => !empty($key), ARRAY_FILTER_USE_KEY); |
|
|
|
|
|
|
|
$record["titolo"] = $data["titolo"]; |
|
$record["valori"] = json_encode($record["valori"], JSON_INVALID_UTF8_IGNORE); |
|
$record["data_aggiornamento"] = date('d/m/Y H:i:s'); |
|
$record["data_trasmissione"] = null; |
|
$record["modulo"] = $data["modulo"] ?? null; |
|
$record["codice_elemento"] = $data["codice_elemento"] ?? 0; |
|
$record["lotto_riferimento"] = $data["lotto_riferimento"] ?? null; |
|
$record["stato"] = TedEsender::getStati()[0]["title"]; |
|
if (!$_SESSION["utente"]->fromHere()) { |
|
$record["codice_ente"] = $_SESSION["utente"]->codice_ente; |
|
} |
|
|
|
if(empty($record["notice_id"])) { |
|
$record["notice_id"] = uuidgen(); |
|
$record["notice_version"] = 1; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
$salva = new Salva(); |
|
$salva->debug = false; |
|
$salva->nome_tabella = "b_guue"; |
|
$salva->operazione = empty($record["codice"]) ? "INSERT" : "UPDATE"; |
|
$salva->ignore = ["soft_delete"]; |
|
if ($salva->operazione == "UPDATE") { |
|
$salva->ignore = array_merge($salva->ignore, ["data_creazione", "data_pubblicazione", "info"]); |
|
} |
|
$salva->oggetto = $record; |
|
$codice = $salva->save(); |
|
if (!empty($codice)) { |
|
$record["first_save"] = $salva->operazione === "INSERT"; |
|
$record["codice"] = $codice; |
|
$this->info = $salva->oggetto; |
|
$this->info["codice"] = $codice; |
|
$this->info["valori"] = json_decode($this->info["valori"], 1); |
|
|
|
if ($_SESSION["utente"]->gerarchia > 15 && $salva->operazione == "INSERT") { |
|
|
|
$permesso = [ |
|
"sezione" => "guue", |
|
"codice_elemento" => $this->info["codice"], |
|
"codice_gestore" => $_SESSION["ente"]->codice, |
|
"funzione" => "0", |
|
"codice_utente" => $_SESSION["utente"]->codice, |
|
]; |
|
|
|
$salva_p = new salva(); |
|
$salva_p->debug = false; |
|
$salva_p->nome_tabella = "r_permessi"; |
|
$salva_p->operazione = "INSERT"; |
|
$salva_p->oggetto = $permesso; |
|
$salva_p->save(); |
|
} |
|
if ($data["draft"] == "N" && $this->completed()) { |
|
|
|
$this->generateXMLNotice(); |
|
$this->xml = $this->sortXMLElementsByXSD($this->xml); |
|
$record["xml"] = $this->xml->saveXML(); |
|
|
|
$salva->oggetto = $record; |
|
$salva->operazione = "UPDATE"; |
|
$salva->save(); |
|
} |
|
} |
|
|
|
return $record; |
|
} |
|
|
|
/** |
|
* fixArrayForXML |
|
* |
|
* @param mixed $data |
|
* @return void |
|
*/ |
|
private function fixArrayForXML(array &$data): void |
|
{ |
|
|
|
$keys = array_keys($data); |
|
foreach ($keys as $key) { |
|
if (is_array($data[$key])) { |
|
$this->fixArrayForXML($data[$key]); |
|
} else { |
|
|
|
if ($key == "BT-538-Lot") { |
|
if ($data[$key] === "__SPECIFY") { |
|
$data[$key] = null; |
|
} |
|
} |
|
if (strpos($key, "(t)-") !== false) { |
|
preg_match('/^\d{2}[:]\d{2}$/', $data[$key], $m); |
|
preg_match('/^\d{2}[:]\d{2}:\d{2}$/', $data[$key], $_m); |
|
if (!empty($m)) { |
|
$data[$key] .= ":00" . self::TIMEZONE_SUFFIX; |
|
} |
|
if (!empty($_m)) { |
|
$data[$key] .= self::TIMEZONE_SUFFIX; |
|
} |
|
} |
|
if (is_string($data[$key])) { |
|
preg_match('/^\d{2}[\/]\d{2}[\/]\d{4}$/', $data[$key], $matches, PREG_OFFSET_CAPTURE, 0); |
|
if (!empty($matches)) { |
|
$data[$key] = (DateTime::createFromFormat("d/m/Y", $data[$key]))->format('Y-m-d' . self::TIMEZONE_SUFFIX); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* toXML |
|
* |
|
* @return String |
|
*/ |
|
public function toXML(): ?String |
|
{ |
|
$this->generateXMLNotice(); |
|
$this->xml = $this->sortXMLElementsByXSD($this->xml); |
|
$xml = $this->xml->saveXML(); |
|
$this->xml = new DOMDocument(); |
|
$this->xml->loadXML($xml, LIBXML_NSCLEAN); |
|
|
|
return $this->xml->saveXML(); |
|
} |
|
|
|
/** |
|
* Effettua un override (che non viene salvato su DB) |
|
* del notice_id da inviare con la scheda |
|
* |
|
* @param string $notice_id |
|
* @return void |
|
*/ |
|
public function overrideNoticeID(string $notice_id) : void { |
|
$this->override_notice_id = $notice_id; |
|
} |
|
/** |
|
* Ottiene il noticeID della scheda. |
|
*/ |
|
public function getNoticeID(): ?string |
|
{ |
|
if(!empty($this->override_notice_id)) |
|
return $this->override_notice_id; |
|
return $this->info["notice_id"] ?? ""; |
|
} |
|
|
|
/** |
|
* Ottiene la versione della scheda |
|
* |
|
* @return string |
|
*/ |
|
public function getNoticeVersion(): string |
|
{ |
|
return str_pad(intval($this->info["notice_version"] ?? 1), 2, "0", STR_PAD_LEFT); |
|
} |
|
/** |
|
* Convert the visual model into XML format |
|
* |
|
* @return String |
|
*/ |
|
public function generateXMLNotice($encoding = '', bool $final_xml_render = true): void |
|
{ |
|
$this->final_xml_render = $final_xml_render; |
|
$notice = array_column($this->notice_types["noticeSubTypes"], null, "subTypeId")[$this->notice]; |
|
|
|
$subtypes = array_column($this->notice_types["documentTypes"], null, "id"); |
|
$subtype = $subtypes[$notice["documentType"]]; |
|
|
|
$this->xml = new DOMDocument('1.0', $encoding); |
|
$this->xml->preserveWhiteSpace = true; |
|
$this->xml->formatOutput = true; |
|
|
|
$root = $this->xml->createElement($subtype["rootElement"]); |
|
if (!empty($subtype["namespace"])) { |
|
|
|
$root = $this->xml->createElementNS($subtype["namespace"], $subtype["rootElement"]); |
|
} |
|
|
|
if (!empty($subtype["additionalNamespaces"])) { |
|
|
|
foreach ($subtype["additionalNamespaces"] as $ns) { |
|
// Store namespace url |
|
$this->namespaces[$ns["prefix"]] = $ns["uri"]; |
|
// Add namespace data to dom |
|
$root->setAttributeNS('http://www.w3.org/2000/xmlns/', "xmlns:{$ns["prefix"]}", $ns["uri"]); |
|
} |
|
} |
|
|
|
$this->xml->appendChild($root); |
|
// Create XPath provider |
|
$this->gen_query_provider = new XML_XPath20_Querier($this->xml, false, true); |
|
$publicationDate = (new DateTime('now'))->getTimestamp(); |
|
|
|
if(false) { |
|
$publicationDate -= (23*60*60 + 30*60); |
|
} |
|
|
|
if (!empty($this->fields["metadata"])) { |
|
|
|
//dd($notice["legalBasis"]);32014L0024 |
|
foreach ($this->fields["metadata"] as $metadata) { |
|
|
|
$field = $this->fields_metadata[$metadata["id"]]; |
|
|
|
$value = ""; |
|
switch ($field["btId"]) { |
|
case "BT-01": |
|
$value = $notice["legalBasis"]; |
|
break; |
|
case "BT-02": |
|
$value = $notice["type"]; |
|
break; |
|
case "BT-03": |
|
$value = $notice["formType"]; |
|
break; |
|
case "BT-04": |
|
case "BT-701": |
|
$value = $this->getNoticeID(); |
|
break; |
|
case "BT-757": |
|
$value = $this->getNoticeVersion(); |
|
break; |
|
case "BT-702": |
|
$value = ""; |
|
if ($field["id"] == "BT-702(a)-notice") { |
|
$value = "ITA"; |
|
} |
|
break; |
|
case "BT-05": |
|
if ($field["id"] == "BT-05(a)-notice") { |
|
$value = date("Y-m-d", $publicationDate) . self::TIMEZONE_SUFFIX; |
|
} else if ($field["id"] == "BT-05(b)-notice") { |
|
$value = date("H:i:s", $publicationDate) . self::TIMEZONE_SUFFIX; |
|
} |
|
break; |
|
case "OPT-001": |
|
$value = $this->notice_types["ublVersion"]; |
|
break; |
|
case "OPT-002": |
|
$sdk_version = $this->getNoticeSDKVersion(); |
|
$value = "eforms-sdk-{$sdk_version}"; |
|
break; |
|
case "OPP-070": |
|
$value = $this->notice; |
|
break; |
|
case "OPT-999": |
|
$value = date('Y-m-d') . self::TIMEZONE_SUFFIX; |
|
break; |
|
default: |
|
break; |
|
} |
|
if (!$this->final_xml_render || !empty_but_not_zero($value)) { |
|
$this->addFieldToXML($field["xpathAbsolute"], $this->xml->childNodes->item(0), $value, [], $field["id"]); |
|
} |
|
} |
|
} |
|
|
|
if (!empty($this->info["valori"]["eforms2"])) { |
|
$this->fixArrayForXML($this->info["valori"]["eforms2"]); |
|
$this->info["valori"]["eforms2"] = array_filter($this->info["valori"]["eforms2"], fn ($key) => !empty($key), ARRAY_FILTER_USE_KEY); |
|
foreach ($this->sections as $section) { |
|
if (!empty($this->info["valori"]["eforms2"][$section["name"]]["values"])) { |
|
|
|
$values = $this->info["valori"]["eforms2"][$section["name"]]["values"]; |
|
|
|
$section = $this->fields["content"][$section["index"]]; |
|
$_tmp = []; |
|
$this->addGroupToXML($section, $values, $_tmp); |
|
} |
|
} |
|
} |
|
|
|
$this->onUpdateXml(); |
|
} |
|
|
|
/** |
|
* Flatten given data into values array |
|
* |
|
* @param $data |
|
* @param Array $values |
|
* |
|
* @return void |
|
*/ |
|
private function extrapolateValues($data, array &$values): void |
|
{ |
|
if (is_array($data)) { |
|
foreach ($data as $d) { |
|
$this->extrapolateValues($d, $values); |
|
} |
|
} else { |
|
$values[] = $data; |
|
} |
|
} |
|
|
|
/** |
|
* Add a group to the XML document |
|
* |
|
* @param Array $section |
|
* @param Array $values |
|
* @param Bool $repeatable |
|
* |
|
* @return void |
|
*/ |
|
private function addGroupToXML(array $section, array $values, array &$current_repetitions = []): void |
|
{ |
|
|
|
$contentType = $section["contentType"]; |
|
|
|
// Render field |
|
if ($contentType === "field") { |
|
// Fetch metadata |
|
$metadata = $this->fields_metadata[$section["id"]] ?? null; |
|
assert($metadata !== null, "Invalid section id {$section['id']}"); |
|
// Get value |
|
$is_duration_or_amount = false; |
|
if (($metadata["legalType"] ?? "") === "VALUE" || ($metadata["legalType"] ?? "") === "DURATION") { |
|
$is_duration_or_amount = true; |
|
$value = $values; |
|
} else { |
|
$value = $values[$section["id"]] ?? ""; |
|
} |
|
|
|
// Get xpath |
|
$xpath = $metadata["xpathAbsolute"]; |
|
|
|
if (!$this->final_xml_render || !empty_but_not_zero($value)) { |
|
if ($is_duration_or_amount) { |
|
if (isset($value["value"]) && !empty_but_not_zero($value["value"]) && !empty($value["type"])) { |
|
|
|
$actualVal = $value["value"]; |
|
if(($metadata["legalType"] ?? "") === "VALUE") { |
|
$actualVal = is_numeric($value["value"]) ? |
|
number_format($value["value"], 2, ".", "") : |
|
$value["value"]; |
|
} |
|
$element = $this->addFieldToXML($xpath, $this->xml->childNodes->item(0), $actualVal, $current_repetitions, $metadata["id"]); |
|
$element->setAttribute($metadata["legalType"] == "VALUE" ? "currencyID" : "unitCode", $value["type"]); |
|
} |
|
} else { |
|
// Fix CPV Format |
|
if (!empty($value) && in_array($metadata["btId"] ?? "", ["BT-262", "BT-263"])) { |
|
$value = str_pad($value, 8, "0"); |
|
} |
|
// Create element |
|
$element = $this->addFieldToXML($xpath, $this->xml->childNodes->item(0), $value, $current_repetitions, $metadata["id"]); |
|
// Set default listName where not specified |
|
if (!empty($metadata["codeList"]) && substr($element->nodeName, -4) == "Code") { |
|
if (empty($element->getAttribute("listName"))) { |
|
$element->setAttribute("listName", $metadata["codeList"]["value"]["id"]); |
|
} |
|
} |
|
// Fix text-multilingual |
|
if (($metadata["type"] ?? "") === "text-multilingual" || $element->nodeName === "cbc:Name") { |
|
$element->setAttribute("languageID", "ITA"); |
|
} |
|
|
|
// Add schemeName where applicable |
|
if ($element->nodeName === "cbc:ID") { |
|
|
|
// Prendiamo i primi tre caratteri per ottenere LOT, ORG, CON ecc |
|
$scheme = substr($element->nodeValue, 0, 3); |
|
// Verifichiamo che ci siano le impostazioni per lo scheme |
|
if (!empty($scheme) && !in_array($scheme, ["GLO"]) && isset($this->info["valori"]["settings"]["scheme"][$scheme])) { |
|
// Otteniamo schemeName |
|
$schemeName = $this->info["valori"]["settings"]["scheme"][$scheme]["schemeName"] ?? null; |
|
|
|
if ($schemeName !== null) { |
|
// Settiamo schemeName |
|
$element->setAttribute("schemeName", $schemeName); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
return; |
|
} |
|
|
|
// Process group |
|
if ($contentType === "group") { |
|
// Check for content |
|
assert(!empty($section["content"]), "Empty content for {$section['id']}"); |
|
|
|
// Repeatable |
|
if ($section["_repeatable"] ?? false) { |
|
|
|
// Check for IDs |
|
if (array_has_numeric_keys($values)) { |
|
// Order and fill holes |
|
ksort($values); |
|
$values = array_values($values); |
|
|
|
$extrapolated_values = []; //verify given group contents before add to XML |
|
|
|
$this->extrapolateValues($values, $extrapolated_values); |
|
|
|
$extrapolated_values = array_filter($extrapolated_values, function ($value) { //filter empty values and ID from group values |
|
if ($value == "") { |
|
return false; |
|
} else { |
|
$matches = []; |
|
preg_match_all('/[A-Z]{3}[-]\d{4}/m', $value, $matches); |
|
return empty($matches[0]); |
|
} |
|
return true; |
|
}); |
|
|
|
if (empty($extrapolated_values) && !empty($section["_idScheme"])) { |
|
//codice magico non toccare |
|
} else { //if not all data was filtered store into XML |
|
// Get path of the group |
|
$group_path = $this->xml_structure[$section["nodeId"]]; |
|
|
|
// For each repetition |
|
foreach ($values as $rep_index => &$rep_values) { |
|
// Explode the XPath |
|
$xpathRelative = $this->explode_xpath($group_path["xpathRelative"]); |
|
// Remove attributes |
|
$xpathRelative = array_filter($xpathRelative, fn ($val) => strpos($val, "[") === false); |
|
// Rebuild the XPath |
|
$xpathRelative = implode("/", $xpathRelative); |
|
// Note the repetition |
|
$current_repetitions[$xpathRelative] = $rep_index; |
|
// Render subelements |
|
|
|
foreach ($section["content"] as $key => $sub_section) { |
|
|
|
$subsection_values = $rep_values[$sub_section["id"]] ?? []; |
|
|
|
// Wrap a possible field in a container so its rendering works correctly |
|
if (!is_array($subsection_values) && $sub_section["contentType"] === "field") { |
|
$subsection_values = [$sub_section["id"] => $subsection_values]; |
|
} |
|
// Recursive rendering |
|
$this->addGroupToXML($sub_section, $subsection_values, $current_repetitions); |
|
} |
|
} |
|
} |
|
} |
|
// Not repeatable |
|
} else { |
|
|
|
foreach ($section["content"] as $sub_section) { |
|
// Wrap a possible field in a container so its rendering works correctly |
|
$subsection_values = $values[$sub_section["id"]] ?? []; |
|
if (!is_array($subsection_values) && $sub_section["contentType"] === "field") { |
|
$subsection_values = [$sub_section["id"] => $subsection_values]; |
|
} |
|
// Recursive rendering |
|
$this->addGroupToXML($sub_section, $subsection_values, $current_repetitions); |
|
} |
|
} |
|
} else { |
|
throw new Exception("Invalid section type {$section['contentType']}"); |
|
} |
|
} |
|
|
|
/** |
|
* Converts an XPath expression into an array, filtering out unnecessary elements if clean == true |
|
* |
|
* @param String $xpath |
|
* @param Bool $strip_useless_parts |
|
* @return Array |
|
*/ |
|
private static function explode_xpath(String $xpath, Bool $keep_attributes = false): array |
|
{ |
|
|
|
preg_match_all("/\[(?:[^\[\]]|(?R))+\]|[^\[\]\/]+/", $xpath, $matches); |
|
$retval = $matches[0]; |
|
|
|
if ($keep_attributes) { |
|
$tmp = $retval; |
|
$retval = []; |
|
foreach ($tmp as $element) { |
|
if ($element[0] == "[") { |
|
$retval[count($retval) - 1] .= $element; |
|
} else { |
|
$retval[] = $element; |
|
} |
|
} |
|
} |
|
|
|
return $retval; |
|
} |
|
|
|
const FORCE_REPEATABLE_IN_FINAL_RENDER = [ |
|
"cac:ContractExecutionRequirement" => true, |
|
"cac:ContractingSystem" => true, |
|
"cac:ContractingPartyType" => true, |
|
"cac:ContractingActivity" => true, |
|
]; |
|
const FORCE_EMPTY_ATTRIBUTE_IN_FINAL_RENDER = []; |
|
|
|
private static function setNodeText(DOMElement &$element, String $value) |
|
{ |
|
|
|
/** @var DOMElement $child */ |
|
foreach ($element->childNodes as $child) { |
|
if ($child->nodeType === XML_TEXT_NODE) { |
|
$element->removeChild($child); |
|
} |
|
} |
|
$textNode = $element->ownerDocument->createTextNode($value); |
|
$element->appendChild($textNode); |
|
} |
|
|
|
public static function explode_attribute_selector($attribute_selector) |
|
{ |
|
// Valore di ritorno |
|
$retval = []; |
|
// Buffer del segmento attuale |
|
$buffer = ""; |
|
// Livello di nest attuale |
|
$nest = 0; |
|
for ($i = 0; $i < strlen($attribute_selector); $i++) { |
|
|
|
$char = $attribute_selector[$i]; |
|
if ($char == "[") { |
|
// Se troviamo una [ nel corso del segmento attuale, aumentiamo il livello di nesting |
|
$nest++; |
|
} elseif ($char == "]") { |
|
// Riduciamo il livello di nesting |
|
$nest--; |
|
// Se il nesting è a 0 abbiamo finito il segmento attuale |
|
if ($nest === 0) { |
|
$buffer .= $char; |
|
$retval[] = $buffer; |
|
$buffer = ""; |
|
continue; |
|
} |
|
} |
|
// Appendiamo il carattere |
|
$buffer .= $char; |
|
} |
|
return $retval; |
|
} |
|
|
|
/** |
|
* Add a field to the XML document using its absolute path |
|
* |
|
* @param String $xpath |
|
* @param String $value |
|
* |
|
* @return DOMElement |
|
*/ |
|
private function addFieldToXML(String $xpath, DOMElement $element, String $value, array $current_repetitions = [], string $field_name = null, DOMElement $parent = null): DOMElement |
|
{ |
|
|
|
// Explode Xpath removing () calls |
|
$path = $this->explode_xpath($xpath, true); |
|
|
|
// Rimuoviamo il * iniziale che esiste solo per questioni di compatibilità |
|
if ($path[0] === "*") { |
|
array_shift($path); |
|
} |
|
// Se dobbiamo lavorare sul padre (e abbiamo un padre) |
|
if ($path[0] === "..") { |
|
if ($parent !== null) |
|
$element = $parent; |
|
array_shift($path); |
|
} |
|
|
|
/** |
|
* @var DOMElement $element |
|
*/ |
|
// Foreach part of the path |
|
for ($index = 0; $index < count($path); $index++) { |
|
// Get current segment |
|
$tag = $path[$index]; |
|
// Check if it has a selector |
|
$has_attribute_selector = str_contains($tag, "["); |
|
|
|
// Keep the full segment |
|
$selector = $tag; |
|
|
|
// If we have an attribute selector we isolate it |
|
if ($has_attribute_selector) { |
|
$attribute_selector = substr($tag, strpos($tag, "[")); |
|
//$attribute_selector = rtrim($attribute_selector, "]"); |
|
$tag = substr($tag, 0, strpos($tag, "[")); |
|
} |
|
|
|
$_repeat = false; |
|
|
|
// Skip NOP since it's been already managed by the previous iteration |
|
if ($selector === "NOP") { |
|
continue; |
|
} else if (strpos($tag, "(") !== false) { |
|
if (strpos($tag, "text()=") === 0) { |
|
// Verifichiamo che non ci sia già il testo prima |
|
if (strlen($element->nodeValue) <= 0) { |
|
$text_attribute = trim(substr($tag, strpos($tag, "/text()=") + 8), "'[]"); |
|
self::setNodeText($element, $text_attribute); |
|
} |
|
} else { |
|
// (Al momento) non gestiamo altri tipi di attributi |
|
} |
|
} |
|
// If the part of the path is an attribute we set it |
|
else if (strpos($selector, "@") === 0) { |
|
if (str_contains($selector, "=") && (substr_count($selector, "'") == 2 || substr_count($selector, '"') == 2)) { |
|
// If it is we mark it for creation and make sure the next iteration doesn't have to |
|
$attribute = explode("=", $selector); |
|
$attribute_name = str_replace("@", "", $attribute[0]); |
|
$attribute_value = str_replace(["'", '"'], "", $attribute[1]); |
|
|
|
$element->setAttribute($attribute_name, $attribute_value); |
|
} else { |
|
$element->setAttribute(ltrim($selector, "@"), $value); |
|
} |
|
// Create / select an elemenenet |
|
} else { |
|
// Check if it has a prefix |
|
$has_prefix = str_contains($tag, ":"); |
|
$tag_prefix = ""; |
|
// Prefix |
|
if ($has_prefix) { |
|
$tag_split = explode(":", $tag, 2); |
|
$tag_prefix = $tag_split[0]; |
|
} |
|
|
|
// Check if it has an attribute we have to consider |
|
$has_attribute = $index < count($path) - 1 && strpos($path[$index + 1], "@") === 0; |
|
$create_attribute = false; |
|
$attribute_name = $attribute_value = ""; |
|
// If we do |
|
if ($has_attribute) { |
|
// We check if it's in the format @attribute='value' |
|
$next_selector = $path[$index + 1]; |
|
if (str_contains($next_selector, "=") && (substr_count($next_selector, "'") == 2 || substr_count($next_selector, '"') == 2)) { |
|
|
|
// If it is we mark it for creation and make sure the next iteration doesn't have to |
|
$create_attribute = true; |
|
$attribute = explode("=", $next_selector); |
|
$attribute_name = str_replace("@", "", $attribute[0]); |
|
$attribute_value = str_replace(["'", '"'], "", $attribute[1]); |
|
$path[$index + 1] = "NOP"; |
|
} |
|
} |
|
|
|
// Select target element |
|
$absolute_selector = $element->getNodePath() . "/" . $selector; |
|
// Verify that we have an attribute |
|
/*if ($has_attribute) { |
|
$absolute_selector = $absolute_selector . "[{$next_selector}]"; |
|
}*/ |
|
try { |
|
$nodes = $this->gen_query_provider->runQuery($absolute_selector); |
|
} catch (Throwable $e) { |
|
// Query fallita |
|
} |
|
// Check if we have to create a new one |
|
$repetitions_for_element = $current_repetitions[$tag] ?? false; |
|
$_repeat = !empty($current_repetitions) && $repetitions_for_element !== false && $repetitions_for_element > 0 && count($nodes) < ($repetitions_for_element + 1); |
|
if (empty($nodes) || $_repeat || ($this->final_xml_render && (self::FORCE_REPEATABLE_IN_FINAL_RENDER[$selector] ?? false))) { |
|
|
|
// Use namespace for prefix |
|
if ($has_prefix) { |
|
$child = $this->xml->createElementNS($this->namespaces[$tag_prefix], $tag); |
|
} else { |
|
$child = $this->xml->createElement($tag); |
|
} |
|
|
|
$force_add = self::FORCE_EMPTY_ATTRIBUTE_IN_FINAL_RENDER[$tag] ?? null; |
|
if ($this->final_xml_render && $force_add !== null) { |
|
foreach ($force_add as $fa_element) { |
|
$full_name = implode(":", $fa_element); |
|
$el = $this->xml->createElementNS($this->namespaces[$fa_element[0]], $full_name, ""); |
|
$child->appendChild($el); |
|
} |
|
} |
|
|
|
// Create attribute if we have to |
|
if ($create_attribute) { |
|
$attribute_name = str_replace(" ", "", $attribute_name); |
|
$attribute_value = str_replace(" ", "", $attribute_value); |
|
$child->setAttribute($attribute_name, $attribute_value); |
|
} |
|
|
|
// Se ha un selettore di attributo vuol dire che si aspetta qualcosa di specifico dai figli |
|
if ($has_attribute_selector) { |
|
if (!$this->final_xml_render || !empty_but_not_zero($value)) { |
|
$all_attributes = $this->explode_attribute_selector($attribute_selector); |
|
$attributes = []; |
|
foreach ($all_attributes as $attribute) { |
|
$attribute = ltrim($attribute, "["); |
|
if (!str_starts_with($attribute, "not(") && !empty($attribute)) { |
|
$attributes[] = $attribute; |
|
} |
|
} |
|
if (!empty($attributes)) { |
|
$attribute_selector = implode("/", $attributes); |
|
// Creiamo i figli richiesti |
|
$this->addFieldToXML("*/" . $attribute_selector, $child, "", $current_repetitions, "", $element); |
|
} |
|
} |
|
} |
|
|
|
// If it's the last part |
|
if ($selector == $path[array_key_last($path)]) { |
|
// We populate the value |
|
if (!empty_but_not_zero($value)) { |
|
if (!empty($field_name)) { |
|
$comment = $this->xml->createComment("$field_name" . ($repetitions_for_element ? "[$repetitions_for_element]" : "")); |
|
$element->appendChild($comment); |
|
} |
|
self::setNodeText($child, $value); |
|
$element->appendChild($child); |
|
} |
|
} else { |
|
$element->appendChild($child); |
|
} |
|
|
|
$element = $child; |
|
continue; |
|
} |
|
|
|
// We don't have to create so we select it |
|
$_index = 0; |
|
// Check what we have to select |
|
$target_index = $repetitions_for_element ?? 0; |
|
foreach ($nodes as $node) { |
|
if ($node->parentNode->nodeName == $element->nodeName) { |
|
if ($target_index == $_index) { |
|
$element = $node; |
|
break; |
|
} |
|
$_index++; |
|
} |
|
} |
|
} |
|
if ($selector == $path[array_key_last($path)] && $selector[0] !== "@" && !empty($value)) { |
|
if (!empty($field_name) && $element->parentNode) { |
|
$comment = $this->xml->createComment("$field_name" . ($repetitions_for_element ? "[$repetitions_for_element]" : "")); |
|
$element->parentNode->insertBefore($comment, $element); |
|
} |
|
self::setNodeText($element, $value); |
|
} |
|
} |
|
return $element; |
|
} |
|
|
|
/** |
|
* Set the ID of a repeteable item |
|
* |
|
* @param mixed $data |
|
* @return Array |
|
*/ |
|
private function setSchemaIDForeachRepeteableItems(array $data = []): array |
|
{ |
|
// TODO Commentare la funzione |
|
|
|
if (isset($_SESSION["notice" . $this->notice])) { |
|
$schemas = $_SESSION["notice" . $this->notice]["settings"]["scheme"]; |
|
} else { |
|
$schemas = $this->info["valori"]["settings"]["scheme"] ?? null; |
|
} |
|
|
|
if (!empty($schemas) && !empty($data["valori"])) { |
|
|
|
$sections = $this->getSections(); |
|
$sections = array_column($sections, null, "id"); |
|
foreach ($schemas as $scheme => $settings) { |
|
$section = $sections[$settings["section"]]; |
|
|
|
if ($section !== null) { |
|
|
|
$data["valori"]["settings"]["scheme"][$scheme] = $settings; |
|
$dot_path = "eforms2.{$settings["xpath"]}"; |
|
|
|
$fields = self::get($data["valori"], $dot_path, null, true); |
|
if (!empty($fields)) { |
|
|
|
$index = 0; |
|
foreach ($fields as $key => $value) { |
|
|
|
$index++; |
|
$val = $settings["pad"]["prefix"] . str_pad($index, $settings["pad"]["length"], $settings["pad"]["string"], $settings["pad"]["type"]); |
|
if(!str_contains($key, "BT-137-Lot")) |
|
self::set($data["valori"], $key, $val); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
return $data; |
|
} |
|
|
|
/** |
|
* Ottiene il displaytype per il campo in questione |
|
* |
|
* @param String $displayType Il displayType attuale come specificato dal form type json |
|
* @param array $field Il campo, come specificato da fields.json |
|
* @return String Tipo compatibile con bs4formgenerator |
|
*/ |
|
private function getDisplayTypeFor(String $displayType, array $field): String |
|
{ |
|
|
|
if (in_array($field["type"], ["text-multilingual", "indicator", "date", "url", "email", "number", "integer", "text", "time", "amount", "phone", "measure"])) { |
|
$displayType = strtoupper($field["type"]); |
|
} |
|
|
|
if (!empty($field["legalType"]) && $field["legalType"] == "DURATION" && $field["type"] == "measure") { |
|
$displayType = "DURATION"; |
|
} |
|
if (!empty($field["legalType"]) && $field["legalType"] == "VALUE" && $field["type"] == "amount") { |
|
$displayType = "VALUE"; |
|
} |
|
|
|
switch ($displayType) { |
|
case "DURATION": |
|
return "duration"; |
|
case "AMOUNT": |
|
case "VALUE": |
|
return "value"; |
|
case "URL": |
|
return "url"; |
|
case "EMAIL": |
|
return "email"; |
|
|
|
case "MEASURE": |
|
case "NUMBER": |
|
return "number"; |
|
case "DATE": |
|
return "date"; |
|
case "TIME": |
|
return "time"; |
|
case "TEXTAREA": |
|
return "textarea"; |
|
case "COMBOBOX": |
|
if (!empty($field["codeList"]["value"]["id"])) { |
|
return "select"; |
|
} |
|
return "text"; |
|
case "INDICATOR": |
|
return "radio"; |
|
case "RADIO": |
|
if (!empty($field["codeList"]["value"]["id"])) { |
|
return "radio"; |
|
} |
|
return "text"; |
|
} |
|
return "text"; |
|
} |
|
|
|
/** |
|
* Riorganizza la struttura delle sezioni ripetibili. Utile per assicurarsi che non vi siano buchi nella struttura |
|
* |
|
* @param array $data |
|
* @return array |
|
*/ |
|
private function organizeRepeteableGroups(array $data): array |
|
{ |
|
|
|
$ret = []; |
|
foreach ($data as $key => $val) { |
|
if (is_numeric($key)) { |
|
$ret[] = $val; |
|
} else { |
|
$ret[$key] = $val; |
|
} |
|
} |
|
|
|
foreach ($ret as &$val) { |
|
if (is_array($val)) { |
|
$val = $this->organizeRepeteableGroups($val); |
|
} |
|
} |
|
return $ret; |
|
} |
|
|
|
/** |
|
* Initializza l'SDK di eForms2 caricando tutti i file relativi |
|
* |
|
* @return void |
|
*/ |
|
private function initializeSDK(): void |
|
{ |
|
// Preveniamo caricamenti superflui. |
|
if ($this->fields !== null && $this->version === $this->loaded_version) { |
|
return; |
|
} |
|
$this->loaded_version = $this->version; |
|
|
|
$this->current_sdk_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . "eforms" . DIRECTORY_SEPARATOR . $this->version; |
|
$this->eforms_translations = $this->getTranslator(); |
|
$this->title = $this->translate("notice|name|{$this->notice}"); |
|
$this->settings = jsonToArray("{$this->current_sdk_path}/../../config/eforms/settings.json"); |
|
$this->fields = $this->getNoticeSchema($this->notice); |
|
$this->sections = $this->getSections(); |
|
|
|
$tmp_metadata = jsonToArray("{$this->current_sdk_path}/fields/fields.sa.json"); |
|
|
|
$this->fields_metadata = array_column($tmp_metadata["fields"], null, "id"); |
|
$this->xml_structure = array_column($tmp_metadata["xmlStructure"], null, "id"); |
|
|
|
// Personalizzazioni di fields.json |
|
$custom_fields_path = "{$this->current_sdk_path}/fields/fields.custom.json"; |
|
if (file_exists($custom_fields_path)) { |
|
$custom_fields = jsonToArray($custom_fields_path); |
|
$custom_fields = array_column($custom_fields["fields"], null, "id"); |
|
$this->fields_metadata = array_merge_recursive_distinct($this->fields_metadata, $custom_fields); |
|
} |
|
|
|
$this->codelist = jsonToArray("{$this->current_sdk_path}/codelists/codelists.json"); |
|
$this->codelist = array_column($this->codelist["codelists"], null, "id"); |
|
$this->notice_types = jsonToArray("{$this->current_sdk_path}/notice-types/notice-types.json"); |
|
|
|
if (!is_dir("{$this->current_sdk_path}/jsoncodelists") || count(glob("{$this->current_sdk_path}/jsoncodelists/*.gc")) == 0) { |
|
foreach ($this->codelist as $key => $attributes) { |
|
$this->getAvailableCodeListFor($key); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Si occupa del caricamento (o della creazione) di un eForm |
|
* |
|
* @param integer $codice |
|
* @return void |
|
*/ |
|
private function init($codice = 0): void |
|
{ |
|
|
|
$this->info = get_campi("b_guue"); |
|
$this->info["codice"] = 0; |
|
$this->info["codice_ente"] = $_SESSION["ente"]->codice; |
|
$this->info["version"] = "eForms"; |
|
$this->info["sdk_version"] = $this->__get("version"); |
|
$this->info["form"] = $this->notice; |
|
$this->info["anno"] = date('Y'); |
|
$this->info["data_creazione"] = date('d/m/Y H:i:s'); |
|
$this->info["utente_modifica"] = $_SESSION["utente"]->codice ?? 0; |
|
$this->info["valori"] = []; |
|
|
|
if (!empty($codice)) { |
|
|
|
$bind = [':codice' => $codice]; |
|
$sql = "SELECT * FROM b_guue WHERE codice = :codice AND soft_delete !='S'"; |
|
|
|
$ris = $this->pdo->go($sql, $bind); |
|
if ($ris->rowCount() == 1) { |
|
|
|
$this->info = $ris->fetch(PDO::FETCH_ASSOC); |
|
$continue = true; |
|
if (!isset($_SESSION["utente"]->elaborazioneCoda) && (!empty($this->info["modulo"]) && !empty($this->info["codice_elemento"]))) { |
|
$continue = false; |
|
if ($_SESSION["utente"]->permission($this->info["modulo"],$this->info["codice_elemento"])) { |
|
$continue = true; |
|
} |
|
} |
|
if ($continue) { |
|
// Preveniamo il cambio di form type |
|
if($this->notice === self::ANY_NOTICE_TYPE) { |
|
$this->notice = $this->info["form"]; |
|
} elseif ($this->info["form"] !== $this->notice) { |
|
$basepath = explode('?', $_SERVER['REQUEST_URI'], 2)[0]; |
|
die("<meta http-equiv='refresh' content='0;URL={$basepath}?codice={$codice}&form={$this->info['form']}'>"); |
|
} |
|
|
|
if (is_json($this->info["valori"])) { |
|
|
|
$this->info["valori"] = json_decode($this->info["valori"], 1); |
|
} else { |
|
|
|
$this->info["valori"] = []; |
|
} |
|
$this->version = $this->info["sdk_version"]; |
|
$this->initializeSDK(); |
|
} else { |
|
throw new LogicException("Non è stato possibile accedere alla scheda GUUE #{$codice}"); |
|
} |
|
} else { |
|
throw new LogicException("Scheda #{$codice} non trovata."); |
|
} |
|
} else { |
|
// ANY_NOTICE_TYPE non è compatibile se non c'è un codice |
|
if($this->notice === self::ANY_NOTICE_TYPE) { |
|
throw new LogicException("Non puoi istanziare una nuova scheda di tipo ANY"); |
|
} |
|
} |
|
// Preveniamo la creazione di form invalidi |
|
if ($this->fields === null) { |
|
throw new LogicException("Non è stato possibile accedere alla scheda GUUE #{$codice}"); |
|
} |
|
if($this->info["modulo"] == "albo_fornitori") { |
|
if ($this->enable_forced_suggestions) { |
|
// Forziamo il numero di ripetizione dei lotti |
|
$this->info["valori"]["settings"]["reps"]["GR-Lot"] = 1; |
|
// Impediamo all'utente di modificarlo |
|
$this->info["valori"]["settings"]["reps_locked"]["GR-Lot"] = true; |
|
// Forziamo il numero di ripetizione dei gruppi di lotti a 0 |
|
$this->info["valori"]["settings"]["reps"]["GR-LotsGroup"] = 0; |
|
// Impediamo all'utente di modificarlo |
|
$this->info["valori"]["settings"]["reps_locked"]["GR-LotsGroup"] = true; |
|
|
|
} |
|
|
|
} |
|
|
|
// Handle user settings |
|
$this->handleUserSessionSettings(); |
|
|
|
} |
|
|
|
/** |
|
* Carica il formato del form attuale |
|
* |
|
* @param string $index |
|
* @return array|null |
|
*/ |
|
private function getNoticeSchema(string $index): ?array |
|
{ |
|
if (file_exists("{$this->current_sdk_path}/notice-types/{$index}.json")) { |
|
|
|
return jsonToArray("{$this->current_sdk_path}/notice-types/{$index}.json"); |
|
} |
|
return null; |
|
} |
|
|
|
/** |
|
* Ritorna le sezioni nell'ordine del render |
|
* |
|
* @return array |
|
*/ |
|
public function getReorderedSections(): array |
|
{ |
|
$sections = $this->sections; |
|
|
|
$found_index = null; |
|
foreach ($sections as $index => $section) { |
|
// Cerchiamo la sezione organizzazioni |
|
if ($section["id"] === "GR-Organisations-Section") { |
|
// Teniamo nota dell'indice |
|
$found_index = $index; |
|
break; |
|
} |
|
} |
|
// Se abbiamo organizzazioni |
|
if ($found_index !== null) { |
|
// Mettiamola da parte |
|
$organizations = $sections[$found_index]; |
|
// Cancelliamola dall'array |
|
unset($sections[$found_index]); |
|
// Rimettiamola ad index 0 |
|
array_unshift($sections, $organizations); |
|
} |
|
|
|
return $sections; |
|
} |
|
|
|
/** |
|
* Ottiene le sezioni del form attuale |
|
* |
|
* @return array|null |
|
*/ |
|
private function getSections(): ?array |
|
{ |
|
|
|
if (!empty($this->fields)) { |
|
|
|
foreach ($this->fields["content"] as $index => $content) { |
|
|
|
if ($content["displayType"] == "SECTION") { |
|
|
|
$sections[] = [ |
|
"index" => $index, |
|
"id" => $content["id"], |
|
"name" => $content["id"], |
|
"type" => $content["displayType"], |
|
"object" => $content["contentType"], |
|
"_label" => $content["_label"], |
|
"_repeatable" => $content["_repeatable"] ?? false, |
|
]; |
|
} |
|
} |
|
|
|
return $sections; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
/** |
|
* Ottiene le codelist per il campo specificato. Generalmente sono usate per popolare le opzioni di un select |
|
* Questo metodo di occupa anche di convertire il formato delle codelist in JSON per questioni di velocità |
|
* |
|
* @param String $id |
|
* @return array|null |
|
*/ |
|
private function getAvailableCodeListFor(String $id): ?array |
|
{ |
|
|
|
if (empty($this->codelist[$id])) { |
|
|
|
return []; |
|
} |
|
|
|
if (file_exists("{$this->current_sdk_path}/jsoncodelists/{$this->codelist[$id]["filename"]}")) { |
|
|
|
$options = file_get_contents("{$this->current_sdk_path}/jsoncodelists/{$this->codelist[$id]["filename"]}"); |
|
$options = json_decode($options, TRUE); |
|
} else if (file_exists("{$this->current_sdk_path}/codelists/{$this->codelist[$id]["filename"]}")) { |
|
|
|
$list = simplexml_load_file("{$this->current_sdk_path}/codelists/{$this->codelist[$id]["filename"]}"); |
|
if (!empty($list) && !empty($list->SimpleCodeList->Row)) { |
|
|
|
foreach ($list->SimpleCodeList->Row as $Row) { |
|
|
|
$code = $Row->xpath("Value[@ColumnRef='code']"); |
|
if (!empty($code) && count($code) == 1) { |
|
|
|
$values = $Row->xpath("Value[not(@ColumnRef='code') and not(@ColumnRef='Name')]"); |
|
if (count($values) > 0) { |
|
|
|
foreach ($values as $value) { |
|
|
|
$options[(string) $value["ColumnRef"]][(string) $code[0]->SimpleValue] = (string) $value->SimpleValue; |
|
} |
|
} |
|
} |
|
} |
|
|
|
$opts = json_encode($options, JSON_PRETTY_PRINT); |
|
if (!is_dir("{$this->current_sdk_path}/jsoncodelists")) { |
|
mkdir("{$this->current_sdk_path}/jsoncodelists", 0750); |
|
} |
|
file_put_contents("{$this->current_sdk_path}/jsoncodelists/{$this->codelist[$id]["filename"]}", $opts); |
|
|
|
$opts = null; |
|
gc_collect_cycles(); |
|
} |
|
} |
|
|
|
switch ($this->language) { |
|
|
|
case 'it': |
|
$language = "ita_label"; |
|
if (!empty($options[$language])) { |
|
break; |
|
} |
|
|
|
case 'es': |
|
$language = "spa_label"; |
|
if (!empty($options[$language])) { |
|
break; |
|
} |
|
|
|
case 'de': |
|
$language = "deu_label"; |
|
if (!empty($options[$language])) { |
|
break; |
|
} |
|
|
|
case 'en': |
|
default: |
|
$language = "eng_label"; |
|
break; |
|
} |
|
|
|
if (!empty($options) && !empty($options[$language])) { |
|
|
|
return $options[$language]; |
|
} |
|
|
|
return []; |
|
} |
|
|
|
/** |
|
* Verifica una proprietà di un campo, valutando EFX se necessario |
|
* |
|
* @param array $field Campo da valutare, preso da fields.sjon |
|
* @param String $property_name Nome della proprietà |
|
* @param int $index Indice del campo per gestire occorrenze multiple |
|
* @return array Ritorna un array contenente il risultato della verifica |
|
*/ |
|
private function checkFieldProperty(array $field, String $property_name, int $index): array |
|
{ |
|
// Valore di ritorno |
|
$return_value = [ |
|
"value" => null, // Effettivo valore della proprietà (null|true|false) |
|
"severity" => null, // Severità della proprietà |
|
"has_efx" => false, // Se true, questa proprietà è stata verificata usando EFX |
|
"error_state" => false, // Se true, questa proprietà non è stata valutata correttamente |
|
"message" => null, // Messaggio della proprietà, usato generalmente dalle assertion per mostrare un messaggio di errore |
|
]; |
|
|
|
// Se non c'è EFX ci sono alcune regole speciali impostabili |
|
if (!$this->enable_efx_evaluation) { |
|
if ($property_name == "mandatory" && array_key_exists($field["btId"], self::EFX_OFF_DEFAULT_MANDATORY)) { |
|
$return_value["value"] = self::EFX_OFF_DEFAULT_MANDATORY[$field["btId"]]; |
|
return $return_value; |
|
} |
|
} |
|
|
|
if (!empty($field)) { |
|
if (!empty($field[$property_name])) { |
|
$property = $field[$property_name]; |
|
|
|
// Credo che severity sia per decidere quale prende priorità ? Per ora sembrano tutti ERROR |
|
$return_value["severity"] = $property["severity"]; |
|
|
|
// Valore di base della proprietà (che può poi essere sovrascritto dai constraint) |
|
|
|
if ($this->isEFX($property["value"])) { // Questo valore può essere EFX |
|
$return_value["has_efx"] = true; |
|
$efx_return = $this->evalBoolean($property["value_xpath"], $property["value_context"], $index); |
|
|
|
// Se EFX ha ritornato null siamo incappati in un errore |
|
if ($efx_return === null) { |
|
$return_value["error_state"] = true; |
|
} else { |
|
$return_value["value"] = $efx_return; |
|
} |
|
} else { |
|
$return_value["value"] = $property["value"] ?? false; |
|
} |
|
|
|
// Valutazione dei constraints |
|
if (!empty($property["constraints"])) { |
|
foreach ($property["constraints"] as $constraint) { |
|
$constraint_value = $this->checkConstraint($constraint, $property_name, $index); |
|
if ($constraint_value["has_efx"]) { |
|
$return_value["has_efx"] = true; |
|
} |
|
// WORKAROUND |
|
if ($constraint_value["error_state"]) { |
|
if ($property_name == "forbidden") { |
|
if (array_key_exists($field["btId"], self::CONSTRAINT_FAILED_DEFAULT_FORBIDDEN)) { |
|
$return_value["value"] = self::CONSTRAINT_FAILED_DEFAULT_FORBIDDEN[$field["btId"]]; |
|
return $return_value; |
|
} |
|
} elseif ($property_name == "mandatory") { |
|
if (array_key_exists($field["btId"], self::CONSTRAINT_FAILED_DEFAULT_MANDATORY)) { |
|
$return_value["value"] = self::CONSTRAINT_FAILED_DEFAULT_MANDATORY[$field["btId"]]; |
|
return $return_value; |
|
} |
|
} |
|
|
|
$return_value["error_state"] = true; |
|
} |
|
if ($constraint_value["value"] !== null) { |
|
$return_value["value"] = $constraint_value["value"]; |
|
$return_value["message"] = $constraint_value["message"]; |
|
$return_value["severity"] = $constraint_value["severity"]; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return $return_value; |
|
} |
|
|
|
/** |
|
* Metodo abbastanza crudo per verificare che l'espressione sia EFX |
|
* |
|
* @param mixed $query |
|
* @return boolean |
|
*/ |
|
private static function isEFX($query): bool |
|
{ |
|
if (gettype($query) != "string") return false; |
|
return preg_match('/\{.+?(?<!\\\\)\} \$\{.+?(?<!\\\\)\}/', $query) === 1; |
|
} |
|
|
|
/** |
|
* Wrapper per valutare una query efx che debba ritornare true o false |
|
* |
|
* @param String $query Query Xpath 2.0 da eseguire |
|
* @param String $context Contesto Xpath 2.0 della query da eseguire |
|
* @param integer $index Indice di riferimento per gestire ripetizioni |
|
* @return boolean|null |
|
*/ |
|
private function evalBoolean(String $query, String $context, int $index): ?bool |
|
{ |
|
|
|
// Per qualche motivo a volte c'è EFX che è semplicemente true o false. Possiamo gestircelo noi. |
|
if ($query === "true()") { |
|
return true; |
|
} |
|
if ($query === "false()") { |
|
return false; |
|
} |
|
|
|
// Se non valutiamo EFX ritorniamo null |
|
if (!$this->enable_efx_evaluation) { |
|
return null; |
|
} |
|
|
|
$error = null; |
|
|
|
// Valutiamo la query |
|
$result = $this->efx_query_provider->evalBoolean($query, $context, $index, $error); |
|
|
|
// Se fallisce con tipo sbagliato, wrappiamo tutto in boolean() |
|
if ($result === null && $error === XML_XPath20_Querier::XPATH_FAIL_WRONG_TYPE) { |
|
$result = $this->efx_query_provider->evalBoolean("boolean($query)", $context, $index, $error); |
|
} |
|
|
|
// Ritorniamo il risultato |
|
return $result; |
|
} |
|
|
|
/** |
|
* Esegue la valutazione di un constraint |
|
* |
|
* @param array $constraint Il constraint da valutare |
|
* @param string $property_name Il nome della proprietà |
|
* @param string $index L'indice dell'elemento per gestire ripetizioni |
|
* @return array |
|
*/ |
|
private function checkConstraint(array $constraint, string $property_name, int $index): array |
|
{ |
|
$return_value = [ |
|
"error_state" => false, |
|
"has_efx" => false, |
|
"value" => null, |
|
"severity" => null, |
|
"message" => null, |
|
]; |
|
|
|
// Step 1. Capire se il constraint si applica a noi |
|
// Controlliamo di avere una notice type list |
|
if (!empty($constraint["noticeTypes"])) { |
|
// Se lo abbiamo, verifichiamo si riferisca a noi |
|
if (!in_array($this->notice, $constraint["noticeTypes"])) { |
|
// Non si riferisce a noi |
|
return $return_value; |
|
} |
|
} |
|
// Step 2. Valutare se c'è una condizione |
|
if (!empty($constraint["condition"])) { |
|
$condition = $constraint["condition"]; |
|
// Verifichiamo che la condizione sia EFX |
|
if (!$this->isEFX($condition)) { |
|
throw new Exception("Expected an EFX query, but got {$condition} instead."); |
|
} |
|
$return_value["has_efx"] = true; |
|
$condition_eval = $this->evalBoolean($constraint["condition_xpath"], $constraint["condition_context"], $index); |
|
|
|
// Se la condizione è falsa, il constraint non si applica e ritorniamo senza errore |
|
if ($condition_eval === false) { |
|
return $return_value; |
|
} |
|
// Se abbiamo ricevuto null da EFX qualcosa è andato storto e logghiamo l'errore |
|
if ($condition_eval === null) { |
|
$return_value["error_state"] = true; |
|
return $return_value; |
|
} |
|
} |
|
|
|
// Step 3. Valutare il valore della condizione |
|
assert(!empty($constraint["value"]), "Invalid constraint structure."); |
|
// Value può essere XPath |
|
if ($this->isEFX($constraint["value"])) { |
|
$return_value["has_efx"] = true; |
|
|
|
$value_eval = $this->evalBoolean($constraint["value_xpath"], $constraint["value_context"], $index); |
|
// Se ritorna null, siamo in stato di errore |
|
if ($value_eval === null) { |
|
$return_value["error_state"] = true; |
|
return $return_value; |
|
} |
|
// Altrimenti possiamo applicarlo al valore di ritorno |
|
$return_value["value"] = $value_eval; |
|
$return_value["message"] = $constraint["message"] ?? null; |
|
$return_value["severity"] = $constraint["severity"] ?? null; |
|
} else { |
|
$return_value["value"] = $constraint["value"] ?? false; |
|
} |
|
return $return_value; |
|
} |
|
|
|
/** |
|
* Fa il parsing di una data in maniera automatica a partire dai formati tipi di di Eforms2 e la ritorna nel formato richiesto |
|
* |
|
* @param String $date_str |
|
* @param String $targetFormat |
|
* @return string|null |
|
*/ |
|
protected static function FixDate(String $date_str, String $targetFormat): ?string |
|
{ |
|
$idx = strpos($date_str, self::TIMEZONE_SUFFIX); |
|
if ($idx !== false) { |
|
$date_str = substr($date_str, 0, $idx); |
|
} |
|
$sourceFormat = self::DetectDateFormat($date_str); |
|
if (!$sourceFormat) return null; |
|
$date = DateTime::createFromFormat($sourceFormat, $date_str); |
|
return $date !== false ? $date->format($targetFormat) : null; |
|
} |
|
|
|
/** |
|
* Rileva il formato data di una stringa |
|
* |
|
* @param String $date_str |
|
* @return String |
|
*/ |
|
protected static function DetectDateFormat(String $date_str): ?String |
|
{ |
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_str)) |
|
return 'Y-m-d'; |
|
if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $date_str)) |
|
return 'm/d/Y'; |
|
if (preg_match('/^\d{2}\.\d{2}\.\d{4}$/', $date_str)) |
|
return 'd.m.Y'; |
|
return null; |
|
} |
|
|
|
/** |
|
* Ottiene la versione SDK di questa notice |
|
* |
|
* @return string |
|
*/ |
|
public function getNoticeSDKVersion(): string |
|
{ |
|
return substr($this->notice_types["sdkVersion"], 0, strlen($this->notice_types["sdkVersion"]) - 2); |
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
* Returns the modules that can provide data |
|
* |
|
* @param string $modulo |
|
* @param integer $elemento |
|
* @return array |
|
*/ |
|
public static function moduliCollegati() |
|
{ |
|
return [ |
|
"gare" => "/gare/guue/integration.php", |
|
"pcp" => "/pcp/guue/integration.php", |
|
"concorsi" => "/concorsi/guue/integration.php", |
|
"albo_fornitori" => "/albo_fornitori/guue/integration.php", |
|
]; |
|
} |
|
|
|
/** |
|
* Ritorna le pubblicazioni collegate ad una gara o altro modulo |
|
* |
|
* @param string $modulo |
|
* @param integer $elemento |
|
* @return array |
|
*/ |
|
public static function getPubblicazioniFromModulo(string $modulo, int $elemento) |
|
{ |
|
global $pdo; |
|
if (!empty(self::moduliCollegati()[$modulo])) { |
|
|
|
return $pdo->go( |
|
"SELECT codice, titolo, numero, anno, numero_ojs, numero_tecnico, form, version, stato, data_trasmissione, data_pubblicazione |
|
FROM b_guue |
|
WHERE modulo = :modulo AND codice_elemento = :elemento AND soft_delete = 'N'", |
|
[":modulo" => $modulo, ":elemento" => $elemento] |
|
)->fetchAll(PDO::FETCH_ASSOC); |
|
} |
|
} |
|
|
|
/** |
|
* Determina che la scheda attuale sia pronta per essere bloccata per ANAC |
|
* |
|
* @return boolean |
|
*/ |
|
public function canBeReadyForAnac() : bool { |
|
return ($this->info["stato"] ?? "") === "DA TRASMETTERE"; |
|
} |
|
/** |
|
* Determina che la scheda attuale sia stata bloccata per ANAC |
|
* |
|
* @return boolean |
|
*/ |
|
public function isReadyForAnac() : bool { |
|
return ($this->info["stato"] ?? "") === "TRASMESSO"; |
|
} |
|
/** |
|
* Determina che la scheda attuale sia trasmissibile ad anac |
|
* |
|
* @return boolean |
|
*/ |
|
public function isReadyForAnacTransmission() : bool { |
|
return ($this->info["stato"] ?? "") === "TRASMESSO" || ($this->info["stato"] ?? "") === "PUBBLICATO"; |
|
} |
|
/** |
|
* Determina che la scheda attuale sia idonea ad essere sbloccata per ANAC |
|
* |
|
* @return boolean |
|
*/ |
|
public function canBeUnlockedForANAC() : bool { |
|
if(empty($this->info["codice"])) return false; |
|
if(!$this->isReadyForAnac()) return false; |
|
|
|
if(empty($this->npa)) return true; |
|
|
|
$scheda = $this->npa->scheda; |
|
|
|
|
|
// Se la scheda è trasmessa o più e non presenta errori, non si può sbloccare |
|
if($scheda["stato"] > 10 && $scheda["errore"] == "NO") { |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
public static function nuovaSchedaFromAnac(string $notice, string $titolo, NuovaPiattaformaAppalti $npa) : ?int { |
|
$ted = new self($notice, 0, $npa); |
|
$ted->setMode("save"); |
|
|
|
$ted->info["modulo"] = $npa->fascicolo["modulo_riferimento"]; |
|
$ted->info["codice_elemento"] = $npa->fascicolo["id_riferimento"]; |
|
$ted->info["lotto_riferimento"] = null; |
|
if(!empty($npa->scheda["codice_lotto"])) { |
|
$codiceLottoNpa = $npa->scheda["codice_lotto"]; |
|
if(!empty($codiceLottoNpa)) { |
|
$lot = $npa->getLots($codiceLottoNpa) ?? null; |
|
if($lot) { |
|
$lot = reset($lot); |
|
$ted->info["lotto_riferimento"] = $lot["codice"]; |
|
} |
|
} |
|
} |
|
$ted->info["eforms2"]["GR-Organisations-Section"] = []; |
|
$ted->info["current_section"] = "GR-Organisations-Section"; |
|
$ted->info["titolo"] = $titolo; |
|
$ted->info["draft"] = "S"; |
|
|
|
|
|
$ted->manageChangeSection(); |
|
$ted->setSuggestions(); |
|
|
|
|
|
$ted->save($ted->info); |
|
|
|
return empty($ted->info["codice"]) ? null : $ted->info["codice"]; |
|
} |
|
|
|
/** |
|
* Duplica una scheda GUUE e ritorna il codice in caso di successo |
|
* |
|
* @param integer $codice Scheda GUUE da duplicare |
|
* @param boolean $rectify Se true la scheda viene marchiata come rettifica |
|
* @param boolean $copy_data Se true vengono copiati anche i dati |
|
* @return integer|null Codice della nuova scheda in caso di successo o NULL in caso di fallimento |
|
*/ |
|
public static function duplicaScheda(int $codice, bool $rectify = false, bool $copy_data = true) : ?int |
|
{ |
|
global $pdo; |
|
$record = $pdo->go("SELECT * FROM b_guue WHERE codice = :codice", [":codice" => $codice])->fetch(PDO::FETCH_OBJ); |
|
|
|
|
|
$record->numero_ojs = ""; |
|
$record->errori = ""; |
|
$record->risposta = ""; |
|
$record->stato = "BOZZA"; |
|
$record->data_trasmissione = NULL; |
|
$record->data_pubblicazione = NULL; |
|
$record->utente_modifica = isset($_SESSION["utente"]) ? $_SESSION["utente"]->codice : -1; |
|
if($rectify) { |
|
$record->valori = json_decode($record->valori, true); |
|
$record->valori["eforms2"]["GR-Change"] = [ |
|
"values" => [ |
|
"2" => [ |
|
"BT-758-notice" => $record->numero_tecnico |
|
] |
|
] |
|
]; |
|
$record->valori = json_encode($record->valori); |
|
} |
|
|
|
$record->info = json_encode(["rectification" => $rectify]); |
|
|
|
$record->numero_tecnico = ""; |
|
$response = $pdo->go( |
|
"INSERT INTO b_guue |
|
(codice_ente, modulo, codice_elemento, lotto_riferimento, version, sdk_version, titolo, numero, anno, numero_ojs, numero_tecnico, form, campi, info, valori, xml, errori, risposta, stato, data_trasmissione, utente_modifica, notice_version, notice_id) |
|
VALUES |
|
(:codice_ente, :modulo, :codice_elemento, :lotto_riferimento, :version, :sdk_version, :titolo, :numero, :anno, :numero_ojs, :numero_tecnico, :form, :campi, :info, :valori, :xml, :errori, :risposta, :stato, :data_trasmissione, :utente_modifica, :notice_version, :notice_id)", |
|
[ |
|
":codice_ente" => $record->codice_ente, |
|
":modulo" => $record->modulo, |
|
":codice_elemento" => $record->codice_elemento, |
|
":lotto_riferimento" => $record->lotto_riferimento, |
|
":version" => $record->version, |
|
":sdk_version" => $record->sdk_version, |
|
":titolo" => $record->titolo, |
|
":numero" => $record->numero, |
|
":anno" => $record->anno, |
|
":numero_ojs" => $record->numero_ojs, |
|
":numero_tecnico" => $record->numero_tecnico, |
|
":form" => $record->form, |
|
":campi" => $record->campi, |
|
":info" => $record->info, |
|
":valori" => $copy_data ? $record->valori : "", |
|
":xml" => "", |
|
":errori" => $record->errori, |
|
":risposta" => $record->risposta, |
|
":stato" => $record->stato, |
|
":data_trasmissione" => $record->data_trasmissione, |
|
":utente_modifica" => $record->utente_modifica, |
|
":notice_id" => uuidgen_guue(), |
|
":notice_version" => $record->notice_version + 1, |
|
":utente_modifica" => $record->utente_modifica, |
|
] |
|
); |
|
if ($response->errorInfo()[0] == "00000") { |
|
$codice = $pdo->lastInsertId(); |
|
if ($_SESSION["utente"]->gerarchia > 15) { |
|
$permesso = [ |
|
"sezione" => "guue", |
|
"codice_elemento" => $codice, |
|
"codice_gestore" => $_SESSION["ente"]->codice, |
|
"funzione" => "0", |
|
"codice_utente" => $_SESSION["utente"]->codice, |
|
]; |
|
|
|
$salva_p = new salva(); |
|
$salva_p->debug = false; |
|
$salva_p->nome_tabella = "r_permessi"; |
|
$salva_p->operazione = "INSERT"; |
|
$salva_p->oggetto = $permesso; |
|
$salva_p->save(); |
|
} |
|
return $codice; |
|
} |
|
return NULL; |
|
} |
|
/** |
|
* Trova le schede GUUE secondo i filtri richiesti |
|
* |
|
* @return array |
|
*/ |
|
public static function fetchSchede(array $fields = ["*"], array $filters = [], bool $multiple = true): array |
|
{ |
|
$fields = array_filter($fields, function($field) { |
|
if($field === "*") return true; |
|
if(!myPDO::validFieldName($field)) { |
|
throw new Exception("Invalid field name {$field}"); |
|
} |
|
return true; |
|
}, ARRAY_FILTER_USE_BOTH); |
|
global $pdo; |
|
|
|
$fields_str = implode(",", $fields); |
|
$bind = []; |
|
$filter_str = []; |
|
// Aggiungo soft delete |
|
$filters["soft_delete"] = ["!=", "S"]; |
|
// Per ogni filtro, lo aggiungiamo nei bind e nella query |
|
foreach ($filters as $field => $value) { |
|
if(!myPDO::validFieldName($field)) { |
|
throw new Exception("Invalid field name {$field}"); |
|
} |
|
$operand = "="; |
|
if (is_array($value)) { |
|
$operand = $value[0]; |
|
$value = $value[1]; |
|
} |
|
$bind_name = uniqid(); |
|
if($operand === "IN" && is_array($value)) { |
|
if(!empty($value)) { |
|
$in_str = "{$field} IN ("; |
|
foreach($value as $idx => $val) { |
|
$in_str .= ":{$bind_name}_{$idx},"; |
|
$bind[":{$bind_name}_{$idx}"] = $val; |
|
} |
|
$in_str = rtrim($in_str, ",") . ")"; |
|
$filter_str[] = $in_str; |
|
} |
|
} elseif($operand === "OR" && is_array($value)) { |
|
$in_str = "("; |
|
foreach($value as $idx => $val) { |
|
$subOperand = "="; |
|
if(is_array($val)) { |
|
$subOperand = $val[0]; |
|
$val = $val[1]; |
|
} elseif($val === null) { |
|
$subOperand = "IS"; |
|
} |
|
$in_str .= " {$field} {$subOperand} :{$bind_name}_{$idx} OR"; |
|
$bind[":{$bind_name}_{$idx}"] = $val; |
|
} |
|
$in_str = rtrim($in_str, " OR") . ")"; |
|
$filter_str[] = $in_str; |
|
} else { |
|
$bind[":{$bind_name}"] = $value; |
|
$filter_str[] = "{$field} $operand :{$bind_name}"; |
|
} |
|
} |
|
// Ricostruiamo la query where |
|
if (!empty($filter_str)) { |
|
$filter_str = "WHERE " . implode(" AND ", $filter_str); |
|
} else { |
|
$filter_str = ""; |
|
} |
|
|
|
$limit_str = ""; |
|
if (!$multiple) { |
|
$limit_str = "LIMIT 1"; |
|
} |
|
|
|
$sql = "SELECT {$fields_str} FROM b_guue {$filter_str} ORDER BY codice ASC {$limit_str}"; |
|
$result = $pdo->go($sql, $bind); |
|
$schede = $multiple ? $result->fetchAll(PDO::FETCH_ASSOC) : $result->fetch(PDO::FETCH_ASSOC); |
|
if (!is_array($schede)) { |
|
return []; |
|
} |
|
return $schede; |
|
} |
|
}
|
|
|