pdo = $pdo; if(!str_ends_with($form, ".xsd")) { $form .= ".xsd"; } $this->form_section = $this->notice = $form; $this->init($codice); if($directory == "config/xml") { $directory = "{$root}/backend/guue/{$directory}"; } $this->settings = $settings; $this->fields = []; $this->settings = file_get_contents("{$root}/backend/guue/config/settings.json"); $this->settings = json_decode($this->settings, true); $this->title = $this->settings["forms"][$this->form_section]["name"]; ini_set('max_execution_time', 300); ini_set('memory_limit', "512M"); $this->debug = false; $this->group = 0; $this->mapper = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "localization.json"); $this->mapper = json_decode($this->mapper, TRUE); $this->mapper["ERRORS_DICT"] = json_decode(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "errors.json"), true); $this->translator = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "translator.json"); $this->translator = json_decode($this->translator, TRUE); $this->elements = [ "empty", "email", "url", "text_ft_multi_lines", "url", "string_400", "string_200", "string_not_empty", "string_with_letter", "string", "string_20", "string_30", "string_50", "string_100", "string_150", "string_200", "string_250", "string_300", "string_400", "string_500", "string_1000", "text_ft_single_line", "text_ft_multi_lines", "text_ft_multi_lines_or_string", "nonNegativeInteger", "_2car", "_3car", "_4car", "_5car", "alphanum", "nb", "prct", "decimal", "time", "date_full", "date", "duration_unit_m_d", "duration_unit_m_y", ]; $this->ignored_keys = [ self::MUL, "COUNTRY_ORIGIN", "ADDRESS_CONTRACTING_BODY_ADDITIONAL", "ADDRESS_FURTHER_INFO", "_attributes", "ADDRESS_PARTICIPATION", "LOT_DIVISION", "ADDRESS_PARTICIPATION", "LOT_DIVISION_SETTINGS", "LOT_DIVISION", "LOT_MAX_NUMBER", "CPV_MAIN", "AC", "AC_QUALITY", "AC_COST", "time_frame", "RADIO_FOR_ECONOMIC_CRITERIA_DOC", "RADIO_FOR_TECHNICAL_CRITERIA_DOC", "RADIO_FOR_PT_RESTRICTED", "LANGUAGES", "LANGUAGE", "TYPE_CONTRACT", "RADIO_FOR_GPA", "AC_QUALITY_SECTION", "CPV_ADDITIONAL", "DATE_RECEIPT_TENDERS", "TIME_RECEIPT_TENDERS", "OBJECT_DESCR", "AWARDED_CONTRACT", "NO_AWARDED_CONTRACT", "PROCUREMENT_DISCONTINUED", "ORIGINAL_OTHER_MEANS", "RADIO_FOR_ORIGINAL_OTHER_MEANS_SECTION", "CONTRACTORS", "CONTRACTOR", "VALUES", "CONTRACTOR_SME_MAN", "ITEM", "TOWN", "RADIO_FOR_DEPOSIT_GUARANTEE_REQUIRED", "RADIO_FOR_MAIN_FINANCING_CONDITION", "RADIO_FOR_LEGAL_FORM", "RADIO_FOR_PARTICULAR_PROFESSION", "PARTICULAR_PROFESSION", "D_ACCORDANCE_ARTICLE", "AGREE_TO_PUBLICATION_MAN", "NON_COMMUNITY_ORIGIN", "TED_ESENDERS.FORM_SECTION.F07_2014.LEFTI.SECTION_FOR_QUALIFICATION", "NO_AWARDED_PRIZE", "AWARDED_PRIZE", "WINNER", "ADDRESS_WINNER", "RADIO_FOR_ENOTICES", "ORIGINAL_ENOTICES", "ORIGINAL_TED_ESENDER", "CHANGE", "SECTION_FOR_CHANGE", "OLD_VALUE", "NEW_VALUE", "RADIO_FOR_ANNEX_D4_PART_1", "PT_AWARD_CONTRACT_WITHOUT_PUBLICATION", "RADIO_FOR_DOCUMENT_URL_OPT_SECTION", "RADIO_FOR_ADDESS_PARTICIPATION", "RADIO_FOR_OPENING_CONDITION", "OBJECT_CONTRACT", "LEGAL_FORM", "OPENING_CONDITION", "DATE_RECEIPT_TENDERS", "RADIO_FOR_OPENING_CONDITION", "DURATION_TENDER_VALID", "DATE_OPENING_TENDERS", "SEVERAL_OPERATORS", "D_FROM_WINDING_PROVIDER", "D_FROM_LIQUIDATOR_CREDITOR", "AWARD_CONTRACT", "RADIO_FOR_LIKELY_SUBCONTRACTED", "NO_LOT_DIVISION", "REDUCTION_RECOURSE_SECTION", "NOTICE_NUMBER_OJ", "FRAMEWORK", "NB_PARTICIPANTS", "DPS_ADDITIONAL_PURCHASERS", "INFO_ADD", "PLACE", "TIME_OPENING_TENDERS", "TITLE", "LOT_NO", "NUTS", "MAIN_SITE", "SHORT_DESCR", "DURATION", "DATE_START", "DATE_END", "AC_WEIGHTING", "RENEWAL_DESCR", "NO_EU_PROGR_RELATED", "NB_MAX_LIMIT_CANDIDATE", "NB_MIN_LIMIT_CANDIDATE", "NB_ENVISAGED_CANDIDATE", "CRITERIA_CANDIDATE", "OPTIONS_DESCR", "EU_PROGR_RELATED", "ESTIMATED_TIMING", "EORDERING", "EPAYMENT", "EINVOICING", "OFFICIALNAME", "ADDRESS", "POSTAL_CODE", "COUNTRY", "TOWN", "E_MAIL", "PHONE", "FAX", "URL", "DATE_DISPATCH_NOTICE", "REVIEW_PROCEDURE", "NATIONALID", "CONTACT_POINT", "URL_GENERAL", "URL_BUYER", "JOINT_PROCUREMENT_INVOLVED", "PROCUREMENT_LAW", "REFERENCE_NUMBER", "PT_AWARD_CONTRACT_WITHOUT_CALL", "D_NO_TENDERS_REQUESTS", "D_PURE_RESEARCH", "D_EXTREME_URGENCY", "D_ADD_DELIVERIES_ORDERED", "CTYPE", "D_BARGAIN_PURCHASE", "D_OUTSIDE_SCOPE", "D_JUSTIFICATION", "DPS", "EAUCTION_USED", "TERMINATION_DPS_SECTION", "TERMINATION_PIN", "CONTRACT_NO", "NB_TENDERS_RECEIVED", "NB_TENDERS_RECEIVED_SME", "RADIO_FOR_ANNEX_D2_PART1", "QUALIFICATION", "RADIO_FOR_INFORMATION", "PARTICIPANT_NAME", "PARTICIPANT_NAME_ADDITIONAL", "RADIO_FOR_VAL_PRIZE", "VAL_PRIZE", "DIRECTIVE_2014_23_EU", "DIRECTIVE_2014_24_EU", "DIRECTIVE_2014_25_EU", "DIRECTIVE_2009_81_EC", "AC_CRITERIA", "RADIO_FOR_PT_NEGOTIATED_WITHOUT_PUBLICATION", "PT_NEGOTIATED_WITHOUT_PUBLICATION", "DESCRIPTION_PROCUREMENT", "AC_CRITERION", "ESENDER_LOGIN", "CUSTOMER_LOGIN", "JUSTIFICATION", "D_CONTRACT_AWARDED_DESIGN_CONTEST", "D_COMMODITY_MARKET", "RADIO_FOR_D_MANUF_FOR_RESEARCH", "QS", "VAL_REVENUE", "VAL_PRICE_PAYMENT", "T" ]; $this->xsd = $form; $this->directory = $directory; $this->suggestionTriggers = []; $this->dom = new DOMDocument(); $this->dom->load("{$this->directory}/{$this->xsd}", LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NOENT|LIBXML_XINCLUDE); $this->dom = $this->loadIncludes($this->dom, $this->directory); $this->dom = $this->loadImports($this->dom, $this->directory); $this->xml = $this->dom->saveXML(); $this->xml = str_replace(["xs:", "lb:", "n2016:"], "", $this->xml); $this->dom = new SimpleXMLIterator($this->xml); $this->form = $this->dom->xpath("element[@name='".str_replace(".xsd","",$form)."']"); if(is_array($this->form) && count($this->form) == 1) $this->form = reset($this->form); $this->getSections(); } /** * List alla available notices' form * * @return Array */ static public function listAvailableNoticeForms() : ?Array { global $config; $path = dirname(__DIR__); $forms = glob("{$path}/config/xml/F*_2014.xsd"); $settings = jsonToArray("{$path}/config/settings.json"); $select = [ 'name' => 'form', 'title' => "Tipologia di avviso", 'attrs' => [ 'rel' => 'S;12;12;A' ], 'options' => [] ]; foreach ($forms as $value) { if ($settings["forms"][basename($value)]["active"] || $config["developEnv"]) { $select['options'][str_replace('.xsd', "", basename($value))] = $settings["forms"][basename($value)]["name"]; } } return $select; } /** * Get eNotice version: eSender|eForms * * @return void */ static public function getVersion(Int $codice = 0) { if(! empty($codice)) { global $pdo; return $pdo->go("SELECT `version` FROM b_guue WHERE codice = :codice", [":codice" => $codice])->fetch(PDO::FETCH_COLUMN, 0); } if(DEVELOP_ENV || strtotime('now') >= strtotime('2023-10-15 00:00:00')) { return "eForms"; } return "eSender"; } /** * Returns the modules that can provide data * * @param string $modulo * @param integer $elemento * @return array */ public static function moduliCollegati() { return [ "gare" => "/gare/guue/init.php", "pcp" => "/pcp/guue/init.php", "concorsi" => "/concorsi/guue/init.php", ]; } /** * Returns all the pubblications of a specific platform's module * * @param string $modulo * @param integer $elemento * @return array */ public static function getPubblicazioniFromModulo(string $modulo, int $elemento) { global $pdo; if (!empty(self::moduliCollegati()[$modulo])) { return $pdo->go("SELECT codice, titolo, numero, anno, numero_ojs, version, numero_tecnico, form, stato, data_trasmissione, data_pubblicazione FROM b_guue WHERE modulo = :modulo AND codice_elemento = :elemento AND soft_delete = 'N'", [":modulo"=>$modulo,":elemento"=>$elemento])->fetchAll(PDO::FETCH_ASSOC); } } /** * Init the model retrieving stored data or it returning a new instance * * @param integer $codice * @return void */ private function init($codice = 0) { $this->info = get_campi("b_guue"); $this->info["codice"] = 0; $this->info["codice_ente"] = $_SESSION["ente"]->codice; $this->info["form"] = $this->form_section; $this->info["anno"] = date('Y'); $this->info["data_creazione"] = date('d/m/Y H:i:s'); $this->info["utente_modifica"] = $_SESSION["utente"]->codice ?? 0; $this->info["valori"] = []; if(! empty($codice)) { $bind = [ ':codice' => $codice, ]; $sql = "SELECT * FROM b_guue WHERE codice = :codice"; if (! empty($_SESSION["utente"]) && !isset($_SESSION["utente"]->elaborazioneCoda) && ! $_SESSION["utente"]->fromHere()) { $sql = "{$sql} AND codice_ente = :codice_ente"; $bind[":codice_ente"] = $_SESSION["utente"]->codice_ente; } if (! empty($_SESSION["utente"]) && !isset($_SESSION["utente"]->elaborazioneCoda) && $_SESSION["utente"]->gerarchia > 10) { $ids = Utente::getPermissionsInModulo($_SESSION["ente"]->codice,$_SESSION["utente"]->codice,"guue"); $ids = implode(",",$ids); $sql .= " AND codice IN ({$ids}) "; } $ris = $this->pdo->go($sql, $bind); if($ris->rowCount() == 1) { $rec = $ris->fetch(PDO::FETCH_ASSOC); $this->info = $rec; if($this->isJson($this->info["valori"])) { $this->info["valori"] = json_decode($this->info["valori"], 1); } } } } /** * Load includes in XML Schema * * @param DOMDocument $dom Instance of DOMDocument * @param String $filepath * @param String $namesp * @return void */ private function loadIncludes(DOMDocument $dom, String $filepath = '', String $namespace = '') { $query = "//*[local-name()='include']"; $xpath = new \DOMXPath($this->dom); if($xpath->query($query)->length > 0) { $this->dom = $this->loadExt($xpath->query($query), $dom, $filepath); $this->dom = $this->loadIncludes($this->dom, $this->directory); } return $this->dom; } /** * Load imports * * @param DOMDocument $dom Instance of DOMDocument * @param String $filepath * * @return void */ private function loadImports($dom, String $filepath = '') { $query = "//*[local-name()='import']"; $xpath = new \DOMXPath($this->dom); if($xpath->query($query)->length > 0) { $this->dom = $this->loadExt($xpath->query($query), $dom, $filepath); $this->dom = $this->loadIncludes($this->dom, $this->directory); } return $this->dom; } /** * Load imports o includes * * @param DOMDocument $dom Instance of DOMDocument * @param String $filepath * * @return void */ private function loadExt($includes, $dom, $filepath) { if(! empty($includes->length)) { foreach ($includes as $file) { $parent = $file->parentNode; $xsdFileName = realpath($filepath.DIRECTORY_SEPARATOR.$file->getAttribute("schemaLocation")); if (! file_exists($xsdFileName)) continue; if(in_array($xsdFileName, $this->loadedFiles ?? [])) continue; $xsd = new \DOMDocument(); $result = $xsd->load($xsdFileName, LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NOENT|LIBXML_XINCLUDE); if($result) { $this->loadedFiles[] = $xsdFileName; foreach ($xsd->documentElement->childNodes as $node) { $node = $dom->importNode($node, true); $parent->insertBefore($node, $file); } } $parent->removeChild($file); } } return $dom; } /** * Get Form Sections * * @return void */ private function getSections() { $this->sections = []; $this->total_completed = 0; foreach ($this->form->complexType->sequence->element as $element) { if(empty($element["name"]) && empty($element["ref"])) continue; $section = [ 'name' => ! empty((string) $element["name"]) ? (string) $element["name"] : (string) $element["ref"], 'type' => (string) $element["type"] ?? null, 'object' => $element->getName() ]; $section["completed"] = false; if ($this->sectionCompleted($section["name"])) { $section["completed"] = true; $this->total_completed++; } if(empty($section["type"]) && ! empty((string) $element["ref"])) $section["type"] = (string) $element["ref"]; $this->sections[] = $section; $subsections = array_filter($this->settings["subsections"], function($subsection) use ($section){ return $subsection["parent"] == $section["name"]; }); if (count($subsections) > 0) { foreach ($subsections as $sub) { $sub["check"] = str_replace("[[FORM]]", $this->form_section, $sub["check"]); $search = "//*[@name='{$sub["name"]}']"; if (count($this->dom->xpath($search)) > 0) { $sub["type"] = $this->dom->xpath($search)[0]->attributes()["type"]->__toString(); $check = self::get($this->info["valori"], $sub["check"]); $key = str_replace("[[FORM]]",$this->form_section,$sub["key"]); if ($sub["name"] == $sub["parent"]) { $key = "{$key}.{$sub["name"]}"; } $subsection_contents = self::get($this->info["valori"], $key); if(is_numeric($check) && $check > 0) { for ($i=0; $i < (int) $check; $i++) { $j = $i + 1; if(empty($subsection_contents) || empty($subsection_contents["£"][$j])) { //aggiungi le sottosezioni al draft se sono vuote o non valorizzate //prima di verificare se le sezioni sono completate $drafted_sections = self::get($this->info["valori"],"TED_ESENDERS.DRAFT_SECTIONS") ?? []; $draft = str_replace("TED_ESENDERS.FORM_SECTION.[[FORM]].","",$sub["key"]). ".£." . $j; if ($sub["parent"] == $sub["name"]) { $draft = "{$sub['name']}.{$draft}"; } if (!in_array($draft, $drafted_sections)) { $drafted_sections[] = $draft; } self::set($this->info["valori"],"TED_ESENDERS.DRAFT_SECTIONS",$drafted_sections); } $completed = $this->sectionCompleted("{$section["name"]}.{$sub["name"]}.£.{$j}"); if ($completed) { $this->total_completed++; } $section = [ "name" => $section["name"], "subsection" => $sub["name"], "type" => $sub["type"], "object" => "element", "completed" => $completed, "index" => $i + 1 ]; $this->sections[] = $section; } } } } } } } /** * Check all form sections was completed * @return bool */ public function ready() : bool { return $this->total_completed == count($this->sections); } /** * Check if given section is last needed to complete form * @param String $section * @return bool */ public function willFormBeReadyAfterSectionCompletion(String $section, String $index) : bool { if ($this->total_completed == count($this->sections) - 1) { $draft_sections = $this->get($this->info["valori"],"TED_ESENDERS.DRAFT_SECTIONS"); $section_check = $section; if (!is_array($draft_sections)) { return false; } #se non è presente un alcun index non siamo in una sottosezione, quindi possiamo essere più stringenti nella verifica if (empty($index)) { return count($draft_sections) == 0 || (count($draft_sections) == 1 && $draft_sections[array_keys($draft_sections)[0]] == $section); } #altrimenti controlla se è presente una corrispondenza per la sezione attuale return count($draft_sections) == 0 || (count($draft_sections) == 1 && strpos($draft_sections[array_keys($draft_sections)[0]],$section_check . ".£.{$index}") !== false); } return false; } /** * Check if given section is completed * @param SimpleXMLIterator $element * @return bool */ public function sectionCompleted(String $section) { return $this->get($this->info["valori"], "TED_ESENDERS.FORM_SECTION.{$this->form_section}.{$section}") !== null && !in_array( $section, $this->get($this->info["valori"],"TED_ESENDERS.DRAFT_SECTIONS") ?? [] ); } /** * Check if form is completed * * @return Bool|null */ public function completed() : ?Bool { $this->getSections(); $check = array_unique(array_column($this->sections, "completed")); if(count($check) == 1 && $check[0]) { return true; } return false; } /** * Get fields For Section * * @param Array $section * @param Bool $multiple * * @return void */ public function getFieldsForSection(Array $section, Array $name = [], $multiple = false) { $this->fields[$section["name"]] = []; $this->optionals[$section["name"]] = []; if(empty($name)) { $name = [ "TED_ESENDERS", "FORM_SECTION", $this->form_section ]; } if($section["object"] == "element") $name[] = $section["name"]; if($multiple) { $name[] = TedEsender::MUL; if(isset($section["index"])) { $name[] = $section["index"]; } } $type = $this->getTypeElement($section["type"]); if(! empty($type)) { if(is_array($type)) $type = reset($type); $this->getField($section["name"], $type, ["name" => $name]); } //if ($this->debug) dd($this->fields); } private function getFormContent() { return $this->info["valori"]["TED_ESENDERS"]["FORM_SECTION"][((string) $this->form->attributes()["name"])]; } /** * unpack choices from XMLIterator * @param Array $node * @return Array */ private function unpackChoice(Array $node) { $result = []; foreach ($node as $key => $value) { if ($key == "sequence") { array_push($result, ...$this->unpackChoice($value)); } if ($key == "element") { if (is_array($value)) { foreach ($value as $v) { if (is_a( $v, SimpleXMLIterator::class ) ) { array_push($result, ((string) $v->attributes()["name"])); } else if (is_array($v)) { if ($v["ref"]) { array_push($result, $v["ref"]); } } } } } } return array_filter($result, function($value) { return !is_null($value) && $value !== ''; }); } /** * Get the element description parameters * * @param String $t * @return SimpleXMLElement */ private function getTypeElement(String $t) { $type = $this->dom->xpath("simpleType[@name='{$t}']"); if(empty($type)) $type = $this->dom->xpath("complexType[@name='{$t}']"); if(empty($type)) $type = $this->dom->xpath("group[@name='{$t}']"); if(empty($type)) $type = $this->dom->xpath("element[@name='{$t}']"); if(! empty($type) && is_array($type)) $type = reset($type); return $type; } /** * Questa funzione è da riscrivere, mo funziona a pezza * Lo scopo di questa funzione è cercare all'interno di un array di array il primo ["name"] che trova */ private static function unpackUntil($arr, String $key, $or) { #controlla se nell'array c'è la chiave ricercata if (is_array($arr)) { if (!empty($arr[$key])) { return $arr[$key]; #se non è presente e l'array è numerico cercala negli indici } else if (!empty($arr[$or]) && is_array($arr[$or])) { if (!empty($arr[$or]["info"]) && !empty($arr[1])) { $or = 1; } return self::unpackUntil($arr[$or], $key, $or); #se non è numerico cercala all'interno delle chiavi } else if (!empty($arr) && isAssociative($arr)) { $last = array_keys($arr)[0]; return self::unpackUntil($arr[$last],$key, $or); } } return null; } /** * Print form fields * * @param Array $field * @return void */ private function printField(Array $field, Bool $row = true) { if(! empty($field["info"]["title"])) { if($row) { ?>
">

translate($field["info"]["title"], self::unpackUntil($field,"name",0)) ?>

printField($field, false); if($row) { ?>
get($this->info["valori"], $key); if (!isset($value)) { $value = (string) $this->getSuggetion($key); if (isset($value) && !empty($field["attrs"]) && is_array($field["attrs"]) && (in_array("onChange",array_keys($field["attrs"])) !== false || in_array("onclick",array_keys($field["attrs"])) !== false)) { $field["triggerOnChange"] = true; } } if(isset($value) && is_string($value)) { $field["val"] = $value; } echo $this->{$field["type"]}($field); } else { $fields = $field; foreach ($fields as $key => $field) { if( (strpos($key, "RADIO_FOR") !== false || empty($field["name"])) && ! in_array($key, $this->ignored_keys) && ! is_numeric($key)) { $attrs = ""; $class = ""; $required_check = ""; if(is_array($field) && in_array("radio", array_column($field, "type"))) { if ($field[0]["required"] ?? false) { $required_check = " "; } $validation = array_unique(array_column($field, "group_validate")); if(empty($validation) || (count($validation) == 1 && $validation[0])) { $attrs = 'rel="S;0;0;checked;group_one" title="' . $this->translate($key, self::unpackUntil($field,"name",0)) . '"'; $class = "valida"; } } $title = $this->translate($key, self::unpackUntil($field,"name",0)) . $required_check; if (!empty($this->settings["titles"][$key])) { $title = $this->translate($this->settings["titles"][$key], self::unpackUntil($field,"name",0)) . $required_check; } ?>
">

">

>translate($key, self::unpackUntil($field,"name",$key)); $this->printField($field, false); if( (strpos($key, "RADIO_FOR") !== false || empty($field["name"])) && ! in_array($key, $this->ignored_keys) && ! is_numeric($key)) { ?>
self::DEFAULT, "codice" => null ]; $subsection = null; $record = $this->info; $draft = $record["valori"]["TED_ESENDERS"]["DRAFT_SECTIONS"] ?? []; if (empty($record["codice"]) && empty($draft)) { foreach ($this->sections as $section) { $draft[] = $section["name"]; } } $to_draft = $data["current_section"]; if ($data["draft"] == "S" && empty($data["current_sub_section"])) { array_push($draft, $to_draft); } else if (in_array($to_draft, $draft) && empty($data["current_sub_section"])) { unset($draft[(array_search($to_draft,$draft))]); } if(! empty($data["titolo"])) { $record["titolo"] = $data["titolo"]; } if(! empty($this->settings["sections"][$data["current_section"]])) { if(! empty($data["current_sub_section"])) { $key = "{$this->settings["sections"][$data["current_section"]]}.{$data["current_sub_section"]}"; $key = str_replace("[[FORM]]", $this->form_section, $key); if(! empty($data["current_sub_section_index"])) { $key = "{$key}.£.{$data["current_sub_section_index"]}"; $draft_key = str_replace("TED_ESENDERS.FORM_SECTION.{$this->form_section}.","",$key); if ($data["draft"] == "S") { array_push($draft,$draft_key); } else if (in_array($draft_key, $draft)) { unset($draft[(array_search($draft_key,$draft))]); } } self::set($record["valori"], $key, []); $result["action"] = self::EDITED_SUBSECTION; } else { $key = "{$this->settings["sections"][$data["current_section"]]}"; $key = str_replace("[[FORM]]", $this->form_section, $key); $subsections = array_filter($this->settings["subsections"], function($subsection) use ($data) { return $subsection["parent"] == $data["current_section"] && !empty( self::get($this->info["valori"], str_replace("[[FORM]]", $this->form_section, $subsection["check"])) ); }); if (count($subsections) > 0) { foreach ($subsections as $subsection) { $subsection_check = str_replace("[[FORM]]", $this->form_section, $subsection["check"]); $subsection_key = str_replace("[[FORM]]", $this->form_section, $subsection["key"]); #se il valore risulta vuoto all'interno del form, viene inserito come zero if (empty(self::get($data["guue"],$subsection_check)) && $subsection["name"] == $subsection["parent"]) { self::set($data["guue"],$subsection_check,0); } $check = (int) (self::get($data["guue"], $subsection_check)); $valori = self::get($record["valori"], $subsection_key) ?? []; if(is_numeric($check) && $check > 0) { if (isset($valori["£"]) && count($valori["£"]) > $check ) { $difference = count($valori["£"]) - $check; $keys = array_slice(array_keys($valori["£"]),count($valori["£"]) - $difference, count($valori["£"])); foreach ($keys as $key) { unset($valori["£"][$key]); } } else if (isset($valori[$data["current_section"]]["£"]) && $valori[$data["current_section"]]["£"] > $check) { $difference = count($valori[$data["current_section"]]["£"]) - $check; $keys = array_slice(array_keys($valori[$data["current_section"]]["£"]),count($valori[$data["current_section"]]["£"]) - $difference, count($valori[$data["current_section"]]["£"])); foreach ($keys as $key) { unset($valori[$data["current_section"]]["£"][$key]); } } self::set($subsection["valori"], $subsection_key, $valori); } else { #nel caso in cui il check sia vuoto ma sono presenti dei contenuti rimuovili foreach ($draft as $index => $draft_section) { if (strpos($draft_section,"{$subsection['parent']}.{$subsection['name']}.£") !== false) { unset($draft[$index]); } } if (!empty($valori["£"])) { unset($valori["£"]); #controlla se la sezione è presente nelle draft } $result["action"] = self::REMOVED_SUBSECTIONS; } } } self::set($record["valori"], $key, []); } } else { self::set($record["valori"],"TED_ESENDERS.FORM_SECTION.{$this->form_section}.{$data['current_section']}",[]); } $this->removeOverridedKeys($data["guue"]); $record["valori"] = $this->md_array_merge($record["valori"], $data["guue"]); $draft = array_unique($draft); self::set($record["valori"],"TED_ESENDERS.DRAFT_SECTIONS",$draft); if(! empty($subsection["valori"])) { $record["valori"] = $this->md_array_merge($record["valori"], $subsection["valori"]); } $this->sortSections(); $record["valori"] = $this->fixHtmlVaues($record["valori"]); array_walk_recursive($record["valori"], 'htmlentitiesdecode'); $record["valori"] = json_encode($record["valori"],JSON_INVALID_UTF8_IGNORE); $record["data_aggiornamento"] = date('d/m/Y H:i:s'); $record["data_trasmissione"] = null; $record["modulo"] = $_POST["modulo"] ?? null; $record["codice_elemento"] = $_POST["codice_elemento"] ?? 0; $record["stato"] = self::stati()[0]["title"]; if (! $_SESSION["utente"]->fromHere()) { $record["codice_ente"] = $_SESSION["utente"]->codice_ente; } $salva = new Salva(); $salva->debug = false; $salva->nome_tabella = "b_guue"; $salva->operazione = "INSERT"; $salva->ignore = ["soft_delete"]; if(! empty($record["codice"])) { $salva->operazione = "UPDATE"; $salva->ignore[] = "data_creazione"; $salva->ignore[] = "utente_modifica"; $salva->ignore[] = "data_pubblicazione"; } $salva->oggetto = $record; $codice = $salva->save(); if(! empty($codice)) { $this->info = $salva->oggetto; $this->info["codice"] = $codice; if($_SESSION["utente"]->gerarchia > 15 && $salva->operazione == "INSERT") { $permesso = []; $permesso["sezione"] = "guue"; $permesso["codice_elemento"] = $this->info["codice"]; $permesso["codice_gestore"] = $_SESSION["ente"]->codice; $permesso["funzione"] = "0"; $permesso["codice_utente"] = $_SESSION["utente"]->codice; $salva = new salva(); $salva->debug = false; $salva->raw = true; $salva->nome_tabella = "r_permessi"; $salva->operazione = "INSERT"; $salva->oggetto = $permesso; $salva->save(); } $this->info["valori"] = json_decode($this->info["valori"], 1); $result["codice"] = $codice; return $result; } $result["codice"] = $codice; return $result; } public function getLastNumber() : int { $number = 0; $latest = $this->pdo->go("SELECT MAX(numero) FROM b_guue WHERE anno = :anno", [":anno" => date('Y')])->fetch(PDO::FETCH_COLUMN, 0); if($latest > 0) { $number = $latest; } return $number; } /** * Create xml notice * * @param Array $valori * @return void */ public function xml(Array $data = [], Array $ente = [], bool $update_state = true) : void { if(empty($ente) || empty($ente["id_guue"])) { $ente = $_SESSION["ente"]->getInfo(); } if(empty($data)) { $data = $this->info; } if(empty($data["numero"])) { $data["numero"] = $this->getLastNumber() + 1; $this->info["numero"] = $data["numero"]; $this->pdo->go( "UPDATE b_guue SET numero = :numero WHERE codice = :codice", [":numero" => $data["numero"], ":codice" => $this->info['codice']] ); } if(strlen($data["numero"]) < 6) { $data["numero"] = str_repeat("0", 6 - strlen($data["numero"])) . $data["numero"]; } $this->auth(); $TED_ESENDERS = $data["valori"]["TED_ESENDERS"]; $data["valori"]["TED_ESENDERS"] = [ "_attributes" => [ "xmlns" =>"http://publications.europa.eu/resource/schema/ted/R2.0.9/reception", "xmlns:tg"=>"http://publications.europa.eu/resource/schema/ted/R2.0.9/reception", "xmlns:tg-nuts"=>"http://publications.europa.eu/resource/schema/ted/2021/nuts", "xmlns:tg-curr" => "http://publications.europa.eu/resource/authority/currency", "xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation"=>"http://publications.europa.eu/resource/schema/ted/R2.0.9.S05/publication TED_EXPORT.xsd", "VERSION"=>"R2.0.9.S05", ], "SENDER" => [ "IDENTIFICATION" => [ "ESENDER_LOGIN" => $this->username, "CUSTOMER_LOGIN" => $ente["id_guue"] ?? "__£__", "NO_DOC_EXT" => "{$data["anno"]}-{$data["numero"]}", "SOFTWARE_VERSION" => "3.0", ], "CONTACT" => [ "ORGANISATION" => "STUDIO AMICA SRL", "COUNTRY" => [ "_attributes" => [ "VALUE" => "IT" ] ], "PHONE" => "", "E_MAIL" => "gaetano.lolli@studioamica.it", ] ] ]; $data["valori"]["TED_ESENDERS"] = array_merge($data["valori"]["TED_ESENDERS"], $TED_ESENDERS); foreach ($data["valori"]["TED_ESENDERS"]["FORM_SECTION"] as $key => $form) { $data["valori"]["TED_ESENDERS"]["FORM_SECTION"][$key] = array_merge([ "_attributes" => [ "LG" => "IT", "CATEGORY" => "ORIGINAL", "FORM" => substr($key, 0, strpos($key, "_")), ] ], $data["valori"]["TED_ESENDERS"]["FORM_SECTION"][$key]); } try { $stati = $this->stati(); $stato = $stati[0]["title"]; $this->xml = $this->errors = ""; $this->fixArrayForXml($data["valori"]); $this->xml = \Spatie\ArrayToXml\ArrayToXml::convert($data["valori"]["tg:TED_ESENDERS"], "tg:TED_ESENDERS"); $originalErrorHandling = libxml_use_internal_errors(true); $document = new DOMDocument(); $document->loadXML($this->xml); $document->schemaValidate(__DIR__ . "/../config/xml/TED_ESENDERS.xsd"); $errors = libxml_get_errors(); $this->errors = []; if(! empty($errors)) { foreach ($errors as $error) { $e = str_replace(["{http://publications.europa.eu/resource/schema/ted/R2.0.9/reception}", "{http://publications.europa.eu/resource/schema/ted/2021/nuts}"], "", $error->message); $e = preg_replace('/[[:cntrl:]]/', '', $e); if(! in_array($e, $this->errors)) $this->errors[] = $e; } } $this->errors = array_filter($this->errors); libxml_clear_errors(); libxml_use_internal_errors($originalErrorHandling); } catch (\Throwable $th) { $message = "GUUE CODICE: {$this->info["codice"]} IMPOSSIBILE GENERARE XML. ERRORE: " . $th->getMessage(); if ($this->debug) { echo $message; } error_log($message); } finally { if(! empty($this->xml) && empty($this->errors)) { $stato = $stati[1]["title"]; } $this->errors = base64_encode(json_encode($this->errors)); $s = array_flip(array_column($stati, 'title')); if($stati[$s[$stato]]["status"] < 20) { if ($update_state) { $this->pdo->go("UPDATE b_guue SET xml = :xml, errori = :errori, stato = :stato, data_aggiornamento = NOW() WHERE codice = :codice", [ ":xml" => base64_encode($this->xml), ":errori" => $this->errors, ":stato" => $stato, ":codice" => $this->info["codice"] ]); } } } } public function getBreadcrumb($breadcrumb = []) { if (!empty($this->info["modulo"]) && !empty(!empty($this->info["codice_elemento"]))) { if ($_SESSION["utente"]->permission($this->info["modulo"],$this->info["codice_elemento"])) { global $beRoot; if (file_exists("{$beRoot}/{$this->info["modulo"]}/panel.php")) { $infoModulo = Modulo::getInfo($this->info["modulo"]); $breadcrumb["/{$this->info["modulo"]}/panel.php?codice={$this->info["codice_elemento"]}"] = __guue($infoModulo["titolo"]) . " - " . __guue("Pannello"); } } } return $breadcrumb; } /** * It iterate form field group * * @param String $section * @param array $fields * @return void */ public function printFields(String $section, Array $fields = []) { if(empty($fields)) { $fields = $this->fields; } foreach ($fields as $field) { if(! isset($field[$section]) && is_array($field)) { $this->printFields($section, $field); } else { $this->printField($field[$section]); } } } /** * Main function to print a form section * * @param Array $section * @return void */ public function printForm(Array $section) { if(! empty($section["name"])) { $n = []; $this->current_section = $s = $section; if(! empty($section["subsection"])) { $n = [ "TED_ESENDERS", "FORM_SECTION", $this->form_section, $section["name"], ]; $s["name"] = $s["subsection"]; $m = true; $this->getFieldsForSection($s, $n, $m); $fields = $this->fields["TED_ESENDERS"]["FORM_SECTION"][$this->form_section][$section["name"]][$section["subsection"]]; if($m && isset($section["index"])) { $fields = $fields[TedEsender::MUL][$section["index"]]; } $this->setMissingTitles($fields, $section["subsection"]); } else { $this->getFieldsForSection($s); $fields = $this->fields["TED_ESENDERS"]["FORM_SECTION"][$this->form_section][$section["name"]]; $this->setMissingTitles($fields, $section["name"]); } if(! empty($this->fields["TED_ESENDERS"]["FORM_SECTION"][$this->form_section][$section["name"]])) { ?>

translate($section["name"], self::unpackUntil($section,"name",0)) ?>

printField($fields, false); ?>
settings["dynamic_translations"][$this->form_section])) { $string = $this->settings["dynamic_translations"][$this->form_section][$string] ?? $string; } $name = explode("][",substr($name,5,-1)); $section = null; if (count($name) > 3) { $section = $name[3]; } //Parse settings name array searching for last subsection foreach ([4,7] as $pos) { if (count($name) > $pos && in_array($name[$pos], array_map(function($section){ return ($section["name"]); },$this->settings["subsections"]))) { $section = $name[$pos]; } } $numeration = ""; if ( ! empty($section) && ! empty($this->settings["numeration_map"]) && ! empty($this->settings["numeration_map"]["{$this->form_section}"]) && ! empty($this->settings["numeration_map"]["{$this->form_section}"]["{$section}"]) && ! empty($this->settings["numeration_map"]["{$this->form_section}"]["{$section}"]["{$string}"]) ) { $numeration = $this->settings["numeration_map"]["{$this->form_section}"]["{$section}"]["{$string}"]; } $translate_functions = [ "PT_NEGOTIATED_WITHOUT_PUBLICATION" => function() use ($string, $name) : String { if (in_array($name[count($name)-2], ["DIRECTIVE_2014_24_EU","DIRECTIVE_2014_25_EU","DIRECTIVE_2009_81_EC"]) && $string == "PT_NEGOTIATED_WITHOUT_PUBLICATION") { return $name[count($name)-2] . "_SELECTION_WITHOUT_PUBLICATION"; } return $string; }, "PT_AWARD_CONTRACT_WITHOUT_CALL" => function() use ($string, $name) : String { if (in_array($name[count($name)-2], ["DIRECTIVE_2014_24_EU","DIRECTIVE_2014_25_EU","DIRECTIVE_2009_81_EC"]) && $string == "PT_AWARD_CONTRACT_WITHOUT_CALL") { return $name[count($name)-2] . "_SELECTION_WITHOUT_CALL"; } return $string; }, "ADDRESS_REVIEW_BODY_WARNING" => function() use ($string, $name) : String { return __guue('(Indirizzo del corpo di revisione)'); }, "ADDRESS_MEDIATION_BODY_WARNING" => function() use ($string, $name) : String { return __guue('(Responsabile delle procedure di mediazione)'); }, "ADDRESS_REVIEW_INFO_WARNING" => function() use ($string, $name) : String { return __guue('(Informazioni sulla revisione)'); }, "ADDRESS_CONTRACTING_BODY_WARNING" => function() use ($string, $name) : String { return __guue('(Denominazione e indirizzi)'); }, "ADDRESS_CONTRACTING_BODY_ADDITIONAL_WARNING" => function() use ($string, $name) : String { return __guue('(Altre stazioni appaltanti)'); }, "ADDRESS_PARTICIPATION_WARNING" => function() use ($string, $name) : String { return __guue("(Offerte e domande di partecipazione)"); }, "ADDRESS_FURTHER_INFO_WARNING" => function() use ($string, $name) : String { return __guue("(Ulteriori informazioni sono disponibili presso)"); } ]; if (!empty($name) && in_array($string, array_keys($translate_functions))) { $translate_func = $translate_functions[$string]; $string = $translate_func(); } if(! empty($this->mapper[$string])) { if(! empty($this->translator[$this->mapper[$string]]["it"])) $string = $this->translator[$this->mapper[$string]]["it"]; } if (!empty($numeration)) { return $numeration . " " . $string; } return $string; } #region [Validation Rules Traductions] private function dateFormatValidationRule(Array $matches, String $raw_err) : String { $key = str_replace($matches[1], "", $matches[0]); if (!empty($this->mapper["ERRORS_DICT"][$key]["it"])) { $err = $this->mapper["ERRORS_DICT"][$key]["it"]; $chunk = str_split($matches[1], 2); $chunk[2] = $chunk[2] . $chunk[3]; unset($chunk[3]); $err = str_replace("[[DATE]]",implode("/",$chunk),$err); } return $err ?? $matches[0]; } private function phoneNumberValidationRule(Array $matches, String $raw_err) : String { $err = null; if (count($matches) > 2) { if ($matches[1] == "phone") { $matches[1] = "Tel"; } $matches[1] = ucfirst($matches[1]); $err = __guue($matches[1]) . " {$matches[2]} " . __guue("non è un numero telefonico valido."); } return $err ?? $matches[0]; } private function numberNoticeValidationRule(Array $matches, String $raw_err) : String { $err = null; if (count($matches) > 1) { $err = $matches[1] . " " . __guue("non è un numero di avviso valido."); } return $err; } private function annexD1ValidationRule(Array $matches, String $raw_err) : String { $m = []; preg_match_all('/[[][@]CTYPE[=](\w+)[]]/m', $raw_err, $m); if (count($m) > 1 && count($m[1]) > 1) { return __guue("Attenzione, la tipologia del contratto risulta incoerente con la giustificazione dell'allegato Annex D1, Tipologia del contratto ({$this->translate($m[1][0])}) è differente dalla tipologia accettata ({$this->translate($m[1][1])})"); } return $raw_err; } private function publicationValidationRule(Array $matches, String $raw_err) : String { $matches = []; preg_match_all('/([A-Z0-9_]+)[\/][@][A-Z0-9_]+=\w+ and ([A-Z0-9_]+)[\/]([A-Z0-9_]+)[[].*[@]PUBLICATION[]]/',$raw_err,$matches); if (count($matches) > 3) { if ($matches[2] == [] || $matches[3] == []) { return $raw_err; } $section2 = $matches[2][0]; $section3 = $matches[3][0]; $replacements = [ "AWARDED_CONTRACT" => "RADIO_FOR_SME_SECTION", "CONTRACTORS" => "ADDRESS_CONTRACTOR", "VALUES" => "VALUE", "AC" => "AC_DEF" ]; if (!empty( $replacements[$section2]) ) { $section2 = $replacements[$section2]; } if (!empty( $replacements[$section3]) ) { $section3 = $replacements[$section3]; } $section = __guue("Sezione") . ": " . $this->translate($section2) . ", " . $this->translate($section3); return __guue("La pubblicazione è richiesta per contratti sotto direttiva 2014/25/EU") . ". " . $section; } return $raw_err; } private function repeatValidationRule(Array $matches, String $raw_err) : String { if ($matches > 2) { $element = $this->translate(strtoupper($matches[2])); $type = $this->translate(strtoupper($matches[1])); return __guue("Non è possibile inserire molteplici elementi") . "(" . $element . " ), " . __guue("se la tipologia di avviso è") . " (" . $type . " )"; } return $raw_err; } private function qualificationValidationRule(Array $matches, String $raw_err) : String { $matches = []; preg_match_all('/\w+[\/][@]TYPE[=](\w+) and (\w+)[[](\d)[]][\/](\w+)[[][\d][]][\/]QS[\/](\w+)/s',$raw_err, $matches); if (count($matches) > 5) { $type = $this->translate($matches[1][0]); $section = $this->translate($matches[2][0]); $index = $this->translate($matches[3][0]); $subsection = $this->translate($matches[4][0]); $field = $this->translate($matches[5][0]); return " n.{$index} " . "(". $field . " ) " . __guue("è prevista solo se la tipologia del contratto è un Sistema di qualificazione ") . "(" . $type . " )"; } return $raw_err; } #endregion /** * Convert validation rules errors into human readable errors * * @param array $errors * @return array */ public function validationRuleToHuman(Array $errors) { $latin = [ 1 => "I", 2 => "II", 3 => "III", 4 => "IV", 5 => "V", 6 => "VI", 7 => "VII", 8 => "VIII", 9 => "IX", 10 => "X", ]; foreach ($errors as $index => $err) { $raw_err = $err; $matches = []; preg_match('/[:](.*)[.][<]br[>]/s', $err, $matches); if (count($matches) == 2) { $err = trim($matches[1]); } else { $matches = []; preg_match('/:([ ]\w+[-]\w+[-]\w+[:].*)[.][ ][<]br[>]/s', $err, $matches); if (count($matches) != 0) { $err = trim($matches[count($matches)-1]); } } $err = str_replace(['"',';',',',"'","/","(",")","
"], "", $err); $err = str_replace([":"," ","__"], "_", $err); $err = str_replace("...","", $err); $err = strtolower($err); #TODO: Rimuove gli errori relativi al campo lingua, da adattare una volta implementate le altre lingue $languages_errors = [ "s4-02-04_languages_not_foreseen_if_the_notice_aims_at_reducing_time_limits", "s4-02-04_languages_not_foreseen_if_contract_award_notice", "s4-02-04_languages_not_foreseen_if_it_is_about_a_qualification_system", "s4-02-04_languages_not_foreseen_if_concession_award_notice", "s4-02-04_languages_mandatory_if_this_notice_is_a_call_for_competition", "s4-02-04_languages_mandatory_if_prior_information_notice" ]; if (in_array($err,$languages_errors)) { unset($errors[$index]); continue; } ################# if (!empty($this->mapper["ERRORS_DICT"][$err]["it"])) { $err = $this->mapper["ERRORS_DICT"][$err]["it"]; } else { foreach ([ '/and_lefti_s3_legal_economic_financial_and_technical_information_not_foreseen_if/s' => $this->mapper["ERRORS_DICT"]["and_lefti_s3_legal_economic_financial_and_technical_information_not_foreseen_if"]["it"], '/notice_dispatch_date_element_must_contain_the_real_sending_date_of_xml_notice[.]dispatch_date_in_the_notice_is_(\d+)/s' => 'dateFormatValidationRule', '/lot_no_must_be_unique_within/s' => __guue('Il numero dei lotti deve essere univoco.'), '/element_(\w+)_(.*)_is_not_a_valid_value_of_the_atomic_type_phone/s' => 'phoneNumberValidationRule', '/element_notice_number_oj_[[]facet_pattern[]]_the_value_(.*)_is_not/s' => 'numberNoticeValidationRule', '/annex_d-1_justification_inconsistency_with_the_contract_type/s' => 'annexD1ValidationRule', '/is_not_a_valid_value_of_the_atomic_type_noticeno/s' => __guue('Il numero dell\'avviso non è valido'), '/duplicate_no_doc_ext_a_notice_with_the_same_identifier_has_already_been_received/s' => __guue('Attenzione, un form con lo stesso identificativo è già stato ricevuto'), '/the_agreement_to_the_publication_of_information_is_required_only_in_case_of_a_notice_covered_by_directive_201425eu/s' => 'publicationValidationRule', '/notice[@]type[=](\w+)_and_number(\w+)[=].*the_section_could_not_be_repeated/s' => 'repeatValidationRule', '/s2-02-08_duration_of_the_qualification_system_foreseen_for_a_qualification_system/s' => 'qualificationValidationRule' ] as $regex => $callback) { $matches = []; preg_match($regex, $err, $matches); if (count($matches) != 0) { if (method_exists($this, $callback)) { $err = $this->{$callback}($matches, $raw_err); } else if (is_string($callback)) { $err = $callback; } } } } $section_matches = []; preg_match('/S(\d+[-]\d+[-]\d+)[:]|S(\d+)/',$raw_err,$section_matches); $err_sect = ""; if (!empty($section_matches) && count($section_matches) > 1) { $section_matches = (explode("-",$section_matches[count($section_matches) - 1])); foreach ($section_matches as $i => $identifier) { $section_matches[$i] = (int) $identifier; if ($i == 0) { $section_matches[$i] = "{$latin[$identifier]})"; } } $err_sect = __guue("Sezione") . " {$section_matches[0]} " . (implode(".",array_slice($section_matches,1))) . " "; } $err = $err_sect . $err; $errors[$index] = $err; } return array_unique($errors); } public function setMode(string $string) : void {} public function manageChangeSection() : void {} public function getReorderedSections() : void {} } include_once 'eFormsManager.class.php';