/**
 * 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;
}
Beispiel #2
0
/**
 * 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;
}
Beispiel #3
0
/**
 * 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;
}
Beispiel #5
0
/**
 * 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;
}
Beispiel #6
0
/**
 * 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;
}
Beispiel #7
0
/**
 * 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;
}