/** * Called by administrators; updates the content stored on the "Fields" tab in the Edit Form pages. * * @param integer $form_id the unique form ID * @param array $infohash a hash containing the contents of the Edit Form Display tab * @return array returns array with indexes:<br/> * [0]: true/false (success / failure)<br/> * [1]: message string<br/> */ function ft_update_form_fields_tab($form_id, $infohash) { global $g_table_prefix, $g_root_url, $g_root_dir, $g_debug, $LANG, $g_field_sizes; $success = true; $message = $LANG["notify_field_changes_saved"]; $infohash = ft_sanitize($infohash); extract(ft_process_hook_calls("start", compact("infohash", "form_id"), array("infohash")), EXTR_OVERWRITE); // stores the cleaned-up version of the POST content $field_info = array(); $sortable_id = $infohash["sortable_id"]; $field_ids = explode(",", $infohash["{$sortable_id}_sortable__rows"]); $order = $infohash["sortable_row_offset"]; $new_sort_groups = explode(",", $infohash["{$sortable_id}_sortable__new_groups"]); foreach ($field_ids as $field_id) { $is_new_field = preg_match("/^NEW/", $field_id) ? true : false; $display_name = isset($infohash["field_{$field_id}_display_name"]) ? $infohash["field_{$field_id}_display_name"] : ""; $form_field_name = isset($infohash["field_{$field_id}_name"]) ? $infohash["field_{$field_id}_name"] : ""; $include_on_redirect = isset($infohash["field_{$field_id}_include_on_redirect"]) ? "yes" : "no"; $field_size = isset($infohash["field_{$field_id}_size"]) ? $infohash["field_{$field_id}_size"] : ""; $col_name = isset($infohash["col_{$field_id}_name"]) ? $infohash["col_{$field_id}_name"] : ""; $old_field_size = isset($infohash["old_field_{$field_id}_size"]) ? $infohash["old_field_{$field_id}_size"] : ""; $old_col_name = isset($infohash["old_col_{$field_id}_name"]) ? $infohash["old_col_{$field_id}_name"] : ""; $is_system_field = in_array($field_id, $infohash["system_fields"]) ? "yes" : "no"; // this is only sent for non-system fields $field_type_id = isset($infohash["field_{$field_id}_type_id"]) ? $infohash["field_{$field_id}_type_id"] : ""; // won't be defined for new fields $old_field_type_id = isset($infohash["old_field_{$field_id}_type_id"]) ? $infohash["old_field_{$field_id}_type_id"] : ""; $field_info[] = array("is_new_field" => $is_new_field, "field_id" => $field_id, "display_name" => $display_name, "form_field_name" => $form_field_name, "field_type_id" => $field_type_id, "old_field_type_id" => $old_field_type_id, "include_on_redirect" => $include_on_redirect, "is_system_field" => $is_system_field, "list_order" => $order, "is_new_sort_group" => in_array($field_id, $new_sort_groups) ? "yes" : "no", "col_name" => $col_name, "old_col_name" => $old_col_name, "col_name_changed" => $col_name != $old_col_name ? "yes" : "no", "field_size" => $field_size, "old_field_size" => $old_field_size, "field_size_changed" => $field_size != $old_field_size ? "yes" : "no"); $order++; } reset($infohash); // delete any extended field settings for those fields whose field type just changed. Two comments: // 1. this is compatible with editing the fields in the dialog window. When that happens & the user updates // it, the code updates the old_field_type_id info in the page so this is never called. // 2. with the addition of Shared Characteristics, this only deletes fields that aren't mapped between the // two fields types (old and new) $changed_fields = array(); foreach ($field_info as $curr_field_info) { if ($curr_field_info["is_new_field"] || $curr_field_info["is_system_field"] == "yes" || $curr_field_info["field_type_id"] == $curr_field_info["old_field_type_id"]) { continue; } $changed_fields[] = $curr_field_info; } if (!empty($changed_fields)) { $field_type_settings_shared_characteristics = ft_get_settings("field_type_settings_shared_characteristics"); $field_type_map = ft_get_field_type_id_to_identifier(); $shared_settings = array(); foreach ($changed_fields as $changed_field_info) { $field_id = $changed_field_info["field_id"]; $shared_settings[] = ft_get_shared_field_setting_info($field_type_map, $field_type_settings_shared_characteristics, $field_id, $changed_field_info["field_type_id"], $changed_field_info["old_field_type_id"]); ft_delete_extended_field_settings($field_id); ft_delete_field_validation($field_id); } foreach ($shared_settings as $setting) { foreach ($setting as $setting_info) { $field_id = $setting_info["field_id"]; $setting_id = $setting_info["new_setting_id"]; $setting_value = ft_sanitize($setting_info["setting_value"]); mysql_query("\n INSERT INTO {$g_table_prefix}field_settings (field_id, setting_id, setting_value)\n VALUES ({$field_id}, {$setting_id}, '{$setting_value}')\n "); } } } // the database column name and size field both affect the form's actual database table structure. If either // of those changed, we need to update the database $db_col_changes = array(); $db_col_change_hash = array(); // added later. Could use refactoring... $table_name = "{$g_table_prefix}form_{$form_id}"; foreach ($field_info as $curr_field_info) { if ($curr_field_info["col_name_changed"] == "no" && $curr_field_info["field_size_changed"] == "no") { continue; } if ($curr_field_info["is_new_field"]) { continue; } $field_id = $curr_field_info["field_id"]; $old_col_name = $curr_field_info["old_col_name"]; $new_col_name = $curr_field_info["col_name"]; $new_field_size = $curr_field_info["field_size"]; $new_field_size_sql = $g_field_sizes[$new_field_size]["sql"]; list($is_success, $err_message) = _ft_alter_table_column($table_name, $old_col_name, $new_col_name, $new_field_size_sql); if ($is_success) { $db_col_changes[$field_id] = array("col_name" => $new_col_name, "field_size" => $new_field_size); } else { // if there have already been successful database column name changes already made, // update the database. This helps prevent things getting out of whack if (!empty($db_col_changes)) { while (list($field_id, $changes) = each($db_col_changes)) { $col_name = $changes["col_name"]; $field_size = $changes["field_size"]; @mysql_query("\n UPDATE {$g_table_prefix}form_fields\n SET col_name = '{$col_name}',\n field_size = '{$field_size}'\n WHERE field_id = {$field_id}\n "); } } $message = $LANG["validation_db_not_updated_invalid_input"]; if ($g_debug) { $message .= " \"{$err_message}\""; } return array(false, $message); } } // now update the fields, and, if need be, the form's database table foreach ($field_info as $field) { if ($field["is_new_field"]) { continue; } $field_id = $field["field_id"]; $display_name = $field["display_name"]; $field_name = $field["form_field_name"]; $field_type_id = $field["field_type_id"]; $include_on_redirect = $field["include_on_redirect"]; $is_system_field = $field["is_system_field"]; $field_size = $field["field_size"]; $col_name = $field["col_name"]; $list_order = $field["list_order"]; $is_new_sort_group = $field["is_new_sort_group"]; if ($is_system_field == "yes") { $query = "\n UPDATE {$g_table_prefix}form_fields\n SET field_title = '{$display_name}',\n include_on_redirect = '{$include_on_redirect}',\n list_order = {$list_order},\n is_new_sort_group = '{$is_new_sort_group}'\n WHERE field_id = {$field_id}\n "; } else { $query = "\n UPDATE {$g_table_prefix}form_fields\n SET field_name = '{$field_name}',\n field_title = '{$display_name}',\n field_size = '{$field_size}',\n col_name = '{$col_name}',\n field_type_id = '{$field_type_id}',\n include_on_redirect = '{$include_on_redirect}',\n list_order = {$list_order},\n is_new_sort_group = '{$is_new_sort_group}'\n WHERE field_id = {$field_id}\n "; } mysql_query($query) or ft_handle_error("Failed query in <b>" . __FUNCTION__ . "</b>, line " . __LINE__ . ": <i>{$query}</i>", mysql_error()); } // if any of the database column names just changed we need to update any View filters that relied on them if (!empty($db_col_changes)) { while (list($field_id, $changes) = each($db_col_changes)) { ft_update_field_filters($field_id); } } // okay! now add any new fields that the user just added $new_fields = array(); foreach ($field_info as $curr_field) { if ($curr_field["is_new_field"]) { $new_fields[] = $curr_field; } } if (!empty($new_fields)) { list($is_success, $error) = ft_add_form_fields($form_id, $new_fields); // if there was a problem adding any of the new fields, inform the user if (!$is_success) { $success = false; $message = $error; } } // Lastly, delete the specified fields. Since some field types (e.g. files) may have additional functionality // needed at this stage (e.g. deleting the actual files that had been uploaded via the form). This occurs regardless // of whether the add fields step worked or not $deleted_field_ids = explode(",", $infohash["{$sortable_id}_sortable__deleted_rows"]); extract(ft_process_hook_calls("delete_fields", compact("deleted_field_ids", "infohash", "form_id"), array()), EXTR_OVERWRITE); // now actually delete the fields ft_delete_form_fields($form_id, $deleted_field_ids); extract(ft_process_hook_calls("end", compact("infohash", "field_info", "form_id"), array("success", "message")), EXTR_OVERWRITE); return array($success, $message); }
/** * Adds/updates all options for a given field. This is called when the user edits fields from the dialog * window on the Fields tab. It updates all information about a field: including the custom settings. * * @param integer $form_id The unique form ID * @param integer $field_id The unique field ID * @param integer $info a hash containing tab1 and/or tab2 indexes, containing all the latest values for * the field * @param array [0] success/fail (boolean), [1] empty string for success, or error message */ function ft_update_field($form_id, $field_id, $tab_info) { global $g_table_prefix, $g_field_sizes, $g_debug, $LANG; $tab_info = ft_sanitize($tab_info); $existing_form_field_info = ft_get_form_field($field_id); // TAB 1: this tab contains the standard settings shared by all fields, regardless of type: display text, // form field name, field type, pass on, field size, data type and database col name $db_col_name_changes = array(); if (is_array($tab_info["tab1"])) { $info = $tab_info["tab1"]; $display_name = _ft_extract_array_val($info, "edit_field__display_text"); // bit weird. this field is a checkbox, so if it's not checked it won't be in the request and // _ft_extract_array_val returns an empty string $include_on_redirect = _ft_extract_array_val($info, "edit_field__pass_on"); $include_on_redirect = empty($include_on_redirect) ? "no" : "yes"; if ($existing_form_field_info["is_system_field"] == "yes") { $query = "\n UPDATE {$g_table_prefix}form_fields\n SET field_title = '{$display_name}',\n include_on_redirect = '{$include_on_redirect}'\n WHERE field_id = {$field_id}\n "; $result = mysql_query($query); if (!$result) { return array(false, $LANG["phrase_query_problem"] . $query); } } else { $field_name = _ft_extract_array_val($info, "edit_field__field_name"); $field_type_id = _ft_extract_array_val($info, "edit_field__field_type"); $field_size = _ft_extract_array_val($info, "edit_field__field_size"); $data_type = _ft_extract_array_val($info, "edit_field__data_type"); $col_name = _ft_extract_array_val($info, "edit_field__db_column"); $query = mysql_query("\n UPDATE {$g_table_prefix}form_fields\n SET field_name = '{$field_name}',\n field_type_id = '{$field_type_id}',\n field_size = '{$field_size}',\n field_title = '{$display_name}',\n data_type = '{$data_type}',\n include_on_redirect = '{$include_on_redirect}',\n col_name = '{$col_name}'\n WHERE field_id = {$field_id}\n "); // if the column name or field size just changed, we need to "physically" update the form's database table // If this fails, we rollback both the field TYPE and the field size. // BUG The *one* potential issue here is if the user just deleted a field type, then updated a field which - for // whatever reason - fails. But this is very much a fringe case $old_field_size = $existing_form_field_info["field_size"]; $old_col_name = $existing_form_field_info["col_name"]; $old_field_type_id = $existing_form_field_info["field_type_id"]; if ($old_field_size != $field_size || $old_col_name != $col_name) { $new_field_size_sql = $g_field_sizes[$field_size]["sql"]; $table_name = "{$g_table_prefix}form_{$form_id}"; list($is_success, $err_message) = _ft_alter_table_column($table_name, $old_col_name, $col_name, $new_field_size_sql); if ($is_success) { if ($old_col_name != $col_name) { $db_col_name_changes[] = $field_id; } } else { $query = mysql_query("\n UPDATE {$g_table_prefix}form_fields\n SET field_type_id = '{$old_field_type_id}',\n field_size = '{$old_field_size}',\n col_name = '{$old_col_name}'\n WHERE field_id = {$field_id}\n "); return array(false, $LANG["phrase_query_problem"] . $err_message); } } // if the field type just changed, the field-specific settings are orphaned. Drop them. In this instance, the // client-side code ensures that the contents of the second tab are always passed so the code below will add // any default values that are needed if ($old_field_type_id != $field_type_id) { ft_delete_extended_field_settings($field_id); } } } // if any of the database column names just changed we need to update any View filters that relied on them if (!empty($db_col_name_changes)) { foreach ($db_col_name_changes as $field_id) { ft_update_field_filters($field_id); } } // TAB 2: update the custom field settings for this field type. tab2 can be any of these values: // 1. a string "null": indicating that the user didn't change anything on the tab) // 2. the empty string: indicating that things DID change, but nothing is being passed on. This can happen // when the user checked the "Use Default Value" for all fields on the tab & the tab // doesn't contain an option list or form field // 3. an array of values if (isset($tab_info["tab2"]) && $tab_info["tab2"] != "null") { $info = is_array($tab_info["tab2"]) ? $tab_info["tab2"] : array(); // since the second tab is being updated, we can rely on all the latest & greatest values being passed // in the request, so clean out all old values ft_delete_extended_field_settings($field_id); // convert the $info (which is an array of hashes) into a friendlier hash. This makes detecting for Option // List fields much easier $setting_hash = array(); for ($i = 0; $i < count($info); $i++) { $setting_hash[$info[$i]["name"]] = $info[$i]["value"]; } $new_settings = array(); while (list($setting_name, $setting_value) = each($setting_hash)) { // ignore the additional field ID and field order rows that are custom to Option List / Form Field types. They'll // be handled below if (preg_match("/edit_field__setting_(\\d)+_field_id/", $setting_name) || preg_match("/edit_field__setting_(\\d)+_field_order/", $setting_name)) { continue; } // TODO BUG. newlines aren't surviving this... why was it added? double quotes? single quotes? $setting_value = ft_sanitize(stripslashes($setting_value)); $setting_id = preg_replace("/edit_field__setting_/", "", $setting_name); // if this field is being mapped to a form field, we serialize the form ID, field ID and order into a single var and // give it a "form_field:" prefix, so we know exactly what the data contains & we can select the appropriate form ID // and not Option List ID on re-editing. This keeps everything pretty simple, rather than spreading the data amongst // multiple fields if (preg_match("/^ft/", $setting_value)) { $setting_value = preg_replace("/^ft/", "", $setting_value); $setting_value = "form_field:{$setting_value}|" . $setting_hash["edit_field__setting_{$setting_id}_field_id"] . "|" . $setting_hash["edit_field__setting_{$setting_id}_field_order"]; } $new_settings[] = "({$field_id}, {$setting_id}, '{$setting_value}')"; } if (!empty($new_settings)) { $new_settings_str = implode(",", $new_settings); $query = "\n INSERT INTO {$g_table_prefix}field_settings (field_id, setting_id, setting_value)\n VALUES {$new_settings_str}\n "; $result = @mysql_query($query) or die($query . " - " . mysql_error()); if (!$result) { return array(false, $LANG["phrase_query_problem"] . $query . ", " . mysql_error()); } } } if (isset($tab_info["tab3"]) && $tab_info["tab3"] != "null") { $validation = is_array($tab_info["tab3"]) ? $tab_info["tab3"] : array(); mysql_query("DELETE FROM {$g_table_prefix}field_validation WHERE field_id = {$field_id}"); $new_rules = array(); foreach ($validation as $rule_info) { // ignore the checkboxes - we don't need 'em if (!preg_match("/^edit_field__v_(.*)_message\$/", $rule_info["name"], $matches)) { continue; } $rule_id = $matches[1]; $error_message = ft_sanitize($rule_info["value"]); mysql_query("\n INSERT INTO {$g_table_prefix}field_validation (rule_id, field_id, error_message)\n VALUES ({$rule_id}, {$field_id}, '{$error_message}')\n "); } } $success = true; $message = $LANG["notify_form_field_options_updated"]; extract(ft_process_hook_calls("end", compact("field_id"), array("success", "message")), EXTR_OVERWRITE); return array($success, $message); }