mantis 는 벌써 4년째 써오는 버그 추적 관리 시스템이다.
최근 직장을 옮기면서 또 다시 설치할 기회를 얻었는데,
홈페이지엘 가보니 최신 버전이 1.1.2였다.
맨 처음 설치해 본 버전이 1.0.8 이었는데, 참 많이도 버전업이 되었다.
1.0.8 버전부터 커스터마이징해서 사용해 온 기능이 있는데 바로 버그노트에 파일을 첨부할 수 있는 기능이다.
1.1.2 버전을 새로 설치하면서 커스터마이징된 내용을 기록해 보려고 한다.
우선 커스터마이징한 파일 목록을 살펴보면 다음과 같다.
admin 폴더는 mantis 를 설치/관리하기 위한 페이지 파일들이 들어있는 폴더로서 데이터베이스 생성 스크립트가 수정되었다.
admin/
db_stats.php 파일의 마지막 쯤에 다음 명령문을 추가한다.
print_table_stats( config_get( 'mantis_bugnote_file_table' ) );커스터마이징 작업으로 새로 추가하려는 테이블의 상태를 보여주기 위한 명령문이다.
admin/
db_table_names_inc.php 파일의 마지막에 다음 명령문을 추가한다.
$t_bugnote_file_table = config_get_global( 'mantis_bugnote_file_table' );커스터마이징 작업으로 새로 추가한 테이블의 이름을 저장하기 위한 변수를 선언하는 명령문이다.
admin/
schema.php 파일의 마지막에 다음 내용을 추가한다.
# Release marker: 1.1.2.woohaha$upgrade[] = Array('CreateTableSQL',Array(config_get('mantis_bugnote_file_table')," id I UNSIGNED NOTNULL PRIMARY AUTOINCREMENT, bug_id I UNSIGNED NOTNULL DEFAULT '0', bugnote_id I UNSIGNED NOTNULL DEFAULT '0', title C(250) NOTNULL DEFAULT \" '' \", description C(250) NOTNULL DEFAULT \" '' \", diskfile C(250) NOTNULL DEFAULT \" '' \", filename C(250) NOTNULL DEFAULT \" '' \", folder C(250) NOTNULL DEFAULT \" '' \", filesize I NOTNULL DEFAULT '0', file_type C(250) NOTNULL DEFAULT \" '' \", date_added T NOTNULL DEFAULT '1970-01-01 00:00:01', content B NOTNULL ",Array('mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS')));$upgrade[] = Array('CreateIndexSQL',Array('idx_bugnote_file_bug_id',config_get('mantis_bugnote_file_table'),'bugnote_id'));$upgrade[] = Array('CreateIndexSQL',Array('idx_bugnotediskfile',config_get('mantis_bugnote_file_table'),'diskfile'));mantis_bugnote_file_table 이라는 이름의 테이블을 구성하기 위한 스크립트문이다.
이 테이블의 구성은 기존의 mantis_bug_file_table 의 구성을 기초로 하였다.
차이점이라면 위에서 파란색 글꼴로 표시되어 있는 버그노트ID 부분이다.
core/
bugnote_api.php 파일의 bugnote_delete 함수에 다음과 같은 쿼리 명령문을 추가한다.
# --------------------
# Delete a bugnote
function bugnote_delete( $p_bugnote_id ) {
$c_bugnote_id = db_prepare_int( $p_bugnote_id );
$t_bug_id = bugnote_get_field( $p_bugnote_id, 'bug_id' );
$t_bugnote_text_id = bugnote_get_field( $p_bugnote_id, 'bugnote_text_id' );
$t_bugnote_text_table = config_get( 'mantis_bugnote_text_table' );
$t_bugnote_table = config_get( 'mantis_bugnote_table' );
$t_bugnote_file_table = config_get( 'mantis_bugnote_file_table' ); # Remove the bugnote
$query = "DELETE FROM $t_bugnote_table
WHERE id='$c_bugnote_id'";
db_query( $query );
# Remove the bugnote text
$query = "DELETE FROM $t_bugnote_text_table
WHERE id='$t_bugnote_text_id'";
db_query( $query );
# Remove the bugnote file $query = "DELETE FROM $t_bugnote_file_table WHERE bugnote_id='$c_bugnote_id'"; db_query( $query ); # log deletion of bug
history_log_event_special( $t_bug_id, BUGNOTE_DELETED, bugnote_format_id( $p_bugnote_id ) );
return true;
}
버그노트를 삭제할 때 버그노트에 연결된 첨부파일도 함께 삭제하기 위한 쿼리문이다.
역시
bugnote_api.php 파일에 다음 함수를 추가한다.
# --------------------
# Get array of attachments associated with the specified bugnote id. The array will be
# sorted in terms of date added (ASC). The array will include the following fields:
# id, title, diskfile, filename, filesize, file_type, date_added.
function bugnote_get_attachments( $p_bug_id, $p_bugnote_id ) {
if ( !file_can_view_bugnote_attachments( $p_bug_id, $p_bugnote_id ) ) {
return;
}
$c_bug_id = db_prepare_int( $p_bug_id );
$c_bugnote_id = db_prepare_int( $p_bugnote_id );
$t_bugnote_file_table = config_get( 'mantis_bugnote_file_table' );
$query = "SELECT id, title, diskfile, filename, filesize, file_type, date_added
FROM $t_bugnote_file_table
WHERE bug_id='$c_bug_id' AND bugnote_id='$c_bugnote_id'
ORDER BY date_added";
$db_result = db_query( $query );
$num_notes = db_num_rows( $db_result );
$t_result = array();
for ( $i = 0; $i < $num_notes; $i++ ) {
$t_result[] = db_fetch_array( $db_result );
}
return $t_result;
}
버그노트에 연결된 첨부파일들을 가져오는 기능의 함수이다.
core/
file_api.php 파일에 다음과 같은 내용의 함수를 추가한다.
# --------------------
# Check if the current user can view attachments for the specified bugnote.
function file_can_view_bugnote_attachments( $p_bug_id, $p_bugnote_id ) {
return 1;
$t_reported_by_me = bug_is_user_reporter( $p_bug_id, auth_get_current_user_id() );
$t_can_view = access_has_bug_level( config_get( 'view_attachments_threshold' ), $p_bug_id );
# @@@ Fix this to be readable
$t_can_view = $t_can_view || ( $t_reported_by_me && config_get( 'allow_view_own_attachments' ) );
return $t_can_view;
}
# --------------------
# Check if the current user can download attachments for the specified bugnote.
function file_can_download_bugnote_attachments( $p_bug_id, $p_bugnote_id ) {
return 1;
$t_reported_by_me = bug_is_user_reporter( $p_bug_id, auth_get_current_user_id() );
$t_can_download = access_has_bug_level( config_get( 'download_attachments_threshold' ), $p_bug_id );
# @@@ Fix this to be readable
$t_can_download = $t_can_download || ( $t_reported_by_me && config_get( 'allow_download_own_attachments' ) );
return $t_can_download;
}
# --------------------
# Check if the current user can delete attachments from the specified bug.
function file_can_delete_bugnote_attachments( $p_bug_id, $p_bugnote_id ) {
if ( bug_is_readonly( $p_bug_id ) ) {
return false;
}
return 1;
$t_reported_by_me = bug_is_user_reporter( $p_bug_id, auth_get_current_user_id() );
$t_can_download = access_has_bug_level( config_get( 'delete_attachments_threshold' ), $p_bug_id );
# @@@ Fix this to be readable
$t_can_download = $t_can_download || ( $t_reported_by_me && config_get( 'allow_delete_own_attachments' ) );
return $t_can_download;
}
# --------------------
# List the attachments belonging to the specified bug. This is used from within
# bug_view_page.php and bug_view_advanced_page.php
function bugnotefile_list_attachments( $p_bug_id, $p_bugnote_id ) {
$t_attachment_rows = bugnote_get_attachments( $p_bug_id, $p_bugnote_id );
$num_files = sizeof( $t_attachment_rows );
if ( $num_files === 0 ) {
return;
}
$t_can_download = file_can_download_bugnote_attachments( $p_bug_id, $p_bugnote_id );
$t_can_delete = file_can_delete_bugnote_attachments( $p_bug_id, $p_bugnote_id );
$t_preview_text_ext = config_get( 'preview_text_extensions' );
$t_preview_image_ext = config_get( 'preview_image_extensions' );
$image_previewed = false;
for ( $i = 0 ; $i < $num_files ; $i++ ) {
$row = $t_attachment_rows[$i];
extract( $row, EXTR_PREFIX_ALL, 'v' );
$t_file_display_name = string_display_line( file_get_display_name( $v_filename ) );
$t_filesize = number_format( $v_filesize );
$t_date_added = date( config_get( 'normal_date_format' ), db_unixtimestamp( $v_date_added ) );
if ( $image_previewed ) {
$image_previewed = false;
PRINT '<br />';
}
if ( $t_can_download ) {
$t_href_start = "<a href=\"file_download.php?file_id=$v_id&type=bugnote\">";
$t_href_end = '</a>';
$t_href_clicket = " [<a href=\"file_download.php?file_id=$v_id&type=bugnote\" target=\"_blank\">^</a>]";
} else {
$t_href_start = '';
$t_href_end = '';
$t_href_clicket = '';
}
$t_exists = config_get( 'file_upload_method' ) != DISK || file_exists( $v_diskfile );
if ( !$t_exists ) {
print_file_icon ( $t_file_display_name );
PRINT ' <span class="strike">' . $t_file_display_name . '</span> (attachment missing)';
} else {
PRINT $t_href_start;
print_file_icon ( $t_file_display_name );
PRINT $t_href_end . ' ' . $t_href_start . $t_file_display_name .
$t_href_end . "$t_href_clicket ($t_filesize bytes) <span class=\"italic\">$t_date_added</span>";
if ( $t_can_delete ) {
PRINT " [<a class=\"small\" href=\"bugnote_file_delete.php?file_id=$v_id\">" . lang_get('delete_link') . '</a>]';
}
if ( ( FTP == config_get( 'file_upload_method' ) ) && file_exists ( $v_diskfile ) ) {
PRINT ' (' . lang_get( 'cached' ) . ')';
}
if ( $t_can_download &&
( $v_filesize <= config_get( 'preview_attachments_inline_max_size' ) ) &&
( $v_filesize != 0 ) &&
( in_array( strtolower( file_get_extension( $t_file_display_name ) ), $t_preview_text_ext, true ) ) ) {
$c_id = db_prepare_int( $v_id );
$t_bugnote_file_table = config_get( 'mantis_bugnote_file_table' );
echo "<script type=\"text/javascript\" language=\"JavaScript\">
<!--
function swap_content( span ) {
displayType = ( document.getElementById( span ).style.display == 'none' ) ? '' : 'none';
document.getElementById( span ).style.display = displayType;
}
-->
</script>";
PRINT " <span id=\"hideSection_$c_id\">[<a class=\"small\" href='#' id='attmlink_".$c_id."' onclick='swap_content(\"hideSection_".$c_id."\");swap_content(\"showSection_".$c_id."\");return false;'>". lang_get( 'show_content' ) ."</a>]</span>";
PRINT " <span style='display:none' id=\"showSection_$c_id\">[<a class=\"small\" href='#' id='attmlink_".$c_id."' onclick='swap_content(\"hideSection_".$c_id."\");swap_content(\"showSection_".$c_id."\");return false;'>". lang_get( 'hide_content' ) ."</a>]";
PRINT "<pre>";
switch ( config_get( 'file_upload_method' ) ) {
case DISK:
if ( file_exists( $v_diskfile ) ) {
$v_content=file_get_contents( $v_diskfile );
}
break;
case FTP:
if ( file_exists( $v_diskfile ) ) {
file_get_contents( $v_diskfile );
} else {
$ftp = file_ftp_connect();
file_ftp_get ( $ftp, $v_diskfile, $v_diskfile );
file_ftp_disconnect( $ftp );
$v_content=file_get_contents( $v_diskfile );
}
break;
default:
$query = "SELECT *
FROM $t_bugnote_file_table
WHERE id='$c_id'";
$result = db_query( $query );
$row = db_fetch_array( $result );
$v_content=$row['content'];
}
echo htmlspecialchars($v_content);
PRINT "</pre></span>\n";
}
if ( $t_can_download &&
( $v_filesize <= config_get( 'preview_attachments_inline_max_size' ) ) &&
( $v_filesize != 0 ) &&
( in_array( strtolower( file_get_extension( $t_file_display_name ) ), $t_preview_image_ext, true ) ) ) {
$t_preview_style = 'border: 0;';
$t_max_width = config_get( 'preview_max_width' );
if ( $t_max_width > 0 ) {
$t_preview_style .= ' max-width:' . $t_max_width . 'px;';
}
$t_max_height = config_get( 'preview_max_height' );
if ( $t_max_height > 0 ) {
$t_preview_style .= ' max-height:' . $t_max_height . 'px;';
}
$t_preview_style = 'style="' . $t_preview_style . '"';
$t_title = file_get_field( $v_id, 'title' );
PRINT "\n<br />$t_href_start<img alt=\"$t_title\" $t_preview_style src=\"file_download.php?file_id=$v_id&type=bugnote\" />$t_href_end";
$image_previewed = true;
}
}
if ( $i != ( $num_files - 1 ) ) {
PRINT "<br />\n";
}
}
}
# --------------------
function bugnote_file_add( $p_bug_id, $p_bugnote_id, $p_tmp_file, $p_file_name, $p_file_type='', $p_table = 'bugnote', $p_error = 0, $p_title = '', $p_desc = '' ) {
if ( php_version_at_least( '4.2.0' ) ) {
switch ( $p_error ) {
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
trigger_error( ERROR_FILE_TOO_BIG, ERROR );
case UPLOAD_ERR_PARTIAL:
case UPLOAD_ERR_NO_FILE:
trigger_error( ERROR_FILE_NO_UPLOAD_FAILURE, ERROR );
}
}
if ( ( '' == $p_tmp_file ) || ( '' == $p_file_name ) ) {
trigger_error( ERROR_FILE_NO_UPLOAD_FAILURE, ERROR );
}
if ( !is_readable( $p_tmp_file ) ) {
trigger_error( ERROR_UPLOAD_FAILURE, ERROR );
}
if ( !file_type_check( $p_file_name ) ) {
trigger_error( ERROR_FILE_NOT_ALLOWED, ERROR );
}
if ( !file_is_name_unique( $p_file_name, $p_bug_id ) ) {
trigger_error( ERROR_DUPLICATE_FILE, ERROR );
}
if ( 'bugnote' == $p_table ) {
$t_project_id = bug_get_field( $p_bug_id, 'project_id' );
$t_bug_id = bug_format_id( $p_bug_id );
}else{
$t_project_id = helper_get_current_project();
$t_bug_id = 0;
}
# prepare variables for insertion
$c_bug_id = db_prepare_int( $p_bug_id );
$c_bugnote_id = db_prepare_int ( $p_bugnote_id );
$c_project_id = db_prepare_int( $t_project_id );
$c_file_type = db_prepare_string( $p_file_type );
$c_title = db_prepare_string( $p_title );
$c_desc = db_prepare_string( $p_desc );
if( $t_project_id == ALL_PROJECTS ) {
$t_file_path = config_get( 'absolute_path_default_upload_folder' );
}
else {
$t_file_path = project_get_field( $t_project_id, 'file_path' );
if( $t_file_path == '' ) {
$t_file_path = config_get( 'absolute_path_default_upload_folder' );
}
}
$c_file_path = db_prepare_string( $t_file_path );
$c_new_file_name = db_prepare_string( $p_file_name );
$t_file_hash = ( 'bugnote' == $p_table ) ? $t_bug_id : config_get( 'document_files_prefix' ) . '-' . $t_project_id;
$t_disk_file_name = $t_file_path . file_generate_unique_name( $t_file_hash . '-' . $p_file_name, $t_file_path );
$c_disk_file_name = db_prepare_string( $t_disk_file_name );
$t_file_size = filesize( $p_tmp_file );
if ( 0 == $t_file_size ) {
trigger_error( ERROR_FILE_NO_UPLOAD_FAILURE, ERROR );
}
$t_max_file_size = (int)min( ini_get_number( 'upload_max_filesize' ), ini_get_number( 'post_max_size' ), config_get( 'max_file_size' ) );
if ( $t_file_size > $t_max_file_size ) {
trigger_error( ERROR_FILE_TOO_BIG, ERROR );
}
$c_file_size = db_prepare_int( $t_file_size );
$t_method = config_get( 'file_upload_method' );
switch ( $t_method ) {
case FTP:
case DISK:
file_ensure_valid_upload_path( $t_file_path );
if ( !file_exists( $t_disk_file_name ) ) {
if ( FTP == $t_method ) {
$conn_id = file_ftp_connect();
file_ftp_put ( $conn_id, $t_disk_file_name, $p_tmp_file );
file_ftp_disconnect ( $conn_id );
}
if ( !move_uploaded_file( $p_tmp_file, $t_disk_file_name ) ) {
trigger_error( FILE_MOVE_FAILED, ERROR );
}
chmod( $t_disk_file_name, 0400 );
$c_content = '';
} else {
trigger_error( ERROR_FILE_DUPLICATE, ERROR );
}
break;
case DATABASE:
$c_content = db_prepare_string( fread ( fopen( $p_tmp_file, 'rb' ), $t_file_size ) );
break;
default:
trigger_error( ERROR_GENERIC, ERROR );
}
$t_file_table = config_get( 'mantis_' . $p_table . '_file_table' );
$c_id = ( 'bugnote' == $p_table ) ? $c_bug_id : $c_project_id;
$query = "INSERT INTO $t_file_table
(bug_id, " . $p_table . "_id, title, description, diskfile, filename, folder, filesize, file_type, date_added, content)
VALUES
($c_id, $c_bugnote_id, '$c_title', '$c_desc', '$c_disk_file_name', '$c_new_file_name', '$c_file_path', $c_file_size, '$c_file_type', " . db_now() .", '$c_content')";
db_query( $query );
if ( 'bugnote' == $p_table ) {
# updated the last_updated date
$result = bug_update_date( $p_bug_id );
# log new bug
history_log_event_special( $p_bug_id, FILE_ADDED, $p_file_name );
}
}
버그노트에 연결된 첨부파일 목록을 구하는 기능의 함수이다.
file_api.php 파일의 file_delete 함수의 내용 중 다음 부분을 수정한다.
if( 'bug' == $p_table
|| 'bugnote' == $p_table ) {
# log file deletion
$t_bug_id = file_get_field( $p_file_id, 'bug_id', 'bug' );
history_log_event_special( $t_bug_id, FILE_DELETED, file_get_display_name ( $t_filename ) );
}
bugnote_add.php 파일에서 버그노트에 연결된 첨부파일을 기록하는 함수 호출 명령문을 추가한다.
$f_bug_id = gpc_get_int( 'bug_id' );
$f_private = gpc_get_bool( 'private' );
$f_time_tracking = gpc_get_string( 'time_tracking', '0:00' );
$f_bugnote_text = trim( gpc_get_string( 'bugnote_text', '' ) );
$f_file = gpc_get_file( 'file' ); ... 중간 생략 ...
# @@@ VB: Do we want to differentiate email notifications for normal notes from time tracking entries?
$t_bugnote_added = bugnote_add( $f_bug_id, $f_bugnote_text, $f_time_tracking, $f_private, $t_note_type );
if ( !$t_bugnote_added ) {
error_parameters( lang_get( 'bugnote' ) );
trigger_error( ERROR_EMPTY_FIELD, ERROR );
}
if ( !is_blank( $f_file['name'] ) ) { $f_file_error = ( isset( $f_file['error'] ) ) ? $f_file['error'] : 0; bugnote_file_add( $f_bug_id, $t_bugnote_added, $f_file['tmp_name'], $f_file['name'], $f_file['type'], 'bugnote', $f_file_error ); } print_successful_redirect_to_bug( $f_bug_id );
bugnote_add_inc.php 파일에 버그노트 입력UI에 첨부파일을 등록할 수 있는 UI를 추가한다.
<form name="bugnoteadd" method="post"
enctype="multipart/form-data" action="bugnote_add.php">
... 중간 생략 ...
<tr class="row-2">
<td class="category" width="25%">
<?php echo lang_get( 'bugnote' ) ?>
</td>
<td width="75%">
<textarea name="bugnote_text" cols="80" rows="10"></textarea>
</td>
</tr>
<tr class="row-2"> <td class="category" width="25%"> <?php echo lang_get( 'select_file' ) ?><br /> <?php echo '<span class="small">(' . lang_get( 'max_file_size' ) . ': ' . number_format( $t_max_file_size/1000 ) . 'k)</span>'?> </td> <td width="75%"> <input type="hidden" name="bug_id" value="<?php echo $f_bug_id ?>" /> <input type="hidden" name="max_file_size" value="<?php echo $t_max_file_size ?>" /> <input name="file" type="file" size="40" /> </td></tr><?php if ( access_has_bug_level( config_get( 'private_bugnote_threshold' ), $f_bug_id ) ) { ?>
bugnote_view_inc.php 파일에 앞에서 추가한 함수를 호출하는 부분을 추가한다.
... 이전 생략 ...
<!-- Attachments --><?php $t_show_attachments = ( $t_bug->reporter_id == auth_get_current_user_id() ) || access_has_bug_level( config_get( 'view_attachments_threshold' ), $f_bug_id ); if ( $t_show_attachments ) {?><tr <?php echo helper_alternate_class() ?>> <td class="category"> <a name="attachments" id="attachments" /> <?php echo lang_get( 'attached_files' ) ?> </td> <td colspan="5"> <?php bugnotefile_list_attachments ( $f_bug_id, $v3_id ); ?> </td></tr><?php }?><tr class="spacer">
<td colspan="2"></td>
</tr>
<?php
} # end for loop
if ( $t_total_time > 0 && access_has_bug_level( config_get( 'time_tracking_view_threshold' ), $f_bug_id ) ) {
echo '<tr><td colspan="2">', sprintf ( lang_get( 'total_time_for_issue' ), db_minutes_to_hhmm( $t_total_time ) ), '</td></tr>';
}
} # end else
?>
</table>
file_download.php 파일에 다음 코드를 추가한다.
switch ( $f_type ) {
case 'bug':
$t_bug_file_table = config_get( 'mantis_bug_file_table' );
$query = "SELECT *
FROM $t_bug_file_table
WHERE id='$c_file_id'";
break;
case 'doc':
$t_project_file_table = config_get( 'mantis_project_file_table' );
$query = "SELECT *
FROM $t_project_file_table
WHERE id='$c_file_id'";
break;
case 'bugnote': $t_bug_file_table = config_get( 'mantis_bugnote_file_table' ); $query = "SELECT * FROM $t_bug_file_table WHERE id='$c_file_id'"; break; default:
access_denied();
}
config_defaults_inc.php 파일 또는 config_inc.php 파일에 다음 부분을 추가한다.
#######################################
# Mantis Database Table Variables
#######################################
... 중간 생략 ...
$g_mantis_email_table = '%db_table_prefix%_email%db_table_suffix%';
$g_mantis_bugnote_file_table = '%db_table_prefix%_bugnote_file%db_table_suffix%';마지막으로 다음과 같은 내용의 bugnote_file_delete.php 파일을 작성한다.
<?php # Mantis - a php based bugtracking system # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org # Copyright (C) 2002 - 2004 Mantis Team - mantisbt-dev@lists.sourceforge.net # This program is distributed under the terms and conditions of the GPL # See the README and LICENSE files for details # -------------------------------------------------------- # $Id: bugnote_file_delete.php,v 1.1 2007/11/07 06:16:41 woohaha Exp $ # -------------------------------------------------------- # Delete a file from a bug and then view the bug require_once( 'core.php' ); $t_core_path = config_get( 'core_path' ); require_once( $t_core_path.'file_api.php' ); $f_file_id = gpc_get_int( 'file_id' ); $t_bug_id = file_get_field( $f_file_id, 'bug_id', 'bugnote' ); access_ensure_bug_level( config_get( 'update_bug_threshold' ), $t_bug_id ); helper_ensure_confirmed( lang_get( 'delete_attachment_sure_msg' ), lang_get( 'delete_attachment_button' ) ); file_delete( $f_file_id, 'bugnote' ); print_header_redirect_view( $t_bug_id );?>지금까지 설명한 파일의 변경 내용을 압축하여 제공한다.
mantis-1.1.2-cust-woohaha.zip