gestione gare pubbliche
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

<?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>
<?
}
}