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.
1866 righe
65 KiB
1866 righe
65 KiB
<?php |
|
|
|
require_once "bs4.trait.php"; |
|
|
|
trait NuovaPiattaformaAppaltiRenderTrait |
|
{ |
|
|
|
use BS4RenderTrait; |
|
|
|
/** |
|
* Definisce quali ripetizioni sono inibite dall'utente |
|
* |
|
* @var array |
|
*/ |
|
public $locked_repetitions = []; |
|
|
|
/** |
|
* Definisce quali sezioni sono inibite dall'utente |
|
*/ |
|
public $locked_sections = []; |
|
|
|
/** |
|
* Definisce quali elementi sono inibiti per l'utente |
|
* |
|
* @var array |
|
*/ |
|
public $locked_fields = []; |
|
|
|
/** |
|
* Definisce le ripetizioni attuali nel corso del rendering |
|
* |
|
* @var array |
|
*/ |
|
private $current_repetitions = []; |
|
|
|
/** |
|
* Print the form for the selected card. |
|
* |
|
* @return void |
|
*/ |
|
function printForm(): void |
|
{ |
|
$requirements = $this->verifyFormRequirements(); |
|
|
|
if (!$requirements["continue"]) { |
|
?> |
|
<fieldset disabled> |
|
<?php |
|
} |
|
if (!empty($this->form["content"])) { |
|
$this->printSection($this->form["content"]); |
|
} |
|
if (!$requirements["continue"]) { |
|
?> |
|
</fieldset> |
|
<script defer> |
|
document.querySelectorAll(".npa-nav-button").forEach(function(el) { |
|
el.style.display = "none"; |
|
}) |
|
</script> |
|
<?php |
|
} |
|
echo $this->embedJavascriptInDOMDocument(); |
|
} |
|
|
|
/** |
|
* Print Card HTML |
|
* |
|
* @param Array $element |
|
* @param ?String $object |
|
* @param Closure $function |
|
* @return void |
|
*/ |
|
function printGroup(array $element, int $index, ?String $object, bool $is_repetition = false, bool $is_last_repetition = false, bool $print_del_button = true, Closure $function = null): void |
|
{ |
|
// Titolo della card |
|
$title = $element["title"] ?? camelCaseToWords($element["id"]); |
|
// Identifica se il corrente è una ripetizione |
|
$repeatable = !empty($object); |
|
// ID univoco dell'elemento |
|
$identifier = str_replace('.', '_', uniqid("npa.", true)); |
|
// ID univoco del gruppo di ripetizioni |
|
$reference = $repeatable ? "repeatable_" . $element["repeatable_reference"] : ""; |
|
// Decide se la card va stampata o meno (viene stampata per i blocchi singoli o per il primo blcoco di una ripetizione) |
|
$open_card = !$repeatable || !$is_repetition; |
|
$close_card = !$repeatable || $is_last_repetition; |
|
// Evitiamo il card inutile |
|
if (!$repeatable && $element["id"] == "anacForm") { |
|
$open_card = false; |
|
$close_card = false; |
|
} |
|
|
|
if ($repeatable && !$is_repetition && !($this->locked_repetitions[$element["id"]] ?? false)) { |
|
$add_button = $this->makeAddButtonElement($reference, $object); |
|
} |
|
|
|
$is_section_header = ($element["depth"] ?? -1) == 1; |
|
?> |
|
|
|
<?php if ($open_card) : ?> |
|
<div class="col-12"> |
|
<div id="<?= uniqid('card') ?>" class="card mb-3 <?= $is_section_header ? 'section-header' : '' ?>"> |
|
<div class="card-header"> |
|
<div class="row d-flex align-items-center"> |
|
<div class="col"> |
|
<p class="<?= $is_section_header ? 'h4' : 'h5' ?> mb-0 text-muted"><?= $title ?></p> |
|
</div> |
|
<?php if (isset($add_button)) : ?> |
|
<?= $this->button($add_button); ?> |
|
<?php endif; ?> |
|
</div> |
|
</div> |
|
<div class="card-body"> |
|
<?php if ($repeatable && !$is_repetition) : ?> |
|
<div class="repeatable_container" id="<?= $reference ?>"> |
|
<?php endif; ?> |
|
|
|
<?php endif; ?> |
|
<div id="<?= $identifier ?>" data-index="<?= $index ?>" data-context="<?= $element["id"] ?>" class="row <?= $element["depth"] == 0 ? "col-12" : "" ?>"> |
|
<?php |
|
if ($print_del_button && $repeatable && $is_repetition) { |
|
$button = $this->makeDelButtonElement($identifier); |
|
echo $this->button($button); |
|
} |
|
|
|
if (!is_null($function)) { |
|
$function(); |
|
} |
|
?> |
|
</div> <!-- id/context --> |
|
<?php if ($repeatable && $is_last_repetition) : ?> |
|
</div> <!-- repeatable_container --> |
|
<?php endif; ?> |
|
<?php if ($close_card) : ?> |
|
</div> <!-- card-body --> |
|
</div> <!-- card --> |
|
</div> |
|
<?php endif; ?> |
|
|
|
<? |
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
* Stampa una sezione del form |
|
* |
|
* @param array $section Metadati della sezione * @param array $realName Nome finora, stabilito con la notazione numerica per le ripetizioni |
|
* @param boolean $is_repetition Definisce se si tratta di una ripetizione |
|
* @param integer $depth Definisce la profondità di questa iterazione |
|
* @param int $startIndex Definisce se è invocato da add2form, definendo lo start index |
|
* @return void |
|
*/ |
|
public function printSection(array $section, array $realName = [], Bool $is_repetition = false, $depth = 0, int $startIndex = 0): void |
|
{ |
|
if ($startIndex) { |
|
$data = []; |
|
$suggestions = []; |
|
} else { |
|
$data = $this->getCardData(); |
|
$suggestions = $this->getSuggestions(); |
|
} |
|
|
|
foreach ($section as $element) { |
|
|
|
switch ($element["contentType"]) { |
|
|
|
case "reference": |
|
|
|
if (!empty($element["location"]) && file_exists("{$this->path}/{$element["location"]}")) { |
|
|
|
$elements = jsonToArray("{$this->path}/{$element["location"]}"); |
|
if (!empty($elements["content"])) { |
|
|
|
$this->printSection($elements["content"], $realName, false, $depth, $startIndex); |
|
} |
|
} |
|
break; |
|
|
|
case "group": |
|
|
|
// Verify that the element has content to print. |
|
if (!empty($element["content"])) { |
|
$element["depth"] = $depth; |
|
// Check if the element's id attribute needs to be inserted into the input field's name. |
|
if (!($element["_skip_id_name"] ?? false) && !$is_repetition) { |
|
array_push($realName, $element["id"]); |
|
} |
|
|
|
// Check if the element is repeatable and then print a group with a button to add the same element to the form; |
|
if ($element["_repeatable"]) { |
|
|
|
$title = $element["id"]; |
|
$object = $this->makeRepeatableObject(["element" => $element, "realName" => $realName, "codice" => $this->scheda["codice"] ?? $this->info["codice"]]); |
|
|
|
$repeats = 1; |
|
$sectionRealKey = implode(".", $realName); |
|
$values = DotPath::get($data, $sectionRealKey, null, false); |
|
|
|
if (!empty($values) && is_array($values) && array_is_list($values)) { |
|
$repeats = count($values); |
|
} |
|
// Carichiamo il numero di entry se presuggerito |
|
$forcedRepeats = 0; |
|
if (!$this->checkLock()) { |
|
$sectionSuggestions = DotPath::get($suggestions, $sectionRealKey); |
|
if (is_array($sectionSuggestions) && array_is_list($sectionSuggestions)) { |
|
$repeats = max($repeats, count($sectionSuggestions)); |
|
$forcedRepeats = count($sectionSuggestions); |
|
} |
|
} |
|
|
|
|
|
$repeats += $startIndex; |
|
|
|
|
|
|
|
for ($i = $startIndex; $i < $repeats; $i++) { |
|
|
|
$this->current_repetitions[$element["id"]] = $i; |
|
|
|
// Reset the repetition name |
|
$realRepetitionName = $realName; |
|
|
|
// Push the repetition name into the keys array |
|
array_push($realRepetitionName, $i); |
|
if ($element["_card"] ?? true) { |
|
|
|
$element["repeatable_reference"] = $this->establishIdAttributeForElement($realName, $element); |
|
$this->printGroup($element, $i, $object, $is_repetition || $i > 0, $i >= ($repeats - 1), $i + 1 > $forcedRepeats, function () use ($element, $realRepetitionName, $depth) { |
|
|
|
$this->printSection($element["content"], $realRepetitionName, false, $depth + 1); |
|
}); |
|
} else { |
|
|
|
$this->printSection($element["content"], $realRepetitionName); |
|
} |
|
|
|
if ($i >= $repeats - 1) { |
|
unset($this->current_repetitions[$element["id"]]); |
|
} |
|
} |
|
} else { |
|
|
|
if ($element["_card"] ?? true) { |
|
|
|
$this->printGroup($element, 0, null, $is_repetition, false, true, function () use ($element, $realName, $depth) { |
|
|
|
$this->printSection($element["content"], $realName, false, $depth + 1); |
|
}); |
|
} else { |
|
|
|
$this->printSection($element["content"], $realName, $depth + 1); |
|
} |
|
} |
|
array_pop($realName); |
|
} |
|
|
|
break; |
|
|
|
case "auto-filled": |
|
|
|
$element["type"] = "text"; |
|
$element["attrs"]["hidden"] = true; |
|
$element["class"][] = "d-none"; |
|
$element["val"] = "TO-BE-FILLED"; |
|
|
|
case "field": |
|
|
|
if ($element["hidden"] ?? false) { |
|
break; |
|
} |
|
// anacForm.lotti.0.campo |
|
$realKey = $this->establishKeyInDotNotation($realName, $element); |
|
// anacForm.lotti.*.campo |
|
$genericKey = preg_replace("/\.(\d+)/", ".*", $realKey); |
|
$element["name"] = $this->establishNameAttributeForElement($realName, $element); |
|
$element["attrs"]["data-reference"] = $this->establishIdAttributeForElement($realName, $element); |
|
if (empty($element["val"])) { |
|
$element["val"] = DotPath::get($data, $realKey); |
|
} |
|
if (!$this->checkLock()) { |
|
$suggestion = DotPath::get($suggestions, $realKey); |
|
if (!empty($suggestion)) { |
|
if ($suggestion["value"]["options"] ?? false) { |
|
$element["type"] = "select"; |
|
$element["options"] = $suggestion["value"]["options"]; |
|
} else { |
|
if (!empty($suggestion)) { |
|
$suggestion["value"] = htmlspecialchars($suggestion["value"], ENT_QUOTES|ENT_SUBSTITUTE, null, false); |
|
switch ($suggestion["forced"]) { |
|
case NPASuggestionType::FORCED: |
|
$element["val"] = $suggestion["value"]; |
|
$element["attrs"]["readonly"] = true; |
|
if (isset($element["title"])) { |
|
$element["title"] = "🪄 " . $element["title"]; |
|
} |
|
break; |
|
case NPASuggestionType::AUTOFILL: |
|
if (empty($element["val"])) { |
|
$element["val"] = $suggestion["value"]; |
|
} |
|
// La mancanza di break qui è volontaria |
|
case NPASuggestionType::OPTIONAL: |
|
$element["attrs"]["suggested_val"] = "{$suggestion["value"]}"; |
|
if (isset($element["title"])) { |
|
$element["title"] = "💡 " . $element["title"]; |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
if ($element["type"] == "idScheda") { |
|
// Cerchiamo le schede pubblicate |
|
$filterSchede = [ |
|
"stato" => [">=", 50], |
|
"codice_npa" => $this->fascicolo["codice"], |
|
]; |
|
// Se abbiamo un codice lotto, cerchiamole relative allo stesso codice lotto |
|
/*if(! empty($this->lot["codice_lotto_npa"])) { |
|
$filterSchede["codice_lotto"] = $this->lot["codice_lotto_npa"]; |
|
} */ |
|
// Se abbiamo un indicatore del tipo, usiamolo |
|
if (!empty($element["scheda_collegata"])) { |
|
$filterSchede["id_scheda"] = $element["scheda_collegata"]; |
|
} |
|
|
|
$schede = NuovaPiattaformaAppalti::fetchSchede(["id_scheda", "codice", "uuid", "timestamp_creazione"], $filterSchede); |
|
|
|
|
|
$element["type"] = "select"; |
|
$element["options"] = []; |
|
foreach ($schede as $id => $scheda) { |
|
$element["options"][$scheda["uuid"]] = __guue("Scheda ") . "$id #{$scheda['codice']} ({$scheda['uuid']})"; |
|
} |
|
} elseif ($element["type"] == "idContratto") { |
|
// Cerchiamo le schede pubblicate |
|
$filterSchede = [ |
|
"stato" => [">=", 50], |
|
"codice_npa" => $this->fascicolo["codice"], |
|
]; |
|
if (!empty($element["scheda_collegata"])) { |
|
$filterSchede["id_scheda"] = $element["scheda_collegata"]; |
|
} |
|
|
|
$schede = NuovaPiattaformaAppalti::fetchSchede(["id_scheda", "codice", "info", "timestamp_creazione"], $filterSchede); |
|
// Filtriamo per quelle con idContratto |
|
foreach ($schede as $idx => $scheda) { |
|
|
|
$scheda["info"] = unserialize($scheda["info"]); |
|
if ($scheda["info"] && !empty($scheda["info"]->idContratto)) { |
|
$schede[$idx]["uuid"] = $scheda["info"]->idContratto; |
|
} |
|
} |
|
|
|
$element["type"] = "select"; |
|
$element["options"] = []; |
|
foreach ($schede as $scheda) { |
|
if (!empty($scheda["uuid"])) { |
|
$id = $scheda["id_scheda"]; |
|
$element["options"][$scheda["uuid"]] = __guue("ID contratto ottenuto da Scheda ") . "$id #{$scheda['codice']} ({$scheda['uuid']})"; |
|
} |
|
} |
|
} elseif ($element["type"] == "idPianificazione") { |
|
|
|
|
|
$element["type"] = "text"; |
|
//$element["options"] = []; |
|
|
|
//if (!empty($this->fascicolo["id_pianificazione"])) { |
|
// $element["options"][$this->fascicolo["id_pianificazione"]] = __guue("ID pianificazione del fascicolo") . " ({$this->fascicolo['id_pianificazione']})"; |
|
//} |
|
} |
|
foreach($this->locked_sections as $locked_section) { |
|
if(str_starts_with($genericKey, $locked_section)) { |
|
$element["attrs"]["readonly"] = true; |
|
$element["rel"] = []; |
|
} |
|
} |
|
if (in_array($genericKey, $this->locked_fields)) { |
|
$element["attrs"]["readonly"] = true; |
|
} |
|
if (!empty($element["name"])) { |
|
$hidden = $element["type"] === "hidden"; |
|
|
|
|
|
echo $hidden ? "" : '<div class="field-wrapper col-12 col-xl-6">'; |
|
echo $this->{$element["type"]}($element); |
|
echo $hidden ? "" : '</div>'; |
|
} |
|
|
|
break; |
|
|
|
default: |
|
|
|
if (DEVELOP_ENV) { |
|
|
|
dump($element); |
|
throw new Exception("Unmanaged element exception."); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Create the 'id' attribute for the element. |
|
* |
|
* @param mixed $names |
|
* @param mixed $element |
|
* @param mixed $withoutRepetitions |
|
* @return string |
|
*/ |
|
function establishIdAttributeForElement(array $names, array $element): ?string |
|
{ |
|
|
|
if (array_is_list($names)) { |
|
|
|
if (!($element["_skip_id_name"] ?? false)) { |
|
|
|
array_push($names, $element["id"]); |
|
} |
|
|
|
|
|
return implode(".", $names); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
/** |
|
* Establish the key in dot notation |
|
* |
|
* @param Array $names |
|
* @param Array $element |
|
* @return string |
|
*/ |
|
function establishKeyInDotNotation(array $names, array $element): ?string |
|
{ |
|
|
|
if (array_is_list($names)) { |
|
|
|
if (!($element["_skip_id_name"] ?? false)) { |
|
|
|
array_push($names, $element["id"]); |
|
} |
|
|
|
return implode(".", $names); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
/** |
|
* Create the 'name' attribute for the element. |
|
* |
|
* @param Array $names |
|
* @param Array $element |
|
* @return String |
|
*/ |
|
function establishNameAttributeForElement(array $names, array $element): ?string |
|
{ |
|
|
|
if (array_is_list($names)) { |
|
|
|
if (!($element["_skip_id_name"] ?? false)) { |
|
|
|
array_push($names, $element["id"]); |
|
} |
|
|
|
$name = "npa[" . implode("][", $names) . "]"; |
|
|
|
return $name; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
/** |
|
* Make repeatable object for ajax request. |
|
* |
|
* @param Array $settings |
|
* @return String |
|
*/ |
|
public static function makeRepeatableObject(array $settings): String |
|
{ |
|
return base64_encode(gzdeflate(json_encode($settings))); |
|
} |
|
|
|
/** |
|
* Creates the remove repetition button |
|
* |
|
* @param string $unique_identifier |
|
* @return array |
|
*/ |
|
protected function makeDelButtonElement(string $unique_identifier): array |
|
{ |
|
return [ |
|
"name" => "button", |
|
"required" => false, |
|
"size" => "pr-2", |
|
"type" => "button", |
|
"title" => "<span class=\"fa fa-sm fa-times\"></span>", |
|
"attrs" => [ |
|
"class" => ["btn btn-xs btn-danger shadow-none ml-3 align-middle npa-delete-button"], |
|
"style" => " margin-top: 0.2em;", |
|
"onClick" => "elimina('#{$unique_identifier}', 'npa/box'); return false;" |
|
] |
|
]; |
|
} |
|
|
|
/** |
|
* Create the button to duplicate an element in the form |
|
* |
|
* @param String $unique_identifier |
|
* @param Array $object |
|
* @return array |
|
*/ |
|
protected function makeAddButtonElement(String $unique_identifier, String $object): array |
|
{ |
|
|
|
return [ |
|
"name" => "button", |
|
"required" => false, |
|
"size" => "pr-2", |
|
"type" => "button", |
|
"title" => "<span class=\"fa fa-plus\"></span> <span class='ml-1'> " . __guue("Aggiungi") . "</span>", |
|
|
|
"attrs" => |
|
[ |
|
"onClick" => "npa.add2Form('{$unique_identifier}', '{$object}');", |
|
"class" => ["btn btn-sm btn-block btn-primary"] |
|
] |
|
]; |
|
} |
|
|
|
|
|
/** |
|
* Retrieves information about the types of form inputs |
|
* |
|
* @param mixed $form |
|
* @return array |
|
*/ |
|
private function getFormInputsTypes(array $form): array |
|
{ |
|
|
|
$inputs = []; |
|
foreach ($form as $element) { |
|
|
|
if (in_array($element["contentType"], ["field", "auto-filled"])) { |
|
|
|
$inputs[$element["id"]] = [ |
|
"type" => $element["type"], |
|
"format" => $element["format"] ?? "text" |
|
]; |
|
|
|
} else { |
|
|
|
$inputs[$element["id"]] = $this->getFormInputsTypes($element["content"]); |
|
$inputs[$element["id"]]["_required"] = $element["_required"] ?? false; |
|
$inputs[$element["id"]]["_dataType"] = $element["_dataType"] ?? null; |
|
$inputs[$element["id"]]["_repeatable"] = $element["_repeatable"] ?? false; |
|
$inputs[$element["id"]]["contentType"] = $element["contentType"] ?? false; |
|
} |
|
|
|
} |
|
|
|
return $inputs; |
|
|
|
} |
|
|
|
/** |
|
* Generates and outputs the basic JavaScript boilerplate code within the npa component |
|
* |
|
* @return ?String |
|
*/ |
|
public static function printJavascriptBoilerplate(): ?String |
|
{ |
|
|
|
if (!$_SESSION["utente"]->readOnly) { |
|
ob_start(); |
|
?> |
|
<script type="text/javascript"> |
|
let clearDossierConfirm = function(codice, metodo) { |
|
$.ajax({ |
|
|
|
type: "POST", |
|
url: `/backend/npa/ajax/clear_dossier.php`, |
|
data: { |
|
codice: codice, |
|
metodo: metodo, |
|
}, |
|
dataType: "script", |
|
beforeSend: function() { |
|
|
|
$("#wait_div").show(); |
|
|
|
} |
|
|
|
}).fail(function(response) { |
|
|
|
swal(js_dict["error-retry"]); |
|
|
|
}).done(function(response) { |
|
|
|
eval(response); |
|
|
|
}).always(function() { |
|
|
|
$("#wait_div").slideUp('fast'); |
|
|
|
}); |
|
} |
|
// Pulisci fascicolo |
|
let clearDossier = function(e, codice, method, i) { |
|
message = js_dict["msg-conferma"]; |
|
if (method == "nuclear") { |
|
if (!i) i = 0; |
|
message = [ |
|
"Con questa azione tutte le schede create saranno dissociate da questa gara. Sei sicuro?", |
|
"Il Reset Fascicolo creerà una revisione del fascicolo attuale e permetterà l'utente di ricominciare da capo con ANAC. Vuoi proseguire?", |
|
"Questa azione potrebbe avere conseguenze notevoli per l'utente. Vuoi proseguire?", |
|
"Questa azione andrebbe usata solo come soluzione estrema nel caso l'utente sia bloccato nella compilazione e non abbia altri metodi di risoluzione. Sei sicuro?", |
|
"Se ci sono già schede confermate o pubblicate, queste resteranno nei server ANAC. Vuoi proseguire?", |
|
][i]; |
|
} |
|
e.preventDefault(); |
|
Swal({ |
|
title: js_dict["sei-sicuro"], |
|
text: message, |
|
type: "error", |
|
showCancelButton: true, |
|
confirmButtonColor: '#3085d6', |
|
cancelButtonColor: '#d33', |
|
confirmButtonText: js_dict.conferma, |
|
cancelButtonText: js_dict.annulla, |
|
closeOnConfirm: true |
|
}).then((result) => { |
|
|
|
if (result.value) { |
|
|
|
if (method == "nuclear" && i < 5) { |
|
clearDossier(e, codice, method, i + 1); |
|
} else { |
|
clearDossierConfirm(codice, method); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
let sendRequest = function(path, id, card) { |
|
if (typeof window.npa != "undefined") { |
|
window.npa.enableAutosave = false; |
|
} |
|
<? |
|
if ($_SESSION["utente"]->authenticationLevel >= 2) { |
|
?> |
|
$.ajax({ |
|
|
|
type: "POST", |
|
url: "/backend/npa/" + path, |
|
data: { |
|
codice: id, |
|
id_scheda: card |
|
}, |
|
dataType: "script", |
|
beforeSend: function() { |
|
|
|
$("#wait_div").show(); |
|
|
|
} |
|
|
|
}).fail(function(response) { |
|
|
|
swal(js_dict["error-retry"]); |
|
|
|
}).done(function(response) { |
|
|
|
if (typeof table !== typeof undefined) { |
|
|
|
table.ajax.reload(); |
|
|
|
} |
|
|
|
eval(response); |
|
|
|
}).always(function() { |
|
|
|
$("#wait_div").slideUp('fast'); |
|
|
|
}); |
|
<? |
|
} else { |
|
?> |
|
|
|
swal({ |
|
title: js_dict.attenzione, |
|
html: "<div class='text-danger rounded-0'>Per eseguire le operazioni dispositive su NPA è necessario effettuare il <strong>login mediante SPID o CIE</strong> o avere un utenza certificata.</div>", |
|
type: "error", |
|
confirmButtonText: "OK", |
|
confirmButtonColor: "#c00" |
|
}); |
|
return; |
|
|
|
<? |
|
} |
|
?> |
|
|
|
} |
|
|
|
let modifyCard = function(id, card) { |
|
|
|
sendRequest("ajax/modifica.php", id, card); |
|
|
|
} |
|
|
|
let modifyToRectify = function(id, card) { |
|
|
|
sendRequest("ajax/modifica_to_rettifica.php", id, card); |
|
|
|
} |
|
|
|
let rectifyCard = function(id, card) { |
|
|
|
sendRequest("ajax/rettifica.php", id, card); |
|
|
|
} |
|
|
|
let dispatchCard = function(id, card) { |
|
|
|
sendRequest("ajax/invia.php", id, card); |
|
|
|
} |
|
|
|
let verifyCard = function(id, card) { |
|
|
|
sendRequest("ajax/verifica.php", id, card); |
|
|
|
} |
|
let confirmCard = function(id, card) { |
|
sendRequest("ajax/conferma.php", id, card); |
|
|
|
} |
|
let confirmRecoveryCard = function(id, card) { |
|
sendRequest("ajax/recupera_conferma.php", id, card); |
|
|
|
} |
|
|
|
let getCIG = function(id) { |
|
|
|
sendRequest("ajax/cig.php", id); |
|
|
|
} |
|
|
|
let publishCard = function(id, card) { |
|
|
|
sendRequest("ajax/pubblica.php", id, card); |
|
|
|
} |
|
|
|
let requestFvoeAccess = function(codice_npa, codice_lotto, cf) { |
|
<? if ($_SESSION["utente"]->authenticationLevel >= 2) : ?> |
|
$.ajax({ |
|
type: "POST", |
|
url: "/backend/npa/ajax/fvoe-check-accesso.php", |
|
data: { |
|
cf: cf, |
|
codice_fascicolo: codice_npa, |
|
codice_lotto: codice_lotto |
|
}, |
|
dataType: "script", |
|
beforeSend: function() { |
|
$("#wait_div").show(); |
|
} |
|
}).fail(function(response) { |
|
swal(js_dict["error-retry"]); |
|
}).done(function(response) { |
|
eval(response); |
|
}).always(function() { |
|
$("#wait_div").slideUp('fast'); |
|
}); |
|
<? else : ?> |
|
swal({ |
|
title: js_dict.attenzione, |
|
html: "<div class='text-danger rounded-0'>Per eseguire le operazioni dispositive su NPA è necessario effettuare il <strong>login mediante SPID o CIE</strong> o avere un utenza certificata.</div>", |
|
type: "error", |
|
confirmButtonText: "OK", |
|
confirmButtonColor: "#c00" |
|
}); |
|
<? endif; ?> |
|
} |
|
|
|
</script> |
|
<? |
|
return ob_get_clean(); |
|
} |
|
|
|
return null; |
|
} |
|
public static function printPubblicazioneEU($pubblicazione, $slim = false) |
|
{ |
|
if (!empty($pubblicazione->datiPubblicazioneEU) && isset($pubblicazione->datiPubblicazioneEU->noticeId)) : ?> |
|
<?php $codice = $pubblicazione->datiPubblicazioneEU->stato->codice ?? "NA" ?> |
|
<?php if (!$slim) : ?> |
|
<div class="col"> |
|
<?php endif; ?> |
|
<div class="card h-100 text-light bg-<?= $codice == "PUBB" ? 'success' : 'info' ?> border-<?= $pubblicazione->datiPubblicazioneEU->stato->codice == "PUBB" ? 'success' : 'info' ?>"> |
|
<div class="card-header border-<?= $codice == "PUBB" ? 'success' : 'info' ?>"> |
|
<h5 class="mb-0"><strong><i class="fas <?= $codice == "PUBB" ? 'fa-check-circle' : 'fa-cog fa-spin' ?> mr-3"></i>Pubblicazione Europa</strong></h5> |
|
</div> |
|
|
|
<ul class="list-group list-group-flush text-dark"> |
|
<li class="list-group-item">NOTICE-ID: <strong><?= $pubblicazione->datiPubblicazioneEU->noticeId ?? render_spinner() ?></strong></li> |
|
<li class="list-group-item">PUBLICATION-ID: <strong><?= $pubblicazione->datiPubblicazioneEU->publicationId ?? render_spinner() ?></strong></li> |
|
<li class="list-group-item">URL GUUE: <strong><a href="<?= $pubblicazione->datiPubblicazioneEU->publicationUrl ?? "javascript:void(0)" ?>" target="_blank"><?= $pubblicazione->datiPubblicazioneEU->publicationUrl ?? render_spinner() ?></a></strong></li> |
|
<li class="list-group-item">DATA PUBBLICAZIONE: <?= !empty($pubblicazione->datiPubblicazioneEU->dataPubblicazione) ? date("d/m/Y H:i:s", strtotime($pubblicazione->datiPubblicazioneEU->dataPubblicazione)) : render_spinner() ?></li> |
|
</ul> |
|
|
|
</div> |
|
<?php if (!$slim) : ?> |
|
</div> |
|
<?php endif; ?> |
|
<? endif; |
|
} |
|
public static function printPubblicazioneIT($pubblicazione) |
|
{ |
|
if (!empty($pubblicazione->datiPubblicazioneIT) && property_exists($pubblicazione->datiPubblicazioneIT, "idAvvisoPVL")) : ?> |
|
<div class="col"> |
|
<?php $codice = $pubblicazione->datiPubblicazioneIT->stato->codice ?? "NA" ?> |
|
<div class="card h-100 text-light bg-<?= $codice == "PUBB" ? 'success' : 'info' ?> border-<?= $codice == "PUBB" ? 'success' : 'info' ?>"> |
|
<div class="card-header border-<?= $codice == "PUBB" ? 'success' : 'info' ?>"> |
|
<h5 class="mb-0"><strong><i class="fas <?= $codice == "PUBB" ? 'fa-check-circle' : 'fa-cog fa-spin' ?> mr-3"></i>Pubblicazione Italia</strong></h5> |
|
</div> |
|
<ul class="list-group list-group-flush text-dark"> |
|
<li class="list-group-item">ID-AVVISO: <?= $pubblicazione->datiPubblicazioneIT->idAvvisoPVL ?? render_spinner() ?></li> |
|
<?php if (!empty($pubblicazione->datiPubblicazioneIT->dataPubblicazione)) : ?> |
|
<li class="list-group-item">URL PVL: <a href="https://pubblicitalegale.anticorruzione.it/avvisi/<?= $pubblicazione->datiPubblicazioneIT->idAvvisoPVL ?>" target="_blank">https://pubblicitalegale.anticorruzione.it/avvisi/<?= $pubblicazione->datiPubblicazioneIT->idAvvisoPVL ?></a></li> |
|
<?php endif ?> |
|
<li class="list-group-item">DATA PUBBLICAZIONE: <?= !empty($pubblicazione->datiPubblicazioneIT->dataPubblicazione) ? date("d/m/Y H:i:s", strtotime($pubblicazione->datiPubblicazioneIT->dataPubblicazione)) : render_spinner() ?></li> |
|
</ul> |
|
</div> |
|
</div> |
|
<? endif; |
|
} |
|
|
|
public static function getBDNCPLink(String $cig) { |
|
return "https://dati.anticorruzione.it/superset/dashboard/dettaglio_cig/?cig={$cig}"; |
|
} |
|
/** |
|
* Embbed javascript code into DomDocument. |
|
* |
|
* @return String |
|
*/ |
|
private function embedJavascriptInDOMDocument(): void |
|
{ |
|
$feel_rules = addslashes(json_encode($this->form["rules"] ?? [])); |
|
|
|
?> |
|
<style> |
|
#npa-autosave-status { |
|
opacity: 0; |
|
transition: opacity 0.3s ease-out; |
|
} |
|
|
|
.npa-internal-navigation { |
|
white-space: nowrap; |
|
overflow: hidden; |
|
text-overflow: ellipsis; |
|
} |
|
|
|
@media (min-width: 1200px) { |
|
.col-xl-6 { |
|
flex: 1 0 50%; |
|
max-width: 100%; |
|
} |
|
} |
|
|
|
/* Workaround for excessive padding */ |
|
.form-group.col-sm-12 { |
|
padding: 0 !important; |
|
} |
|
|
|
/* Workaround for tab-pane row */ |
|
.tab-pane .row .active { |
|
display: flex !important; |
|
} |
|
|
|
.tab-content>.active.row { |
|
display: flex !important; |
|
} |
|
|
|
.btn-xs { |
|
padding: 0 0.4rem; |
|
font-size: 0.8rem; |
|
} |
|
|
|
#master-fieldset>.col-12 { |
|
padding-left: 0 !important; |
|
padding-right: 0 !important; |
|
} |
|
</style> |
|
<script type="text/javascript"> |
|
if (typeof window.npa === "undefined") { |
|
|
|
const npa = class { |
|
|
|
constructor() { |
|
this.npaNaviObserver = null; |
|
this.iconSuccess = "<i class='fa fa-check text-success-light mr-1'></i>"; |
|
this.iconFail = "<i class='fa fa-exclamation-triangle text-danger-light mr-1'></i>"; |
|
this.lastVersion = null; |
|
this.locked = <?= $this->checkLock() ? 'true' : 'false' ?>; |
|
this.enableAutosave = !this.locked; |
|
} |
|
|
|
ready() { |
|
f_ready(); |
|
}; |
|
bindEvents() { |
|
const self = this; |
|
self.groupRepeatables(); |
|
window.addOptionalSuggestionsButtons(); |
|
}; |
|
|
|
|
|
scrollToSection(id) { |
|
document.querySelector(`#${id}`).scrollIntoView({ |
|
block: 'start', |
|
behavior: 'smooth' |
|
}) |
|
} |
|
makeNavigator() { |
|
const self = this; |
|
// Inizializziamo il toggle per mostrare o meno la barra di navigazione piccola |
|
if (self.npaNaviObserver == null) { |
|
const commands = document.getElementById("guue-commands"); |
|
const bigNavPlaceholder = document.getElementById("npa-big-nav-placeholder"); |
|
const bigNav = document.getElementById("npa-big-nav"); |
|
const smallNav = document.getElementById("npa-small-nav"); |
|
let observer = new IntersectionObserver(function(entries) { |
|
entries.forEach((entry) => { |
|
if (entry.isIntersecting) { |
|
smallNav.style.height = "0"; |
|
smallNav.style.opacity = "0"; |
|
commands.classList.remove("fixed"); |
|
} else { |
|
smallNav.style.display = "flex"; |
|
smallNav.style.height = "40px"; |
|
smallNav.style.opacity = "1"; |
|
commands.classList.add("fixed"); |
|
} |
|
}); |
|
}); |
|
observer.observe(bigNavPlaceholder); |
|
self.npaNaviObserver = observer; |
|
} |
|
|
|
const firstLevelHeaders = document.querySelectorAll("#master-fieldset .section-header"); |
|
const container = document.querySelector("#guue-sections > .list-group "); |
|
let links = []; |
|
firstLevelHeaders.forEach(function(element) { |
|
if (!element.classList.contains("d-none")) { |
|
|
|
const headerContent = element.querySelector(".col-12 > .card > .card-header .row > .col > p"); |
|
|
|
let linkContainer = document.createElement("button"); |
|
linkContainer.classList.add("list-group-item"); |
|
linkContainer.classList.add("npa-internal-navigation"); |
|
linkContainer.classList.add("text-left"); |
|
linkContainer.onclick = function(e) { |
|
self.scrollToSection(element.id); |
|
e.preventDefault(); |
|
}; |
|
let link = document.createElement("span"); |
|
|
|
// Creiamo un observer |
|
let observer = new IntersectionObserver(function(entries) { |
|
entries.forEach((entry) => { |
|
if (entry.isIntersecting) { |
|
linkContainer.classList.add("active"); |
|
} else { |
|
linkContainer.classList.remove("active"); |
|
} |
|
}); |
|
}, { |
|
'threshold': element.clientHeight > 1000 ? 0.05 : 0.15 |
|
}); |
|
observer.observe(element); |
|
|
|
link.innerText = headerContent.textContent; |
|
|
|
linkContainer.append(link) |
|
|
|
links.push(linkContainer); |
|
} |
|
}) |
|
links.forEach(function(link) { |
|
container.appendChild(link); |
|
}) |
|
} |
|
add2Form(target, data) { |
|
const self = this; |
|
let repeatable_target = target; |
|
let container = document.getElementById(repeatable_target); |
|
let iterations = Number(container.childElementCount); |
|
if (container.dataset.hasTabs) { |
|
iterations = Number(container.dataset.iterations); |
|
} |
|
|
|
$.ajax({ |
|
type: "POST", |
|
url: "ajax/add2Form.php", |
|
data: { |
|
data: data, |
|
index: iterations + 1, |
|
}, |
|
dataType: "html", |
|
beforeSend: function() { |
|
|
|
$("#wait_div").show(); |
|
|
|
} |
|
}) |
|
.fail(function(response) { |
|
|
|
swal(js_dict["error-retry"]); |
|
|
|
}) |
|
.done(function(response) { |
|
|
|
let newChild = document.createElement("template"); |
|
newChild.innerHTML = response.trim(); |
|
newChild = container.appendChild(newChild.content.firstChild); |
|
// Assicuriamoci di eseguire eventuali boilerplate dei centri di costo |
|
const boiler = newChild.querySelectorAll("script[id^=boiler_]").forEach(function(el) { |
|
eval(el.innerText); |
|
}); |
|
// Make new elements respond |
|
self.ready(); |
|
self.bindEvents(); |
|
}) |
|
.always(function() { |
|
|
|
$("#wait_div").slideUp('fast'); |
|
|
|
}); |
|
|
|
}; |
|
convertiRipetizioniInArray(dizionario) { |
|
// Verifica se l'oggetto è un dizionario |
|
if (typeof dizionario !== 'object' || dizionario === null) { |
|
return dizionario; |
|
} |
|
|
|
// Verifica se il dizionario contiene ripetizioni |
|
if (Object.keys(dizionario).some(key => /\d+/.test(key))) { |
|
// Se ci sono ripetizioni, trasforma il dizionario in un array |
|
var array = Object.keys(dizionario) // PD |
|
//.filter(key => key.startsWith('rep_')) |
|
.map(key => this.convertiRipetizioniInArray(dizionario[key])); |
|
|
|
if (array.length == 1 && array[0] == null) { |
|
return null; |
|
} |
|
return array; |
|
} |
|
|
|
// Altrimenti, continua la ricorsione sugli elementi del dizionario |
|
for (let key in dizionario) { |
|
dizionario[key] = this.convertiRipetizioniInArray(dizionario[key]); |
|
} |
|
if ("idTipologica" in dizionario) { |
|
dizionario = ("codice" in dizionario && dizionario["codice"] != null) ? { |
|
"codice": dizionario["codice"], |
|
"idTipologica": dizionario["idTipologica"] |
|
} : null; |
|
} |
|
return dizionario; |
|
} |
|
serializeForm(form) { |
|
const formData = new FormData(form); |
|
const jsonObject = {}; |
|
|
|
// Helper function to set a value deep into the object |
|
function setValue(path, value, obj) { |
|
const keys = path.match(/[^[\]]+/g); // Split by brackets and remove empty strings |
|
let current = obj; |
|
|
|
for (let i = 0; i < keys.length; i++) { |
|
const key = keys[i]; |
|
if (i === keys.length - 1) { |
|
// Last key, set the value |
|
if (Array.isArray(current[key])) { |
|
current[key].push(value); |
|
} else if (key.match(/^\d+$/) && !current[key]) { |
|
// Treat as array element if key is a number |
|
current[key] = value; |
|
} else if (current[key] && typeof current[key] === 'object') { |
|
// Existing object, convert to array if needed and append |
|
if (Array.isArray(current[key])) { |
|
current[key].push(value); |
|
} else { |
|
current[key] = [current[key], value]; |
|
} |
|
} else { |
|
current[key] = value; |
|
} |
|
} else { |
|
// Traverse/create objects/arrays as needed |
|
if (!current[key]) { |
|
current[key] = keys[i + 1].match(/^\d+$/) ? [] : {}; |
|
} |
|
current = current[key]; |
|
} |
|
} |
|
} |
|
for (const [key, value] of formData.entries()) { |
|
setValue(key, value, jsonObject); |
|
} |
|
|
|
return jsonObject; |
|
} |
|
|
|
// Evento di salvataggio con verifica |
|
salva(e) { |
|
window.npa.enableAutosave = false; |
|
$('#bozza-input').val(0); |
|
|
|
// Step 1. Validazione interna (rel) |
|
var errors = []; |
|
document.querySelectorAll("#npa-form *[rel]").forEach(function(el) { |
|
var error = valida($(el)); |
|
if (error.length > 0) { |
|
errors = errors.concat(error); |
|
} |
|
}) |
|
|
|
if (errors.length > 0) { |
|
swal({ |
|
title: js_dict.attenzione, |
|
html: "<div class='text-danger rounded-0'>" + errors.join('<br>') + "</div>", |
|
type: "error", |
|
confirmButtonText: "OK", |
|
confirmButtonColor: "#c00" |
|
}).then((result) => { |
|
window.npa.enableAutosave = true; |
|
}); |
|
|
|
e.preventDefault(); |
|
|
|
return; |
|
} |
|
|
|
if (typeof feelin !== "undefined") { |
|
errors = this.checkFeelRules(); |
|
if (errors.length > 0) { |
|
swal({ |
|
title: "Validazione DMN", |
|
html: "<p>Le seguenti regole DMN non sono soddisfatte:</p><div class='rounded-0 text-left'><ul><li>" + errors.join('</li><li>') + "</li></ul></div>", |
|
type: "warning", |
|
showCancelButton: true, |
|
confirmButtonColor: '#3085d6', |
|
cancelButtonColor: '#d33', |
|
confirmButtonText: "Procedi comunque", |
|
cancelButtonText: "Rivedi il form", |
|
closeOnConfirm: true |
|
}).then((result) => { |
|
if (result.value) { |
|
this.onSubmit(); |
|
} |
|
window.npa.enableAutosave = true; |
|
}); |
|
e.preventDefault(); |
|
|
|
return; |
|
} |
|
} |
|
this.onSubmit(); |
|
|
|
} |
|
onSubmit() { |
|
|
|
const form = document.getElementById('npa-form'); |
|
const jsonResult = JSON.stringify(this.serializeForm(form), null, 2); |
|
let submitURI = form.action + window.location.search; |
|
$("#wait_div").show(); |
|
|
|
// Facciamo una richiesta post all'autosave |
|
fetch( |
|
submitURI, { |
|
method: 'post', |
|
body: jsonResult, |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
} |
|
) |
|
.then(response => response.text()) |
|
.then(response => { |
|
eval(response); |
|
}) |
|
.catch(ex => { |
|
swal(js_dict["error-retry"]); |
|
console.error(`[NPA] ❌ Salvataggio fallito: ${ex}`); |
|
}) |
|
.finally (_ => { |
|
$("#wait_div").slideUp('fast'); |
|
}); |
|
|
|
} |
|
// Evento di resync scheda |
|
syncCard(e) { |
|
const self = this; |
|
e.preventDefault(); |
|
Swal({ |
|
title: `Sincronizza scheda`, |
|
text: `Quest'azione sincronizzerà questa scheda con i contenuti presenti sui server ANAC. Vuoi continuare? I dati attuali saranno persi.`, |
|
type: "warning", |
|
showCancelButton: true, |
|
confirmButtonColor: '#3085d6', |
|
cancelButtonColor: '#d33', |
|
confirmButtonText: js_dict.conferma, |
|
cancelButtonText: js_dict.annulla, |
|
closeOnConfirm: true |
|
}).then((result) => { |
|
if (result.value) { |
|
|
|
var searchParams = new URLSearchParams(window.location.search); |
|
const codice = searchParams.get("codice"); |
|
if (!codice) return; |
|
$.ajax({ |
|
type: "POST", |
|
url: `ajax/sync_card.php`, |
|
data: { |
|
codice: codice, |
|
}, |
|
dataType: "script", |
|
beforeSend: function() { |
|
$("#wait_div").show(); |
|
} |
|
}).fail(function(response) { |
|
swal(js_dict["error-retry"]); |
|
}).done(function(response) { |
|
eval(response); |
|
}).always(function() { |
|
$("#wait_div").slideUp('fast'); |
|
}); |
|
} |
|
}); |
|
} |
|
// Evento di creazione rettifica |
|
creaRevisione(e, tipo) { |
|
const self = this; |
|
e.preventDefault(); |
|
Swal({ |
|
title: `Crea ${tipo}`, |
|
text: `Quest'azione creerà una nuova scheda di ${tipo} per la scheda corrente. Vuoi proseguire?`, |
|
type: "warning", |
|
showCancelButton: true, |
|
confirmButtonColor: '#3085d6', |
|
cancelButtonColor: '#d33', |
|
confirmButtonText: js_dict.conferma, |
|
cancelButtonText: js_dict.annulla, |
|
closeOnConfirm: true |
|
}).then((result) => { |
|
if (result.value) { |
|
Swal({ |
|
title: `Vuoi conservare i dati?`, |
|
text: `Vuoi portare i dati compilati nella scheda precedente sulla ${tipo}?`, |
|
type: "info", |
|
showCancelButton: true, |
|
confirmButtonColor: '#3085d6', |
|
cancelButtonColor: '#d33', |
|
confirmButtonText: "Si", |
|
cancelButtonText: "No", |
|
closeOnConfirm: true |
|
}).then((result) => { |
|
if (!result.value && result.dismiss != "cancel") return; |
|
let conserva_dati = result.value ? 1 : 0; |
|
|
|
var searchParams = new URLSearchParams(window.location.search); |
|
const codice = searchParams.get("codice"); |
|
if (!codice) return; |
|
$.ajax({ |
|
type: "POST", |
|
url: `ajax/create_revision.php`, |
|
data: { |
|
tipo: tipo, |
|
codice: codice, |
|
conserva_dati: conserva_dati, |
|
}, |
|
dataType: "script", |
|
beforeSend: function() { |
|
$("#wait_div").show(); |
|
} |
|
}).fail(function(response) { |
|
swal(js_dict["error-retry"]); |
|
}).done(function(response) { |
|
eval(response); |
|
}).always(function() { |
|
$("#wait_div").slideUp('fast'); |
|
}); |
|
}) |
|
} |
|
}); |
|
} |
|
// Evento di cancellazione scheda |
|
eliminaScheda(e, codice) { |
|
const self = this; |
|
e.preventDefault(); |
|
Swal({ |
|
title: js_dict["sei-sicuro"], |
|
text: js_dict["msg-conferma"], |
|
type: "error", |
|
showCancelButton: true, |
|
confirmButtonColor: '#3085d6', |
|
cancelButtonColor: '#d33', |
|
confirmButtonText: js_dict.conferma, |
|
cancelButtonText: js_dict.annulla, |
|
closeOnConfirm: true |
|
}).then((result) => { |
|
if (result.value) { |
|
self.eliminaSchedaConfirm(codice); |
|
} |
|
}); |
|
} |
|
eliminaSchedaConfirm(codice) { |
|
$.ajax({ |
|
|
|
type: "POST", |
|
url: `ajax/delete.php`, |
|
data: { |
|
codice: codice, |
|
}, |
|
dataType: "script", |
|
beforeSend: function() { |
|
|
|
$("#wait_div").show(); |
|
|
|
} |
|
|
|
}).fail(function(response) { |
|
|
|
swal(js_dict["error-retry"]); |
|
|
|
}).done(function(response) { |
|
|
|
eval(response); |
|
|
|
}).always(function() { |
|
|
|
$("#wait_div").slideUp('fast'); |
|
|
|
}); |
|
} |
|
// Evento di salvataggio in bozza |
|
salvaBozza(e) { |
|
window.npa.enableAutosave = false; |
|
$('#bozza-input').val(1); |
|
this.onSubmit(); |
|
} |
|
|
|
autoSave() { |
|
const self = window.npa; |
|
if (!self.enableAutosave) { |
|
return; |
|
} |
|
|
|
const form = document.getElementById("npa-form"); |
|
|
|
if (!form || !(form instanceof HTMLFormElement)) { |
|
return; |
|
} |
|
let formData = self.serializeForm(form); |
|
formData["bozza"] = 1; |
|
const serializedFormData = JSON.stringify(formData, null, 2); |
|
|
|
// Verifichiamo che il form sia cambiato |
|
if (self.lastVersion != null) { |
|
if (serializedFormData == self.lastVersion) { |
|
return; |
|
} |
|
} else { |
|
self.lastVersion = serializedFormData; |
|
return; |
|
} |
|
|
|
console.log("[NPA] ⌛ Autosalvataggio in corso"); |
|
self.lastVersion = serializedFormData; |
|
// Otteniamo l'url di submit |
|
let submitURI = form.action + window.location.search; |
|
|
|
const autosaveStatus = document.getElementById("npa-autosave-status"); |
|
|
|
|
|
// Facciamo una richiesta post all'autosave |
|
fetch( |
|
submitURI, { |
|
method: 'post', |
|
body: serializedFormData, |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
} |
|
) |
|
.then(response => response.text()) |
|
.then(response => { |
|
|
|
|
|
// Estraiamo la query dalla risposta |
|
// window.location.href="/backend/npa/edit.php?codice=19&scheda=P1_16&modulo_riferimento=gare&id_riferimento=507" |
|
let split_response = response.split("href=", 2); |
|
// ['window.location.href=', ['"/backend/npa/edit.php?codice=19&scheda=P1_16&modulo_riferimento=gare&id_riferimento=507"'] |
|
if (split_response.length != 2) { |
|
throw new Error("[1] Impossibile determinare l'id della scheda dall'autosalvataggio") |
|
} |
|
split_response = split_response[1].split("?", 2); |
|
// ['"/backend/npa/edit.php', 'codice=19&scheda=P1_16&modulo_riferimento=gare&id_riferimento=507"'] |
|
if (split_response.length != 2) { |
|
throw new Error("[2] Impossibile determinare l'id della scheda dall'autosalvataggio") |
|
} |
|
// Settiamola nell'url attuale senza ricaricare la pagina |
|
// ?codice=19&scheda=P1_16&modulo_riferimento=gare&id_riferimento=507&autosave=1 |
|
const currentParams = new URLSearchParams(window.location.search); |
|
if (!currentParams.get('codice') || currentParams.get('codice') == "0") { |
|
window.location.search = `?${split_response[1].trimEx("\"")}`; |
|
} |
|
|
|
const now = new Date(); |
|
autosaveStatus.innerHTML = `${self.iconSuccess} Ultimo autosalvataggio alle ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; |
|
autosaveStatus.classList.remove("alert-danger"); |
|
autosaveStatus.classList.add("alert-success"); |
|
autosaveStatus.style.opacity = "1"; |
|
|
|
|
|
}) |
|
.catch(ex => { |
|
autosaveStatus.innerHTML = `${npa.iconFail} Attenzione: l'ultimo autosalvataggio è fallito`; |
|
autosaveStatus.classList.remove("alert-success"); |
|
autosaveStatus.classList.add("alert-danger"); |
|
console.error(`[NPA] ❌ Autosalvataggio fallito: ${ex}`); |
|
}) |
|
} |
|
generateRandomID() { |
|
var S4 = function() { |
|
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); |
|
}; |
|
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4()); |
|
} |
|
findClosest(reference, arr) { |
|
let closest = null; |
|
let closestDistance = Infinity; |
|
|
|
arr.forEach((dom) => { |
|
let cur = dom; |
|
let distance = 0; |
|
|
|
while (cur && cur !== reference && cur !== document.body) { |
|
cur = cur.parentNode; |
|
distance++; |
|
} |
|
|
|
if (cur === reference && distance < closestDistance) { |
|
closest = dom; |
|
closestDistance = distance; |
|
} |
|
}); |
|
|
|
return closest; |
|
} |
|
groupRepeatables() { |
|
const self = this; |
|
document.querySelectorAll(".repeatable_container").forEach(function(el) { |
|
|
|
let i = 0; |
|
let tabs = null; |
|
let tabHeaders = null; |
|
let hasTabs = el.dataset.hasTabs; |
|
let selectedBind = ""; |
|
|
|
let toSelect = null; |
|
// Aggiunta o rimozione |
|
if (hasTabs) { |
|
// Otteniamo la tab correntemente selezionata |
|
selectedBind = el.querySelector(".nav-link.active").dataset.bindtarget; |
|
|
|
// Otteniamo, puliamo e rimuoviamo i vecchi header |
|
tabHeaders = el.removeChild(document.getElementById(el.dataset.tabHeadersId)); |
|
tabHeaders.innerHTML = ""; |
|
// Otteniamo e rimuoviamo le vechie tab |
|
tabs = el.removeChild(document.getElementById(el.dataset.tabsId)); |
|
|
|
// I vecchi elementi in tab vengono trasposti dentro il repeatable nell'ordine corretto |
|
let tempContainer = []; |
|
let index = 0; |
|
tabs.childNodes.forEach(function(oldChild) { |
|
if (oldChild.nodeType !== Node.ELEMENT_NODE) |
|
return; |
|
tempContainer.push(oldChild); |
|
}); |
|
|
|
// Iteriamo i figli nuovi e verifichiamo che ce ne |
|
// sia solo uno nuovo. |
|
el.childNodes.forEach(function(newChild) { |
|
if (newChild.id) { |
|
if (toSelect === null) { |
|
if (newChild.id) { |
|
toSelect = newChild; |
|
} |
|
} else { |
|
// Ne abbiamo più di uno, non selezioneremo nulla |
|
toSelect = false; |
|
} |
|
} |
|
}); |
|
tempContainer.forEach(function(oldChild) { |
|
el.insertBefore(oldChild, el.children[index++]); |
|
}); |
|
// In questo modo abbiamo vecchi figli e poi i nuovi figli e possiamo far ricreare header e tutto |
|
} else { |
|
// Creiamo l'header |
|
tabHeaders = document.createElement("ul"); |
|
tabHeaders.classList.add("nav"); |
|
tabHeaders.classList.add("nav-tabs"); |
|
tabHeaders.classList.add("npa-tabs"); |
|
tabHeaders.classList.add("px-4"); |
|
tabHeaders.setAttribute("role", "tablist"); |
|
tabHeaders.id = self.generateRandomID(); |
|
// Creiamo il contenitore di tabs |
|
tabs = document.createElement("div"); |
|
tabs.classList.add("tab-content"); |
|
tabs.id = self.generateRandomID(); |
|
|
|
// Assegnamo id casuali per poterli riferire dopo |
|
el.dataset.hasTabs = true; |
|
el.dataset.tabHeadersId = tabHeaders.id; |
|
el.dataset.tabsId = tabs.id; |
|
|
|
i = 0; |
|
} |
|
let straightToTabs = []; |
|
|
|
let iteration = 0; |
|
el.childNodes.forEach(function(child) { |
|
if (child.nodeType !== Node.ELEMENT_NODE) { |
|
return; |
|
} |
|
// Header della tab |
|
let tabLi = document.createElement("li"); |
|
tabLi.classList.add("nav-item"); |
|
tabLi.setAttribute("role", "presentation"); |
|
let tabButton = document.createElement("a"); |
|
tabButton.classList.add("nav-link"); |
|
tabButton.classList.add("ignore-lock"); |
|
|
|
tabButton.dataset.toggle = "tab"; |
|
tabButton.dataset.target = `#${child.id}`; |
|
tabButton.style.minWidth = "90px"; |
|
tabButton.style.textAlign = "left"; |
|
tabLi.appendChild(tabButton); |
|
tabHeaders.appendChild(tabLi); |
|
|
|
if (Number(child.dataset.index) > Number(iteration)) { |
|
iteration = Number(child.dataset.index); |
|
} |
|
|
|
// Creo un ID univoco per associare facilmente tab button e tab |
|
if (child.dataset.bind) { // Riportiamo quello esistente se già c'è |
|
// Nessuna azione necessaria |
|
} else { |
|
child.dataset.bind = self.generateRandomID(); |
|
} |
|
tabButton.dataset.bindtarget = child.dataset.bind; |
|
|
|
|
|
let tabText = document.createElement("span"); |
|
tabText.classList.add("nav-link-text"); |
|
tabButton.appendChild(tabText); |
|
tabText.innerText = i + 1; |
|
let found = []; |
|
for (let key of ["denominazione", "lotIdentifier", "cig"]) { |
|
const el = child.querySelector(`input[data-reference$='${key}']`); |
|
if (el && el.value) { |
|
found.push(el); |
|
} |
|
} |
|
if (found.length > 0) { |
|
if (found.length === 1) { |
|
found = found[0]; |
|
} else { |
|
found = self.findClosest(child, found); |
|
} |
|
tabText.innerText = found.value; |
|
} |
|
// Tab in se |
|
child.classList.remove("col-12"); |
|
child.classList.add("tab-pane"); |
|
child.classList.add("fade"); |
|
child.classList.add("row"); |
|
straightToTabs.push(child); |
|
// Cerchiamo di recuperare il tasto di eliminazione |
|
if (i > 0) { |
|
let deleteButton = child.querySelector(".npa-delete-button"); |
|
if (deleteButton) { |
|
deleteButton.style.display = "inline"; |
|
deleteButton.parentNode.style.display = "none"; |
|
tabButton.appendChild(deleteButton.cloneNode(true)); |
|
deleteButton.style.display = "none"; |
|
} |
|
} |
|
i++; |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
tabHeaders.style.display = "flex"; |
|
tabHeaders.classList.add("mb-3"); |
|
|
|
straightToTabs.forEach(function(child) { |
|
tabs.appendChild(child); |
|
}); |
|
el.dataset.iterations = iteration; |
|
el.appendChild(tabHeaders); |
|
el.appendChild(tabs); |
|
|
|
// Se abbiamo un solo elemento non mostriamo le tab |
|
if (i == 1) { |
|
tabHeaders.style.display = "none"; |
|
} |
|
// Una volta creati andiamo a caccia dell'attivo |
|
let toBeActive = el.querySelector(`.nav-link[data-bindtarget]`) |
|
// Cerchiamo di ripristinare quello che era già selezionato ove possibile |
|
if (selectedBind.length > 0) { |
|
let selectedActive = el.querySelector(`.nav-link[data-bindtarget="${selectedBind}"]`); |
|
if (selectedActive) { |
|
toBeActive = selectedActive; |
|
} |
|
} |
|
// Settiamo attive sia tab button che child |
|
if (toBeActive) { |
|
// Però se abbiamo invece da selezionare uno nuovo |
|
// perchè è quello appena selezionato |
|
if (toSelect) { |
|
// toBeActive viene deselezionato |
|
toBeActive.classList.remove("active"); |
|
// Cerchiamo la sua tab |
|
const child = document.querySelector(`[data-bind="${toBeActive.dataset.bindtarget}"]`); |
|
if (child) { |
|
child.classList.remove("show") |
|
child.classList.remove("active"); |
|
} |
|
// e quindi toSelect diventa l'effettivo toBeActive |
|
toBeActive = document.querySelector(`[data-bindtarget="${toSelect.dataset.bind}"]`); |
|
} |
|
toBeActive.classList.add("active"); |
|
// Cerchiamo la sua tab |
|
const child = document.querySelector(`[data-bind="${toBeActive.dataset.bindtarget}"]`); |
|
if (child) { |
|
child.classList.add("show") |
|
child.classList.add("active"); |
|
} |
|
|
|
} |
|
|
|
}); |
|
} |
|
checkFeelRules() { |
|
const form = document.getElementById("npa-form"); |
|
if (!form || !(form instanceof HTMLFormElement)) { |
|
return []; |
|
} |
|
const formData = new FormData(form); |
|
|
|
var context = {}; |
|
const isDate = str => { |
|
let [d, M, y, h, m, s] = str.split(/[: T Z /]/); |
|
return (y && M > 0 && M <= 12 && d > 0 && d <= 31) ? true : false; |
|
} |
|
const toDate = str => { |
|
let [d, M, y, h, m, s] = str.split(/[: T Z /]/); |
|
h = h ? `${h}` : "00"; |
|
m = m ? `${m}` : "00"; |
|
s = s ? `${s}` : "00"; |
|
let newDateStr = `${y}-${M}-${d}T${h.padStart(2, '0')}:${m.padStart(2, '0')}:${s.padStart(2, '0')}`; |
|
return Date.parse(newDateStr); |
|
} |
|
// creiamo il contesto per feel |
|
for (const pair of formData.entries()) { |
|
var name = pair[0]; |
|
var value = pair[1]; |
|
// cerchiamo i campi anac |
|
const find = "npa[anacForm]" |
|
if (name.indexOf(find) == 0) { |
|
name = name.substr(find.length); |
|
// Dividiamo per i tag in quadre |
|
var splitName = name.split("["); |
|
var tmpname = splitName.join(""); |
|
splitName = tmpname.split("]").filter(Boolean); |
|
// Construiamo il contesto con i path ottenuti |
|
var contextPointer = context; |
|
for (var i = 0; i < splitName.length; i++) { |
|
var namePart = splitName[i]; |
|
if (i == splitName.length - 1) { |
|
value = value.length == 0 ? null : value; |
|
if (typeof value == "string") { |
|
if (value == "false") { |
|
value = false; |
|
} else if (value == "true") { |
|
value = true; |
|
} else if (value == "null") { |
|
value = null; |
|
} else if (isDate(value)) { |
|
value = toDate(value); |
|
} else if (!isNaN(value)) { |
|
value = parseFloat(value); |
|
if (value == 0) value = 0; |
|
} |
|
|
|
} |
|
if (namePart == "codice" && typeof value == "number") { |
|
value = value.toString(); |
|
} |
|
if (namePart == "codice" && typeof value == "number") { |
|
value = value.toString(); |
|
} |
|
contextPointer[namePart] = value; |
|
} else { |
|
if (contextPointer[namePart] === undefined) { |
|
contextPointer[namePart] = {} |
|
} |
|
contextPointer = contextPointer[namePart]; |
|
} |
|
} |
|
// Rendiamo le ripetizioni degli array |
|
} |
|
} |
|
|
|
context = this.convertiRipetizioniInArray(context); |
|
|
|
var errors = []; |
|
|
|
for (var rule of window.npaFeelRules) { // |
|
var failed_references = []; |
|
// Verifichiamo che la regola possa essere eseguita |
|
|
|
// Per ogni rirerimento della regola |
|
for (var reference of rule.references) { |
|
// Dividiamo il riferimento nel suo percorso |
|
var reference_path = reference.split("."); |
|
// Teniamo da parte un puntatore al contesto |
|
var reference_pointer = context; |
|
// Per ogni parte del path |
|
for (var reference_segment of reference_path) { |
|
// Se siamo arrivati ad una tipologica e abbiamo null, c'è e possiamo star tranquilli |
|
if ((reference_segment == "codice" || reference_segment == "idTipologiche") && reference_pointer == null) { |
|
break; |
|
} |
|
if (reference_pointer == null) continue; |
|
reference_pointer = reference_pointer[reference_segment]; |
|
// Se è undefined non la abbiamo |
|
if (typeof reference_pointer == "undefined" || reference_pointer === null) { |
|
failed_references.push(reference); |
|
break; |
|
// Se troviamo per caso un array proseguiamo sempre con il primo membro |
|
} else if (Array.isArray(reference_pointer)) { |
|
reference_pointer = reference_pointer[0]; |
|
} |
|
} |
|
|
|
console.log(`${reference} = ${reference_pointer} (${typeof reference_pointer})`); |
|
|
|
} |
|
if (failed_references.length > 0) { |
|
/*console.warn(`La regola '${rule.description}' non può essere validata:`); |
|
console.warn(`I seguenti riferimenti non sono soddisfatti:`); |
|
for (var failed_reference of failed_references) { |
|
console.warn(`- ${failed_reference}`); |
|
}*/ |
|
} else { |
|
var fail = rule.feel.length > 0; |
|
for (var feel_eval of rule.feel) { |
|
fail = fail && feelin.unaryTest( |
|
feel_eval, context |
|
); |
|
} |
|
if (fail) { |
|
errors.push(rule.description + `<p class="mt-1 mb-0"><strong>Campi coinvolti</strong>: <small><ul><li> ${rule.references.join('</li><li>')}</li></ul></small></p>`); |
|
console.error(`[NPA] ❌ Validazione FEEL non passata: ${rule.description}`); |
|
for (var feel_eval of rule.feel) { |
|
console.log(feel_eval); |
|
} |
|
} |
|
} |
|
} |
|
return errors; |
|
} |
|
isElementVisible(element) { |
|
if (!(element instanceof Element)) { |
|
return true; |
|
} |
|
|
|
var style = window.getComputedStyle(element); |
|
|
|
if (style.display === 'none' || style.visibility === 'hidden') { |
|
return false; |
|
} |
|
|
|
return this.isElementVisible(element.parentNode); |
|
} |
|
notifyEmpty() { |
|
const self = this; |
|
let hasVisibleFields = false; |
|
const visibleInputs = document.querySelectorAll("#master-fieldset input:not([type='hidden']):not([hidden])"); |
|
visibleInputs.forEach(function(el) { |
|
// Ce ne basta trovare uno |
|
if (hasVisibleFields) return; |
|
if (self.isElementVisible(el)) { |
|
hasVisibleFields = true; |
|
} |
|
}) |
|
if (!hasVisibleFields) { |
|
document.getElementById("npa-empty-form").classList.remove("d-none"); |
|
} |
|
} |
|
|
|
} |
|
window.npa = document.npa = new npa(); |
|
|
|
|
|
} |
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
var npaFeelRules = "<?= ($feel_rules) ?>"; |
|
|
|
if (!npa.locked) { |
|
window.npaFeelRules = JSON.parse(npaFeelRules); |
|
const form = document.getElementById("npa-form"); |
|
if (!form || !(form instanceof HTMLFormElement)) { |
|
// |
|
} else { |
|
const formData = new FormData(form); |
|
window.npa.lastVersion = null; |
|
$("#npa-form #master-fieldset :input").change(window.npa.autoSave); |
|
} |
|
} |
|
npa.bindEvents(); |
|
npa.makeNavigator(); |
|
if (!npa.locked) { |
|
npa.notifyEmpty(); |
|
} |
|
|
|
}); |
|
</script> |
|
<? |
|
|
|
} |
|
}
|
|
|