From 8de868fe4af21fac192f43464c416b03eac0edbf Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 19 Jul 2016 15:12:43 +0900 Subject: [PATCH] Add new direct reference data element list to edit page An edit page can have a new type of reference data type that is not a link between table A and main table, but a sub table to main table with several text fields + enable field. This sub field list can have a max set, that adds empty rows to keep max empty list available. The sub table filed does not need a read_data element as the read is directly connected to the master table (uses the elemen list key name for table and the elements as read fields). This sub elements need to have at least one as type = text and can have error check addded (currently unique and alphanumeric work). Also fix all old addslashes to correct db_escape_string Read in for reference list also can have multiple elements (read data -> name | seperated) All sub reads have element prefixes Inline documentation update --- www/admin/edit_base.inc | 4 +- www/admin/table_arrays/array_edit_access.inc | 14 +- www/libs/Class.DB.Array.IO.inc | 2 +- www/libs/Class.Form.Generate.inc | 194 +++++++++++++++---- www/libs/Class.Login.inc | 2 +- 5 files changed, 168 insertions(+), 48 deletions(-) diff --git a/www/admin/edit_base.inc b/www/admin/edit_base.inc index 5213b172..61622dac 100644 --- a/www/admin/edit_base.inc +++ b/www/admin/edit_base.inc @@ -311,14 +311,14 @@ $elements[] = $form->form_create_element("color"); $elements[] = $form->form_create_element("description"); // add name/value list here -// $elements[] = $form->form_show_list_table("edit_access_data"); + $elements[] = $form->form_show_list_table("edit_access_data"); break; break; default: print "NO NO NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO!"; break; } -//$form->debug('edit', "Elements:
".$form->print_ar($elements));
+//		$form->debug('edit', "Elements: 
".$form->print_ar($elements));
 		$DATA['elements'] = $elements;
 		$DATA['hidden'] = $form->form_create_hidden_fields();
 		$DATA['save_delete'] = $form->form_create_save_delete();
diff --git a/www/admin/table_arrays/array_edit_access.inc b/www/admin/table_arrays/array_edit_access.inc
index b00af6b0..c6898b8e 100644
--- a/www/admin/table_arrays/array_edit_access.inc
+++ b/www/admin/table_arrays/array_edit_access.inc
@@ -40,13 +40,9 @@
 		"element_list" => array (
 			"edit_access_data" => array (
 				"output_name" => "Edit Access Data",
-				"delete" => 0, // set then reference entries are deleted, else the "enable" flag is only set
+				"type" => "reference_data", # is not a sub table read and connect, but only a sub table with data
+				"max_empty" => 5, # maxium visible if no data is set, if filled add this number to visible
 				"prefix" => "ead",
-				"read_data" => array (
-					"table_name" => "edit_access_data",
-					"pk_id" => "edit_acesss_id",
-					"name" => "name"
-				),
 				"elements" => array (
 					"edit_access_data_id" => array (
 						"output_name" => "Activate",
@@ -68,7 +64,13 @@
 						"output_name" => "Activate",
 						"int" => 1,
 						"element_list" => array(1)
+					),
+					"edit_access_id" => array (
+						"int" => 1,
+						"type" => "hidden",
+						"fk_id" => 1 # reference main key from master table above
 					)
+
 				)
 			)
 		)
diff --git a/www/libs/Class.DB.Array.IO.inc b/www/libs/Class.DB.Array.IO.inc
index 2faa225a..c0021ef8 100644
--- a/www/libs/Class.DB.Array.IO.inc
+++ b/www/libs/Class.DB.Array.IO.inc
@@ -444,7 +444,7 @@ $this->debug('write_check', "[$column][".$this->table_array[$column]["value"]."]
 						if ($addslashes)
 							$q_data .= $this->db_escape_string($this->convert_entities($this->table_array[$column]["value"]));
 						else
-							$q_data .= addslashes($this->table_array[$column]["value"]);
+							$q_data .= $this->db_escape_string($this->table_array[$column]["value"]);
 						$q_data .= "'";
 					}
 				}
diff --git a/www/libs/Class.Form.Generate.inc b/www/libs/Class.Form.Generate.inc
index 3a7bb3cc..ad7a7a3b 100644
--- a/www/libs/Class.Form.Generate.inc
+++ b/www/libs/Class.Form.Generate.inc
@@ -886,8 +886,7 @@
 				{
 					// each error check can be a piped seperated value, lets split it
 //$this->debug('edit', $value["error_check"]);
-					$error_checks = explode("|", $value["error_check"]);
-					foreach ($error_checks as $error_check)
+					foreach (explode('|', $value["error_check"]) as $error_check)
 					{
 						switch ($error_check)
 						{ 
@@ -914,7 +913,7 @@
 								break;
 							// check unique, check if field in table is not yet exist
 							case "unique":
-								$q = "SELECT ".$key." FROM ".$this->table_name." WHERE ".$key." = '".addslashes($this->table_array[$key]["value"])."'";
+								$q = "SELECT ".$key." FROM ".$this->table_name." WHERE ".$key." = '".$this->db_escape_string($this->table_array[$key]["value"])."'";
 								if ($this->table_array[$this->int_pk_name]["value"])
 									$q .= " AND ".$this->int_pk_name." <> ".$this->table_array[$this->int_pk_name]["value"];
 								list($$key) = $this->db_return_row($q);
@@ -1003,6 +1002,14 @@
 				// if mandatory, check that at least on pk exists or if at least the mandatory field is filled
 				while (list($table_name, $reference_array) = each($this->element_list))
 				{
+					// set pk/fk id for this
+					foreach ($reference_array['elements'] as $_name => $_data)
+					{
+						if ($_data['pk_id'])
+							$_pk_name = $_name;
+						if ($_data['fk_id'])
+							$_fk_name = $_name;
+					}
 					// get the leasy of keys from the elements array
 					$keys = array_keys($reference_array["elements"]);
 					// prefix
@@ -1030,10 +1037,8 @@
 							{
 								$mand_okay = 1;
 							}
-						// we found a mandatory field. check now if one is set to satisfy the main mandatory
-						// also check, if this field is mandatory and its not set, but any other, throw an error
-//						for ($i = 0; $i < count($_POST[$prfx.$el_name]); $i ++)
-//						{
+							// we found a mandatory field. check now if one is set to satisfy the main mandatory
+							// also check, if this field is mandatory and its not set, but any other, throw an error
 //$this->debug('edit_error_chk', "RG error - Data[".$prfx.$el_name.": ".$_POST[$prfx.$el_name][$i]." | ".$_POST[$prfx.$el_name]." - ".$reference_array['enable_name']." - ".$_POST[$reference_array['enable_name']][$_POST[$prfx.$el_name][$i]]);
 							if ($data_array["mandatory"] && $_POST[$prfx.$el_name][$i])
 							{
@@ -1058,9 +1063,30 @@
 							{
 								$row_okay[$i] = 0;
 							}
-
-//						}
-
+							// do optional error checks like for normal fields
+							// currently active: unique/alphanumeric
+							if ($data_rray['error_check'])
+							{
+								foreach (explode('|', $value["error_check"]) as $error_check)
+								{
+									switch ($error_check)
+									{
+										// check unique, check if field in table is not yet exist
+										case "unique":
+											$q = "SELECT ".$_pk_name." FROM ".$table_name." WHERE ".$el_name." = '".$this->db_escape_string($_POST[$prfx.$el_name][$i])."'";
+											if ($this->table_array[$this->int_pk_name]["value"])
+												$q .= " AND ".$this->int_pk_name." <> ".$this->table_array[$this->int_pk_name]["value"];
+											list($$key) = $this->db_return_row($q);
+											if ($$key)
+												$this->msg .= sprintf($this->l->__("The field %s in row %s can be used only once!
"), $reference_array["output_name"], $i); + break; + case "alphanumericspace": + if (!preg_match("/^[0-9A-Za-z\ ]+$/", $_POST[$prfx.$el_name][$i])) + $this->msg .= sprintf($this->l->__("Please enter a valid alphanumeric (Numbers and Letters, spaces allowed) value for the %s Field and row %s!
"), $reference_array["output_name"], $i); + break; + } + } + } } // if main mandatory } @@ -1203,7 +1229,7 @@ { //$this->debug('form', "HERE"); // check if this text name already exists (lowercase compare) - $q = "SELECT ".$this->table_array[$key]["pk_name"]." FROM ".$this->table_array[$key]["table_name"]." WHERE LCASE(".$this->table_array[$key]["input_name"].") = '".addslashes(strtolower($this->table_array[$key]["input_value"]))."'"; + $q = "SELECT ".$this->table_array[$key]["pk_name"]." FROM ".$this->table_array[$key]["table_name"]." WHERE LCASE(".$this->table_array[$key]["input_name"].") = '".$this->db_escape_string(strtolower($this->table_array[$key]["input_value"]))."'"; // if a where was given, add here if ($this->table_array[$key]["where"]) $q .= " AND ".$this->table_array[$key]["where"]; @@ -1217,7 +1243,7 @@ // if a where was given, set this key also [dangerous!] // postgreSQL compatible insert - $q = "INSERT INTO ".$this->table_array[$key]["table_name"]." (".$this->table_array[$key]["input_name"].") VALUES ('".addslashes($this->table_array[$key]["input_value"])."')"; + $q = "INSERT INTO ".$this->table_array[$key]["table_name"]." (".$this->table_array[$key]["input_name"].") VALUES ('".$this->db_escape_string($this->table_array[$key]["input_value"])."')"; $this->db_exec($q); if ($this->table_array[$key]["where"]) { @@ -1238,7 +1264,7 @@ if ($this->table_array[$key]["input_value"] != $this->table_array[$key]["value"]) { // check if "right input" is in DB - $q = "SELECT ".$this->table_array[$key]["input_name"]." FROM ".$this->table_array[$key]["table_name"]." WHERE LCASE(".$this->table_array[$key]["input_name"].") = '".strtolower(addslashes($this->table_array[$key]["input_value"]))."'"; + $q = "SELECT ".$this->table_array[$key]["input_name"]." FROM ".$this->table_array[$key]["table_name"]." WHERE LCASE(".$this->table_array[$key]["input_name"].") = '".strtolower($this->db_escape_string($this->table_array[$key]["input_value"]))."'"; // if a where was given, add here if ($this->table_array[$key]["where"]) $q .= " AND ".$this->table_array[$key]["where"]; @@ -1357,6 +1383,8 @@ // check if there is a hidden key, update, else insert while (list($el_name, $data_array) = each($reference_array["elements"])) { + // this is only for reference_data part, at least one of the text fields need to be set for writing + $blow_write = array (); //$this->debug('edit_error_query', "QUERY: ".$this->print_ar($_POST)); // go through all submitted data // for ($i = 0; $i < count($_POST[$el_name]); $i ++) @@ -1372,6 +1400,16 @@ { $no_write[$i] = 1; } + // flag if data is in the text field and we are in a reference data set + if ($reference_array['type'] == 'reference_data' ) + { + if ($data_array['type'] == 'text' && $_POST[$prfx.$el_name][$i]) + $block_write[$i] = 1; + } + else + { + $block_write[$i] = 1; + } // set type and boundaries for insert/update if ($data_array["pk_id"] && $_POST[$prfx.$el_name][$i]) { @@ -1388,19 +1426,24 @@ } // write all data (insert/update) because I don't know until all are processed if it is insert or update // don't write primary key backup for update -$this->debug('edit_error', "I: $i | EL Name: $prfx$el_name | Data: ".$_POST[$prfx.$el_name][$i]." {".$_POST[$prfx.$el_name]."} | Type: ".$type[$i]." | PK: ".$data_array["pk_id"]." "); - if (!$data_array["pk_id"]) + // for reference_data type, only write if at least one text type field is set +//$this->debug('edit_error', "I: $i | EL Name: $prfx$el_name | Data: ".$_POST[$prfx.$el_name][$i]." | Type: ".$type[$i]." | PK: ".$data_array["pk_id"].", Block write: ".$block_write[$i]); + // only add elements that are not PK or FK flaged + if (!$data_array['pk_id'] && !$data_array['fk_id']) { - // update + // update data list if (strlen($q_data[$i])) $q_data[$i] .= ", "; - // insert + // insert name part list if ($q_names[$i]) $q_names[$i] .= ", "; - $q_names[$i] .= $el_name; + // insert value part list if (strlen($q_values[$i])) $q_values[$i] .= ", "; - // data part + // insert column name add + $q_names[$i] .= $el_name; + // data part, read from where [POST] + // radio group selections (only one can be active) if ($data_array['type'] == 'radio_group') { if ($i == $_POST[$prfx.$el_name]) @@ -1412,10 +1455,11 @@ $this->debug('edit_error', "I: $i | EL Name: $prfx$el_name | Data: ".$_POST[$prf { $_value = $_POST[$prfx.$el_name][$i]; } + // pre write data set. if int value, unset flagged need to be set null or 0 depending on settings if ($data_array['int'] || $data_array['int_null']) { if (!$_value && $data_array['int_null']) - $value = 'NULL'; + $_value = 'NULL'; elseif (!isset($_value)) $_value = 0; $q_data[$i] .= $el_name." = ".$_value; @@ -1423,27 +1467,31 @@ $this->debug('edit_error', "I: $i | EL Name: $prfx$el_name | Data: ".$_POST[$prf } else { - $q_data[$i] .= $el_name." = '".addslashes($_value)."'"; - $q_values[$i] .= "'".addslashes($_value)."'"; + // normal data gets escaped + $q_data[$i] .= $el_name." = '".$this->db_escape_string($_value)."'"; + $q_values[$i] .= "'".$this->db_escape_string($_value)."'"; } } } } // eche table elements + // finalize the queries, add FK key reference for inserts and run the query for ($i = 0; $i < count($type); $i ++) { + $q = ''; if (!$no_write[$i]) { if ($type[$i] == "update") { $q = $q_begin[$i].$q_data[$i].$q_end[$i]; } - else + elseif ($block_write[$i]) { $q = $q_begin[$i].$q_names[$i].", ".$this->int_pk_name.$q_middle[$i].$q_values[$i].", ".$this->table_array[$this->int_pk_name]["value"].$q_end[$i]; } -//$this->debug('edit', "Q: ".$q."
"); +$this->debug('edit', "Pos[$i] => ".$type[$i]." Q: ".$q."
"); // write the dataset - $this->db_exec($q); + if ($q) + $this->db_exec($q); } } // for each created query } // each element list @@ -1549,42 +1597,52 @@ $this->debug('edit_error', "I: $i | EL Name: $prfx$el_name | Data: ".$_POST[$prf // PARAMS show which element list // RETURN array for output // DESC create list of elements next to each other for a group of data in an input field + // this currently only works for a list that is filled from a sub table and creates only a connection to this one + // new version will allow a sub list with free input fields to directly fill a sub table to a master table public function form_create_element_list_table($table_name) { + // output name for the viewable left table td box, prefixed with * if mandatory $output_name = $this->element_list[$table_name]["output_name"]; if ($this->element_list[$table_name]["mandatory"]) $output_name .= ' *'; // delete button name, if there is one set if ($this->element_list[$table_name]["delete_name"]) $data['delete_name'] = $this->element_list[$table_name]["delete_name"]; - // set the enable checkbox name if there is one + // set the enable checkbox for delete, if the delete flag is given if there is one if ($this->element_list[$table_name]["enable_name"]) { $data['enable_name'] = $this->element_list[$table_name]["enable_name"]; if ($this->element_list[$table_name]["delete"]) $data['delete'] = 1; } + // prefix for the elements, to not collide with names in the master set if ($this->element_list[$table_name]["prefix"]) $data["prefix"] = $this->element_list[$table_name]["prefix"]."_"; + // the sub data table name $data['table_name'] = $table_name; - $pos = 0; // position in while for overwrite if needed + // build the select part if (!is_array($this->element_list[$table_name]["elements"])) $this->element_list[$table_name]["elements"] = array (); reset($this->element_list[$table_name]["elements"]); // generic data read in (counts for all rows) + // visible list data output while (list($el_name, $data_array) = each($this->element_list[$table_name]["elements"])) { - $_el_name = $el_name; - $el_name = $data["prefix"].$el_name; +// $this->debug('CFG', 'El: '.$el_name.' -> '.$this->print_ar($data_array)); // if the element name matches the read array, then set the table as a name prefix - $q_select[] = $_el_name; // this is for reading the data + $q_select[] = $el_name; // this is for reading the data + // prefix the name for any further data parts + $el_name = $data["prefix"].$el_name; $data['output_name'][$el_name] = $data_array["output_name"]; // this are the output names (if given) $data['type'][$el_name] = $data_array["type"]; /// this is the type of the field // set the primary key name if ($data_array['pk_id']) $data['pk_name'] = $el_name; - // if drop down db read data for element list + if ($data_array['fk_id']) + $data['fk_name'] = $el_name; + // if drop down db read data for element list from the given sub table as from the query + // only two elements are allowed: pos 0 is key, pso 1 is visible output name if ($data_array['type'] == 'drop_down_db') { $md_q = md5($data_array['query']); @@ -1599,57 +1657,80 @@ $this->debug('edit_error', "I: $i | EL Name: $prfx$el_name | Data: ".$_POST[$prf $data['output_data'][$el_name][] = $res[1]; } } - else + elseif ($data_array["element_list"]) { $data['element_list'][$el_name] = $data_array["element_list"]; // this is for the checkboxes } $proto[$el_name] = ($this->error) ? $_POST[$el_name][(count($_POST[$el_name]) - 1)] : ''; // this is for the new line } +// $this->debug('CFG DATA', 'Data: '.$this->print_ar($data)); +// $this->debug('CFG PROTO', 'Proto: '.$this->print_ar($proto)); +// $this->debug('CFG SELECT', 'Proto: '.$this->print_ar($q_select)); // query for reading in the data //$this->debug('edit_error', "ERR: ".$this->error); // if we got a read data, build the read select for the read, and read out the "selected" data if ($this->element_list[$table_name]["read_data"]) { - array_unshift($q_select, $this->element_list[$table_name]["read_data"]["name"]); + // we need a second one for the query build only + // prefix all elements with the $table name + foreach ($q_select as $_pos => $element) + { + $_q_select[$_pos] = $table_name.'.'.$element; + } + // add the read names in here, prefix them with the table name + // earch to read part is split by | + if ($this->element_list[$table_name]["read_data"]["name"]) + { + foreach (explode('|', $this->element_list[$table_name]["read_data"]["name"]) as $read_name) + { + array_unshift($_q_select, $this->element_list[$table_name]["read_data"]["table_name"].'.'.$read_name); + array_unshift($q_select, $read_name); + } + } // set the rest of the data so we can print something out $data['type'][$data["prefix"].$this->element_list[$table_name]["read_data"]["name"]] = 'string'; // build the read query $q = "SELECT "; // if (!$this->table_array[$this->int_pk_name]["value"]) // $q .= "DISTINCT "; - // prefix join key with table name - $q .= str_replace($this->element_list[$table_name]["read_data"]["pk_id"], $this->element_list[$table_name]["read_data"]["table_name"].".".$this->element_list[$table_name]["read_data"]["pk_id"], implode(", ", $q_select))." "; + // prefix join key with table name, and implode the query select part + $q .= str_replace($table_name.'.'.$this->element_list[$table_name]["read_data"]["pk_id"], $this->element_list[$table_name]["read_data"]["table_name"].'.'.$this->element_list[$table_name]["read_data"]["pk_id"], implode(', ', $_q_select)).' '; // if (!$this->table_array[$this->int_pk_name]["value"] && $this->element_list[$table_name]["read_data"]["order"]) // $q .= ", ".$this->element_list[$table_name]["read_data"]["order"]." "; + // read from the read table as main, and left join to the sub table to read the actual data $q .= "FROM ".$this->element_list[$table_name]["read_data"]["table_name"]." "; $q .= "LEFT JOIN ".$table_name." "; $q .= "ON ("; $q .= $this->element_list[$table_name]["read_data"]["table_name"].".".$this->element_list[$table_name]["read_data"]["pk_id"]." = ".$table_name.".".$this->element_list[$table_name]["read_data"]["pk_id"]." "; // if ($this->table_array[$this->int_pk_name]["value"]) - $q .= "AND ".$this->int_pk_name." = ".(($this->table_array[$this->int_pk_name]["value"]) ? $this->table_array[$this->int_pk_name]["value"] : 'NULL')." "; + $q .= "AND ".$table_name.".".$this->int_pk_name." = ".(($this->table_array[$this->int_pk_name]["value"]) ? $this->table_array[$this->int_pk_name]["value"] : 'NULL')." "; $q .= ") "; if ($this->element_list[$table_name]["read_data"]["order"]) - $q .= " ORDER BY ".$this->element_list[$table_name]["read_data"]["order"]; + $q .= " ORDER BY ".$this->element_list[$table_name]["read_data"]["table_name"].'.'.$this->element_list[$table_name]["read_data"]["order"]; } else { // only create query if we have a primary key + // reads directly from the reference table if ($this->table_array[$this->int_pk_name]["value"]) $q = "SELECT ".implode(", ", $q_select)." FROM ".$table_name." WHERE ".$this->int_pk_name." = ".$this->table_array[$this->int_pk_name]["value"]; } +// $this->debug('CFG QUERY', 'Q: '.$q); // only run if we have query strnig if ($q) { + $pos = 0; // position in while for overwrite if needed // read out the list and add the selected data if needed while ($res = $this->db_return($q)) { + $_data = array (); $prfx = $data["prefix"]; // short // go through each res for ($i = 0; $i < count($q_select); $i ++) { // query select part, set to the element name $el_name = $q_select[$i]; -//$this->debug('edit_error', "[$i] POS[$prfx$el_name]: ".$_POST[$prfx.$el_name][$pos]." | RES: ".$res[$el_name]); +//$this->debug('edit_error', "[$i] ELNAME: $el_name | POS[$prfx$el_name]: ".$_POST[$prfx.$el_name][$pos]." | RES: ".$res[$el_name]); // if we have an error, we take what we have in the vars, if not we take the data from the db if ($this->error) { @@ -1674,6 +1755,43 @@ $this->debug('edit_error', "I: $i | EL Name: $prfx$el_name | Data: ".$_POST[$prf unset($_data); } } + // if this is normal single reference data check the content on the element count + // if there is a max_empty is set, then fill up new elements (unfilled) until we reach max empty + if ($this->element_list[$table_name]['type'] == 'reference_data' && is_numeric($this->element_list[$table_name]['max_empty']) && $this->element_list[$table_name]['max_empty'] > 0) + { + // if the max empty is bigger than 10, just cut it to ten at the moment + if ($this->element_list[$table_name]['max_empty'] > 10) + $this->element_list[$table_name]['max_empty'] = 10; + // check if we need to fill fields + $element_count = count($data['content']); + $missing_empty_count = $this->element_list[$table_name]['max_empty'] - count($data['content']); +// $this->debug('CFG MAX', 'Max empty: '.$this->element_list[$table_name]['max_empty'].', Missing: '.$missing_empty_count.', Has: '.$element_count); + if ($missing_empty_count < $this->element_list[$table_name]['max_empty']) + { + for ($pos = count($data['content']); $pos <= ($this->element_list[$table_name]['max_empty'] + $element_count); $pos ++) + { + $_data = array (); + + // the fields that need to be filled are in data->type array: + // pk fields are unfilled + // fk fields are filled with the fk_id "int_pk_name" value + foreach ($data['type'] as $el_name => $type) + { + $_data[$el_name] = ''; + if ($el_name == $data['pk_name']) + { + } + elseif ($el_name == $data['fk_name']) + { + $_data[$el_name] = $this->table_array[$this->int_pk_name]["value"]; + } + } + $data['content'][] = $_data; + $data['pos'][] = array(0 => $pos); // this is for the checkboxes + } + } + } + // push in an empty line of this type, but only if we have a delete key if ($data['delete_name']) $data['content'][] = $proto; diff --git a/www/libs/Class.Login.inc b/www/libs/Class.Login.inc index 9e5da147..53b25eb7 100644 --- a/www/libs/Class.Login.inc +++ b/www/libs/Class.Login.inc @@ -632,7 +632,7 @@ // set the full acl list too $this->acl['acl_list'] = $_SESSION['DEFAULT_ACL_LIST']; // debug - $this->debug('ACL', $this->print_ar($this->acl)); +// $this->debug('ACL', $this->print_ar($this->acl)); } // METHOD: login_check_edit_access