/** * History table's field name column used to be 32 chars long (before 1.1.0a4), * while custom field names can be up to 64. This function updates history * records related to long custom fields to store the complete field name * instead of the truncated version * * * @return int 2, because that's what ADOdb/DataDict does when things happen properly */ function install_update_history_long_custom_fields() { # Disable query logging even if enabled in config, due to possibility of mass spam $t_log_queries = install_set_log_queries(); # Build list of custom field names longer than 32 chars for reference $t_custom_field_table = db_get_table('custom_field'); $t_query = "SELECT name FROM {$t_custom_field_table}"; $t_result = db_query_bound($t_query); while ($t_field = db_fetch_array($t_result)) { if (utf8_strlen($t_field[0]) > 32) { $t_custom_fields[utf8_substr($t_field[0], 0, 32)] = $t_field[0]; } } if (!isset($t_custom_fields)) { # There are no custom fields longer than 32, nothing to do # Re-enable query logging if we disabled it install_set_log_queries($t_log_queries); return 2; } # Build list of standard fields to filter out from history # Fields mapping: category_id is actually logged in history as 'category' $t_standard_fields = array_replace(columns_get_standard(false), array('category_id'), array('category')); $t_field_list = ""; foreach ($t_standard_fields as $t_field) { $t_field_list .= "'{$t_field}', "; } $t_field_list = rtrim($t_field_list, ', '); # Get the list of custom fields from the history table $t_history_table = db_get_table('bug_history'); $t_query = "SELECT DISTINCT field_name\n\t\tFROM {$t_history_table}\n\t\tWHERE type = " . NORMAL_TYPE . "\n\t\tAND field_name NOT IN ( {$t_field_list} )"; $t_result = db_query_bound($t_query); # For each entry, update the truncated custom field name with its full name # if a matching custom field exists while ($t_field = db_fetch_array($t_result)) { # If field name's length is 32, then likely it was truncated so we try to match if (utf8_strlen($t_field[0]) == 32 && array_key_exists($t_field[0], $t_custom_fields)) { # Match found, update all history records with this field name $t_update_query = "UPDATE {$t_history_table}\n\t\t\t\tSET field_name = " . db_param() . "\n\t\t\t\tWHERE field_name = " . db_param(); db_query_bound($t_update_query, array($t_custom_fields[$t_field[0]], $t_field[0])); } } # Re-enable query logging if we disabled it install_set_log_queries($t_log_queries); return 2; }
/** * Get all accessible columns for the current project / current user.. * @param int $p_project_id project id * @return array array of columns * @access public */ function columns_get_all($p_project_id = null) { $t_columns = columns_get_standard(); # add plugin columns $t_columns = array_merge($t_columns, array_keys(columns_get_plugin_columns())); # Add project custom fields to the array. Only add the ones for which the current user has at least read access. if ($p_project_id === null) { $t_project_id = helper_get_current_project(); } else { $t_project_id = $p_project_id; } $t_related_custom_field_ids = custom_field_get_linked_ids($t_project_id); foreach ($t_related_custom_field_ids as $t_id) { if (!custom_field_has_read_access_by_project_id($t_id, $t_project_id)) { continue; } $t_def = custom_field_get_definition($t_id); $t_columns[] = 'custom_' . $t_def['name']; } return $t_columns; }
/** * Gets the next accessible history event for current user and specified db result. * @param string $p_result The database result. * @param integer $p_user_id The user id or null for logged in user. * @param boolean $p_check_access_to_issue true: check that user has access to bugs, * false otherwise. * @return array containing the history event or false if no more matches. */ function history_get_event_from_row($p_result, $p_user_id = null, $p_check_access_to_issue = true) { static $s_bug_visible = array(); $t_user_id = null === $p_user_id ? auth_get_current_user_id() : $p_user_id; $t_project_id = helper_get_current_project(); while ($t_row = db_fetch_array($p_result)) { extract($t_row, EXTR_PREFIX_ALL, 'v'); # Make sure the entry belongs to current project. if ($t_project_id != ALL_PROJECTS && $t_project_id != bug_get_field($v_bug_id, 'project_id')) { continue; } if ($p_check_access_to_issue) { if (!isset($s_bug_visible[$v_bug_id])) { $s_bug_visible[$v_bug_id] = access_has_bug_level(VIEWER, $v_bug_id); } if (!$s_bug_visible[$v_bug_id]) { continue; } } if ($v_type == NORMAL_TYPE) { if (!in_array($v_field_name, columns_get_standard())) { # check that the item should be visible to the user $t_field_id = custom_field_get_id_from_name($v_field_name); if (false !== $t_field_id && !custom_field_has_read_access($t_field_id, $v_bug_id, $t_user_id)) { continue; } } if ($v_field_name == 'target_version' && !access_has_bug_level(config_get('roadmap_view_threshold'), $v_bug_id, $t_user_id)) { continue; } if ($v_field_name == 'due_date' && !access_has_bug_level(config_get('due_date_view_threshold'), $v_bug_id, $t_user_id)) { continue; } if ($v_field_name == 'handler_id' && !access_has_bug_level(config_get('view_handler_threshold'), $v_bug_id, $t_user_id)) { continue; } } # bugnotes if ($t_user_id != $v_user_id) { # bypass if user originated note if ($v_type == BUGNOTE_ADDED || $v_type == BUGNOTE_UPDATED || $v_type == BUGNOTE_DELETED) { if (!access_has_bug_level(config_get('private_bugnote_threshold'), $v_bug_id, $t_user_id) && bugnote_get_field($v_old_value, 'view_state') == VS_PRIVATE) { continue; } } if ($v_type == BUGNOTE_STATE_CHANGED) { if (!access_has_bug_level(config_get('private_bugnote_threshold'), $v_bug_id, $t_user_id) && bugnote_get_field($v_new_value, 'view_state') == VS_PRIVATE) { continue; } } } # tags if ($v_type == TAG_ATTACHED || $v_type == TAG_DETACHED || $v_type == TAG_RENAMED) { if (!access_has_bug_level(config_get('tag_view_threshold'), $v_bug_id, $t_user_id)) { continue; } } # attachments if ($v_type == FILE_ADDED || $v_type == FILE_DELETED) { if (!access_has_bug_level(config_get('view_attachments_threshold'), $v_bug_id, $t_user_id)) { continue; } } # monitoring if ($v_type == BUG_MONITOR || $v_type == BUG_UNMONITOR) { if (!access_has_bug_level(config_get('show_monitor_list_threshold'), $v_bug_id, $t_user_id)) { continue; } } # relationships if ($v_type == BUG_ADD_RELATIONSHIP || $v_type == BUG_DEL_RELATIONSHIP || $v_type == BUG_REPLACE_RELATIONSHIP) { $t_related_bug_id = $v_new_value; # If bug doesn't exist, then we don't know whether to expose it or not based on the fact whether it was # accessible to user or not. This also simplifies client code that is accessing the history log. if (!bug_exists($t_related_bug_id) || !access_has_bug_level(config_get('view_bug_threshold'), $t_related_bug_id, $t_user_id)) { continue; } } $t_event = array(); $t_event['bug_id'] = $v_bug_id; $t_event['date'] = $v_date_modified; $t_event['userid'] = $v_user_id; # user_get_name handles deleted users, and username vs realname $t_event['username'] = user_get_name($v_user_id); $t_event['field'] = $v_field_name; $t_event['type'] = $v_type; $t_event['old_value'] = $v_old_value; $t_event['new_value'] = $v_new_value; return $t_event; } return false; }
/** * Retrieves the raw history events for the specified bug id and returns it in an array * The array is indexed from 0 to N-1. The second dimension is: 'date', 'userid', 'username', * 'field','type','old_value','new_value' * @param int $p_bug_id * @param int $p_user_id * @return array */ function history_get_raw_events_array($p_bug_id, $p_user_id = null) { $t_mantis_bug_history_table = db_get_table('mantis_bug_history_table'); $t_mantis_user_table = db_get_table('mantis_user_table'); $t_history_order = config_get('history_order'); $c_bug_id = db_prepare_int($p_bug_id); $t_user_id = null === $p_user_id ? auth_get_current_user_id() : $p_user_id; $t_roadmap_view_access_level = config_get('roadmap_view_threshold'); $t_due_date_view_threshold = config_get('due_date_view_threshold'); # grab history and display by date_modified then field_name # @@@ by MASC I guess it's better by id then by field_name. When we have more history lines with the same # date, it's better to respect the storing order otherwise we should risk to mix different information # I give you an example. We create a child of a bug with different custom fields. In the history of the child # bug we will find the line related to the relationship mixed with the custom fields (the history is creted # for the new bug with the same timestamp...) $query = "SELECT *\n\t\t\t\tFROM {$t_mantis_bug_history_table}\n\t\t\t\tWHERE bug_id=" . db_param() . "\n\t\t\t\tORDER BY date_modified {$t_history_order},id"; $result = db_query_bound($query, array($c_bug_id)); $raw_history_count = db_num_rows($result); $raw_history = array(); $t_private_bugnote_threshold = config_get('private_bugnote_threshold'); $t_private_bugnote_visible = access_has_bug_level(config_get('private_bugnote_threshold'), $p_bug_id, $t_user_id); $t_tag_view_threshold = config_get('tag_view_threshold'); $t_view_attachments_threshold = config_get('view_attachments_threshold'); $t_show_monitor_list_threshold = config_get('show_monitor_list_threshold'); $t_show_handler_threshold = config_get('view_handler_threshold'); $t_standard_fields = columns_get_standard(); for ($i = 0, $j = 0; $i < $raw_history_count; ++$i) { $t_row = db_fetch_array($result); $v_type = $t_row['type']; $v_field_name = $t_row['field_name']; $v_user_id = $t_row['user_id']; $v_new_value = $t_row['new_value']; $v_old_value = $t_row['old_value']; $v_date_modified = $t_row['date_modified']; if ($v_type == NORMAL_TYPE) { if (!in_array($v_field_name, $t_standard_fields)) { # check that the item should be visible to the user # We are passing 32 here to notify the custom field API # that legacy history entries for field names longer than # 32 chars created when the db column was of that size were # truncated (no longer the case since 1.1.0a4, see #8002) $t_field_id = custom_field_get_id_from_name($v_field_name, 32); if (false !== $t_field_id && !custom_field_has_read_access($t_field_id, $p_bug_id, $t_user_id)) { continue; } } if ($v_field_name == 'target_version' && !access_has_bug_level($t_roadmap_view_access_level, $p_bug_id, $t_user_id)) { continue; } if ($v_field_name == 'due_date' && !access_has_bug_level($t_due_date_view_threshold, $p_bug_id, $t_user_id)) { continue; } if ($v_field_name == 'handler_id' && !access_has_bug_level($t_show_handler_threshold, $p_bug_id, $t_user_id)) { continue; } } // bugnotes if ($t_user_id != $v_user_id) { // bypass if user originated note if ($v_type == BUGNOTE_ADDED || $v_type == BUGNOTE_UPDATED || $v_type == BUGNOTE_DELETED) { if (!$t_private_bugnote_visible && bugnote_get_field($v_old_value, 'view_state') == VS_PRIVATE) { continue; } } if ($v_type == BUGNOTE_STATE_CHANGED) { if (!$t_private_bugnote_visible && bugnote_get_field($v_new_value, 'view_state') == VS_PRIVATE) { continue; } } } // tags if ($v_type == TAG_ATTACHED || $v_type == TAG_DETACHED || $v_type == TAG_RENAMED) { if (!access_has_bug_level($t_tag_view_threshold, $p_bug_id, $t_user_id)) { continue; } } # attachments if ($v_type == FILE_ADDED || $v_type == FILE_DELETED) { if (!access_has_bug_level($t_view_attachments_threshold, $p_bug_id, $t_user_id)) { continue; } } // monitoring if ($v_type == BUG_MONITOR || $v_type == BUG_UNMONITOR) { if (!access_has_bug_level($t_show_monitor_list_threshold, $p_bug_id, $t_user_id)) { continue; } } # relationships if ($v_type == BUG_ADD_RELATIONSHIP || $v_type == BUG_DEL_RELATIONSHIP || $v_type == BUG_REPLACE_RELATIONSHIP) { $t_related_bug_id = $v_new_value; # If bug doesn't exist, then we don't know whether to expose it or not based on the fact whether it was # accessible to user or not. This also simplifies client code that is accessing the history log. if (!bug_exists($t_related_bug_id) || !access_has_bug_level(VIEWER, $t_related_bug_id, $t_user_id)) { continue; } } $raw_history[$j]['date'] = $v_date_modified; $raw_history[$j]['userid'] = $v_user_id; # user_get_name handles deleted users, and username vs realname $raw_history[$j]['username'] = user_get_name($v_user_id); $raw_history[$j]['field'] = $v_field_name; $raw_history[$j]['type'] = $v_type; $raw_history[$j]['old_value'] = $v_old_value; $raw_history[$j]['new_value'] = $v_new_value; $j++; } # end for loop return $raw_history; }
/** * Retrieves the raw history events for the specified bug id and returns it in an array * The array is indexed from 0 to N-1. The second dimension is: 'date', 'userid', 'username', * 'field','type','old_value','new_value' * @param integer $p_bug_id A valid bug identifier. * @param integer $p_user_id A valid user identifier. * @param integer $p_start_time The start time to filter by, or null for all. * @param integer $p_end_time The end time to filter by, or null for all. * @return array */ function history_get_raw_events_array($p_bug_id, $p_user_id = null, $p_start_time = null, $p_end_time = null) { $t_history_order = config_get('history_order'); $t_user_id = null === $p_user_id ? auth_get_current_user_id() : $p_user_id; $t_roadmap_view_access_level = config_get('roadmap_view_threshold'); $t_due_date_view_threshold = config_get('due_date_view_threshold'); # grab history and display by date_modified then field_name # @@@ by MASC I guess it's better by id then by field_name. When we have more history lines with the same # date, it's better to respect the storing order otherwise we should risk to mix different information # I give you an example. We create a child of a bug with different custom fields. In the history of the child # bug we will find the line related to the relationship mixed with the custom fields (the history is creted # for the new bug with the same timestamp...) $t_params = array($p_bug_id); $t_query = 'SELECT * FROM {bug_history} WHERE bug_id=' . db_param(); $t_where = array(); if ($p_start_time !== null) { $t_where[] = 'date_modified >= ' . db_param(); $t_params[] = $p_start_time; } if ($p_end_time !== null) { $t_where[] = 'date_modified < ' . db_param(); $t_params[] = $p_end_time; } if (count($t_where) > 0) { $t_query .= ' AND ' . implode(' AND ', $t_where); } $t_query .= ' ORDER BY date_modified ' . $t_history_order . ',id'; $t_result = db_query($t_query, $t_params); $t_raw_history = array(); $t_private_bugnote_visible = access_has_bug_level(config_get('private_bugnote_threshold'), $p_bug_id, $t_user_id); $t_tag_view_threshold = config_get('tag_view_threshold'); $t_view_attachments_threshold = config_get('view_attachments_threshold'); $t_show_monitor_list_threshold = config_get('show_monitor_list_threshold'); $t_show_handler_threshold = config_get('view_handler_threshold'); $t_standard_fields = columns_get_standard(); $j = 0; while ($t_row = db_fetch_array($t_result)) { extract($t_row, EXTR_PREFIX_ALL, 'v'); if ($v_type == NORMAL_TYPE) { if (!in_array($v_field_name, $t_standard_fields)) { # check that the item should be visible to the user $t_field_id = custom_field_get_id_from_name($v_field_name); if (false !== $t_field_id && !custom_field_has_read_access($t_field_id, $p_bug_id, $t_user_id)) { continue; } } if ($v_field_name == 'target_version' && !access_has_bug_level($t_roadmap_view_access_level, $p_bug_id, $t_user_id)) { continue; } if ($v_field_name == 'due_date' && !access_has_bug_level($t_due_date_view_threshold, $p_bug_id, $t_user_id)) { continue; } if ($v_field_name == 'handler_id' && !access_has_bug_level($t_show_handler_threshold, $p_bug_id, $t_user_id)) { continue; } } # bugnotes if ($t_user_id != $v_user_id) { # bypass if user originated note if ($v_type == BUGNOTE_ADDED || $v_type == BUGNOTE_UPDATED || $v_type == BUGNOTE_DELETED) { if (!$t_private_bugnote_visible && bugnote_get_field($v_old_value, 'view_state') == VS_PRIVATE) { continue; } } if ($v_type == BUGNOTE_STATE_CHANGED) { if (!$t_private_bugnote_visible && bugnote_get_field($v_new_value, 'view_state') == VS_PRIVATE) { continue; } } } # tags if ($v_type == TAG_ATTACHED || $v_type == TAG_DETACHED || $v_type == TAG_RENAMED) { if (!access_has_bug_level($t_tag_view_threshold, $p_bug_id, $t_user_id)) { continue; } } # attachments if ($v_type == FILE_ADDED || $v_type == FILE_DELETED) { if (!access_has_bug_level($t_view_attachments_threshold, $p_bug_id, $t_user_id)) { continue; } } # monitoring if ($v_type == BUG_MONITOR || $v_type == BUG_UNMONITOR) { if (!access_has_bug_level($t_show_monitor_list_threshold, $p_bug_id, $t_user_id)) { continue; } } # relationships if ($v_type == BUG_ADD_RELATIONSHIP || $v_type == BUG_DEL_RELATIONSHIP || $v_type == BUG_REPLACE_RELATIONSHIP) { $t_related_bug_id = $v_new_value; # If bug doesn't exist, then we don't know whether to expose it or not based on the fact whether it was # accessible to user or not. This also simplifies client code that is accessing the history log. if (!bug_exists($t_related_bug_id) || !access_has_bug_level(config_get('view_bug_threshold'), $t_related_bug_id, $t_user_id)) { continue; } } $t_raw_history[$j]['date'] = $v_date_modified; $t_raw_history[$j]['userid'] = $v_user_id; # user_get_name handles deleted users, and username vs realname $t_raw_history[$j]['username'] = user_get_name($v_user_id); $t_raw_history[$j]['field'] = $v_field_name; $t_raw_history[$j]['type'] = $v_type; $t_raw_history[$j]['old_value'] = $v_old_value; $t_raw_history[$j]['new_value'] = $v_new_value; $j++; } # end for loop return $t_raw_history; }
/** * Retrieves the raw history events for the specified bug id and returns it in an array * The array is indexed from 0 to N-1. The second dimension is: 'date', 'userid', 'username', * 'field','type','old_value','new_value' * @param int $p_bug_id * @param int $p_user_id * @return array */ function history_get_raw_events_array($p_bug_id, $p_user_id = null) { $t_mantis_bug_history_table = db_get_table('bug_history'); $t_mantis_user_table = db_get_table('user'); $t_history_order = config_get('history_order'); $c_bug_id = db_prepare_int($p_bug_id); $t_user_id = null === $p_user_id ? auth_get_current_user_id() : $p_user_id; $t_roadmap_view_access_level = config_get('roadmap_view_threshold'); $t_due_date_view_threshold = config_get('due_date_view_threshold'); # grab history and display by date_modified then field_name # @@@ by MASC I guess it's better by id then by field_name. When we have more history lines with the same # date, it's better to respect the storing order otherwise we should risk to mix different information # I give you an example. We create a child of a bug with different custom fields. In the history of the child # bug we will find the line related to the relationship mixed with the custom fields (the history is creted # for the new bug with the same timestamp...) $query = "SELECT *\n\t\t\t\tFROM {$t_mantis_bug_history_table}\n\t\t\t\tWHERE bug_id=" . db_param() . "\n\t\t\t\tORDER BY date_modified {$t_history_order},id"; $result = db_query_bound($query, array($c_bug_id)); $raw_history_count = db_num_rows($result); $raw_history = array(); $t_private_bugnote_threshold = config_get('private_bugnote_threshold'); $t_private_bugnote_visible = access_has_bug_level(config_get('private_bugnote_threshold'), $p_bug_id, $t_user_id); $t_standard_fields = columns_get_standard(); for ($i = 0, $j = 0; $i < $raw_history_count; ++$i) { $t_row = db_fetch_array($result); $v_type = $t_row['type']; $v_field_name = $t_row['field_name']; $v_user_id = $t_row['user_id']; $v_new_value = $t_row['new_value']; $v_old_value = $t_row['old_value']; $v_date_modified = $t_row['date_modified']; if ($v_type == NORMAL_TYPE) { if (!in_array($v_field_name, $t_standard_fields)) { // check that the item should be visible to the user // custom fields - we are passing 32 here to notify the API that the custom field name is truncated by the history column from 64 to 32 characters. $t_field_id = custom_field_get_id_from_name($v_field_name, 32); if (false !== $t_field_id && !custom_field_has_read_access($t_field_id, $p_bug_id, $t_user_id)) { continue; } } if ($v_field_name == 'target_version' && !access_has_bug_level($t_roadmap_view_access_level, $p_bug_id, $t_user_id)) { continue; } if ($v_field_name == 'due_date' && !access_has_bug_level($t_due_date_view_threshold, $p_bug_id, $t_user_id)) { continue; } } // bugnotes if ($t_user_id != $v_user_id) { // bypass if user originated note if ($v_type == BUGNOTE_ADDED || $v_type == BUGNOTE_UPDATED || $v_type == BUGNOTE_DELETED) { if (!$t_private_bugnote_visible && bugnote_get_field($v_old_value, 'view_state') == VS_PRIVATE) { continue; } } if ($v_type == BUGNOTE_STATE_CHANGED) { if (!$t_private_bugnote_visible && bugnote_get_field($v_new_value, 'view_state') == VS_PRIVATE) { continue; } } } // tags if ($v_type == TAG_ATTACHED || $v_type == TAG_DETACHED || $v_type == TAG_RENAMED) { if (!access_has_global_level(config_get('tag_view_threshold'))) { continue; } } $raw_history[$j]['date'] = $v_date_modified; $raw_history[$j]['userid'] = $v_user_id; # user_get_name handles deleted users, and username vs realname $raw_history[$j]['username'] = user_get_name($v_user_id); $raw_history[$j]['field'] = $v_field_name; $raw_history[$j]['type'] = $v_type; $raw_history[$j]['old_value'] = $v_old_value; $raw_history[$j]['new_value'] = $v_new_value; $j++; } # end for loop return $raw_history; }
/** * Get all accessible columns for the current project / current user.. * @param int $p_project_id project id * @return array array of columns * @access public */ function columns_get_all($p_project_id = null) { $t_columns = columns_get_standard(); # add plugin columns $t_columns = array_merge($t_columns, array_keys(columns_get_plugin_columns())); $t_columns = array_merge($t_columns, columns_get_custom_fields($p_project_id)); return $t_columns; }