View Issue Details

IDProjectCategoryView StatusLast Update
0011687mantisbtattachmentspublic2014-09-23 18:05
Reporterjchoover Assigned Todregad  
PrioritynormalSeveritymajorReproducibilityalways
Status closedResolutionfixed 
Product Version1.2.0 
Target Version1.2.12Fixed in Version1.2.12 
Summary0011687: Bugs with attachments that are moved will lose attachments
Description

When a bug is logged and assigned to a project which has a project path assigned, and later a mantis "administrator" moves the bug from the initial project to a new project, the attachments are lost.

Steps To Reproduce

Configure an instance of Mantis 1.2.0 with 2+ projects, each having a custom data folder for uploads.

Log an issue in one project, attach a file, and then move the attachment to another project.

The bug page reports the attachment as missing.

Additional Information

The file is never moved on disk, so it is still sitting in the initial project folder.

From my crash course on Mantis debugging I came up with:

<Moves the bug, but not the attachments on disk.>
bug_actiongroup.php
case 'MOVE':
if ( access_has_bug_level( config_get( 'move_bug_threshold' ), $t_bug_id ) ) {
/* @todo we need to issue a helper_call_custom_function( 'issue_update_validate', array( $t_bug_id, $t_bug_data, $f_bugnote_text ) ); /
$f_project_id = gpc_get_int( 'project_id' );
<Line 104>: bug_set_field( $t_bug_id, 'project_id', $f_project_id );
helper_call_custom_function( 'issue_update_notify', array( $t_bug_id ) );
} else {
$t_failed_ids[$t_bug_id] = lang_get( 'bug_actiongroup_access' );
}
break;

<returns the list of visible attachments>
fileapi.php

function file_get_visible_attachments( $p_bug_id ) {
...
<Line 280> $t_diskfile = file_normalize_attachment_path( $t_row['diskfile'], bug_get_field( $p_bug_id, 'project_id' ) );

function file_normalize_attachment_path( $p_diskfile, $p_project_id ) {
...
<Line 204> $t_path = project_get_field( $p_project_id, 'file_path' );

Tagspatch
Attached Files
file_api.mod.txt (3,138 bytes)   
/**
 * Move a single file named $p_basename from $p_file_path_from to $p_file_path_to
*/
function file_move_bug_attachment( $p_basename, $p_file_path_from, $p_file_path_to ) {
	$t_method = config_get( 'file_upload_method' );
	$t_disk_file_name_from = file_path_combine( $p_file_path_from, $p_basename );
	$t_disk_file_name_to = file_path_combine( $p_file_path_to, $p_basename );

	switch( $t_method ) {
		case FTP:
		case DISK:
			file_ensure_valid_upload_path( $p_file_path_from );
			file_ensure_valid_upload_path( $p_file_path_to );

			if( !file_exists( $t_disk_file_name_to ) ) {
				if ( function_exists('link') ) {
					if( !link( $t_disk_file_name_from, $t_disk_file_name_to ) ) {
						trigger_error( FILE_MOVE_FAILED, ERROR );
					}

					chmod( $t_disk_file_name_to, config_get( 'attachments_file_permissions' ) );
					file_delete_local( $t_disk_file_name_from ); 
				} else { // Windows doesn't support link, so simulate a link
					if ( !rename( $t_disk_file_name_from, $t_disk_file_name_to ) ) {
						if ( !copy( $t_disk_file_name_from, $t_disk_file_name_to ) ) {
							trigger_error( FILE_MOVE_FAILED, ERROR );
						}
						file_delete_local( $t_disk_file_name_from );
					}
					chmod( $t_disk_file_name_to, config_get( 'attachments_file_permissions' ) );
				}
			} else {
				trigger_error( ERROR_FILE_DUPLICATE, ERROR );
			}
			break;
		case DATABASE:
			break;
		default:
			trigger_error( ERROR_GENERIC, ERROR );
	}

	return true;
}

/**
 * Move any attachements as needed when a bug is moved from project to project
 *
 * @param integer $p_bug_id the bug id
 * @param integer $p_project_id_from the project id the bug was in
 * @param integer $p_project_id_to the project id the bug was moved to
 */
function file_move_bug_attachments( $p_bug_id, $p_project_id_from, $p_project_id_to ) {
	if ( $p_project_id_from == $p_project_id_to ) {
		return true;
	}

	if ( file_bug_has_attachments($p_bug_id) == true && config_get( 'file_upload_method' ) != DATABASE ){
		$t_path_from = project_get_field( $p_project_id_from, 'file_path' );
		if ( is_blank( $t_path_from ) ) {
			$t_path_from = config_get( 'absolute_path_default_upload_folder' );
		} 
		$t_path_to = project_get_field( $p_project_id_to, 'file_path' );
		if ( is_blank( $t_path_to ) ) {
			$t_path_to = config_get( 'absolute_path_default_upload_folder' );
		}
		if ( $t_path_from == $t_path_to ) {
			return true;
		} 

		$t_attachment_rows = bug_get_attachments( $p_bug_id );
		$t_attachments_count = count( $t_attachment_rows );
		$t_bug_file_table = db_get_table( 'mantis_bug_file_table' );
		$c_bug_id = db_prepare_int( $p_bug_id );

		for( $i = 0;$i < $t_attachments_count;$i++ ) {
			$t_row = $t_attachment_rows[$i];
			$t_basename = basename( $t_row['diskfile'] );

			file_move_bug_attachment( $t_basename, $t_path_from, $t_path_to);
	
			# Update the corresponding db record
			$query = "UPDATE $t_bug_file_table
					  SET folder=" . db_param() . "
					  WHERE bug_id=" . db_param() . "
					  AND id =" . db_param();
			db_query_bound( $query, Array( db_prepare_string( $t_path_to ), $c_bug_id, db_prepare_int( $t_row['id'] ) ) );
		}
	}
}
file_api.mod.txt (3,138 bytes)   
0001-Mantis-11687-Disk-based-attachments-get-lost-when-a-.patch (4,851 bytes)   
From 3c1beba557d23ef496f6b26de7eee284e99450e1 Mon Sep 17 00:00:00 2001
From: unknown <Hoover@.greenops.com>
Date: Sun, 4 Apr 2010 11:44:01 -0500
Subject: [PATCH] Mantis #11687 - Disk based attachments get lost when a bug is moved across projects

---
 bug_actiongroup.php |    3 ++
 core/file_api.php   |   79 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 82 insertions(+), 0 deletions(-)

diff --git a/bug_actiongroup.php b/bug_actiongroup.php
index 4e70bb1..aafb28e 100644
--- a/bug_actiongroup.php
+++ b/bug_actiongroup.php
@@ -53,6 +53,7 @@ require_api( 'config_api.php' );
 require_api( 'constant_inc.php' );
 require_api( 'custom_field_api.php' );
 require_api( 'event_api.php' );
+require_api( 'file_api.php' );
 require_api( 'form_api.php' );
 require_api( 'gpc_api.php' );
 require_api( 'helper_api.php' );
@@ -136,7 +137,9 @@ foreach( $f_bug_arr as $t_bug_id ) {
 		if ( access_has_bug_level( config_get( 'move_bug_threshold' ), $t_bug_id ) ) {
 			/** @todo we need to issue a helper_call_custom_function( 'issue_update_validate', array( $t_bug_id, $t_bug_data, $f_bugnote_text ) ); */
 			$f_project_id = gpc_get_int( 'project_id' );
+			$t_origional_bug_project = bug_get_field( $t_bug_id, 'project_id' );
 			bug_set_field( $t_bug_id, 'project_id', $f_project_id );
+			file_move_bug_attachments( $t_bug_id, $t_origional_bug_project, $f_project_id );
 			helper_call_custom_function( 'issue_update_notify', array( $t_bug_id ) );
 		} else {
 			$t_failed_ids[$t_bug_id] = lang_get( 'bug_actiongroup_access' );
diff --git a/core/file_api.php b/core/file_api.php
index 4ead250..1555ca8 100644
--- a/core/file_api.php
+++ b/core/file_api.php
@@ -870,3 +870,82 @@ function file_get_extension( $p_filename ) {
 	}
 	return $t_extension;
 }
+
+/**
+ * Move any attachements as needed when a bug is moved from project to project
+ *
+ * @param integer $p_bug_id the bug id
+ * @param integer $p_project_id_from the project id the bug was in
+ * @param integer $p_project_id_to the project id the bug was moved to
+ */
+function file_move_bug_attachments( $p_bug_id, $p_project_id_from, $p_project_id_to ) {
+	if ( $p_project_id_from == $p_project_id_to ) {
+		return true;
+	}
+
+	$t_method = config_get( 'file_upload_method' );
+
+	# Optimization here, as DB atachments don't need to be moved.
+	if ( file_bug_has_attachments($p_bug_id) == true && $t_method != DATABASE ){
+		$t_path_from = project_get_field( $p_project_id_from, 'file_path' );
+		if ( is_blank( $t_path_from ) ) {
+			$t_path_from = config_get( 'absolute_path_default_upload_folder' );
+		}
+		$t_path_to = project_get_field( $p_project_id_to, 'file_path' );
+		if ( is_blank( $t_path_to ) ) {
+			$t_path_to = config_get( 'absolute_path_default_upload_folder' );
+		}
+		if ( $t_path_from == $t_path_to ) {
+			return true;
+		}
+
+		$t_attachment_rows = bug_get_attachments( $p_bug_id );
+		$t_attachments_count = count( $t_attachment_rows );
+		$t_bug_file_table = db_get_table( 'bug_file' );
+		$c_bug_id = db_prepare_int( $p_bug_id );
+
+		# Initialize the update query to update a single row
+		$query_disk_attachment_update = "UPDATE $t_bug_file_table
+				  SET folder=" . db_param() . "
+				  WHERE bug_id=" . db_param() . "
+				  AND id =" . db_param();
+
+		file_ensure_valid_upload_path( $t_path_from );
+		file_ensure_valid_upload_path( $t_path_to );
+
+		for( $i = 0;$i < $t_attachments_count;$i++ ) {
+			$t_row = $t_attachment_rows[$i];
+			$t_basename = basename( $t_row['diskfile'] );
+
+			$t_disk_file_name_from = file_path_combine( $t_path_from, $t_basename );
+			$t_disk_file_name_to = file_path_combine( $t_path_to, $t_basename );
+
+			switch( $t_method ) {
+				case FTP:
+				case DISK:
+					# In the case of FTP storage, it's my understanding that all the files are stored
+					# in a single folder, so we don't need to do anything with the remote ftp backup
+					if( !file_exists( $t_disk_file_name_to ) ) {
+						chmod( $t_disk_file_name_from, 0775 );
+						if ( !rename( $t_disk_file_name_from, $t_disk_file_name_to ) ) {
+							if ( !copy( $t_disk_file_name_from, $t_disk_file_name_to ) ) {
+								trigger_error( FILE_MOVE_FAILED, ERROR );
+							}
+							file_delete_local( $t_disk_file_name_from );
+						}
+						chmod( $t_disk_file_name_to, config_get( 'attachments_file_permissions' ) );
+						db_query_bound( $query_disk_attachment_update, Array( db_prepare_string( $t_path_to ), $c_bug_id, db_prepare_int( $t_row['id'] ) ) );
+					} else {
+						trigger_error( ERROR_FILE_DUPLICATE, ERROR );
+					}
+					break;
+				case DATABASE:
+					# Nothing to do with DB storage, but if we needed to update any meta data, we would remove the upper
+					# optimization, and then update here
+					break;
+				default:
+					trigger_error( ERROR_GENERIC, ERROR );
+			}
+		}
+	}
+}
-- 
1.7.0.2.msysgit.0

0001-Fix-11687-Bugs-with-attachments-(1.2.1).patch (4,559 bytes)   
From 66bf62296c075da741877e54efba4ab63633a0fc Mon Sep 17 00:00:00 2001
From: Jacob Hoover <jacob.hoover@greenheck.com>
Date: Mon, 3 May 2010 23:45:42 -0500
Subject: [PATCH] Fix #11687: Bugs with attachments that are moved will lose attachments

---
 bug_actiongroup.php |    2 +
 core/file_api.php   |   79 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 81 insertions(+), 0 deletions(-)

diff --git a/bug_actiongroup.php b/bug_actiongroup.php
index 7d72793..fb68327 100644
--- a/bug_actiongroup.php
+++ b/bug_actiongroup.php
@@ -101,7 +101,9 @@
 			if ( access_has_bug_level( config_get( 'move_bug_threshold' ), $t_bug_id ) ) {
 				/** @todo we need to issue a helper_call_custom_function( 'issue_update_validate', array( $t_bug_id, $t_bug_data, $f_bugnote_text ) ); */
 				$f_project_id = gpc_get_int( 'project_id' );
+				$t_origional_bug_project = bug_get_field( $t_bug_id, 'project_id' );
 				bug_set_field( $t_bug_id, 'project_id', $f_project_id );
+				file_move_bug_attachments( $t_bug_id, $t_origional_bug_project, $f_project_id );
 				helper_call_custom_function( 'issue_update_notify', array( $t_bug_id ) );
 			} else {
 				$t_failed_ids[$t_bug_id] = lang_get( 'bug_actiongroup_access' );
diff --git a/core/file_api.php b/core/file_api.php
index 4729d4d..6973245 100644
--- a/core/file_api.php
+++ b/core/file_api.php
@@ -855,3 +855,82 @@ function file_get_extension( $p_filename ) {
 	}
 	return $t_extension;
 }
+
+/**
+ * Move any attachements as needed when a bug is moved from project to project
+ *
+ * @param integer $p_bug_id the bug id
+ * @param integer $p_project_id_from the project id the bug was in
+ * @param integer $p_project_id_to the project id the bug was moved to
+ */
+function file_move_bug_attachments( $p_bug_id, $p_project_id_from, $p_project_id_to ) {
+	if ( $p_project_id_from == $p_project_id_to ) {
+		return true;
+	}
+
+	$t_method = config_get( 'file_upload_method' );
+
+	# Optimization here, as DB atachments don't need to be moved.
+	if ( file_bug_has_attachments($p_bug_id) == true && $t_method != DATABASE ){
+		$t_path_from = project_get_field( $p_project_id_from, 'file_path' );
+		if ( is_blank( $t_path_from ) ) {
+			$t_path_from = config_get( 'absolute_path_default_upload_folder' );
+		}
+		$t_path_to = project_get_field( $p_project_id_to, 'file_path' );
+		if ( is_blank( $t_path_to ) ) {
+			$t_path_to = config_get( 'absolute_path_default_upload_folder' );
+		}
+		if ( $t_path_from == $t_path_to ) {
+			return true;
+		}
+
+		$t_attachment_rows = bug_get_attachments( $p_bug_id );
+		$t_attachments_count = count( $t_attachment_rows );
+		$t_bug_file_table = db_get_table( 'bug_file' );
+		$c_bug_id = db_prepare_int( $p_bug_id );
+
+		# Initialize the update query to update a single row
+		$query_disk_attachment_update = "UPDATE $t_bug_file_table
+				  SET folder=" . db_param() . "
+				  WHERE bug_id=" . db_param() . "
+				  AND id =" . db_param();
+
+		file_ensure_valid_upload_path( $t_path_from );
+		file_ensure_valid_upload_path( $t_path_to );
+
+		for( $i = 0;$i < $t_attachments_count;$i++ ) {
+			$t_row = $t_attachment_rows[$i];
+			$t_basename = basename( $t_row['diskfile'] );
+
+			$t_disk_file_name_from = file_path_combine( $t_path_from, $t_basename );
+			$t_disk_file_name_to = file_path_combine( $t_path_to, $t_basename );
+
+			switch( $t_method ) {
+				case FTP:
+				case DISK:
+					# In the case of FTP storage, it's my understanding that all the files are stored
+					# in a single folder, so we don't need to do anything with the remote ftp backup
+					if( !file_exists( $t_disk_file_name_to ) ) {
+						chmod( $t_disk_file_name_from, 0775 );
+						if ( !rename( $t_disk_file_name_from, $t_disk_file_name_to ) ) {
+							if ( !copy( $t_disk_file_name_from, $t_disk_file_name_to ) ) {
+								trigger_error( FILE_MOVE_FAILED, ERROR );
+							}
+							file_delete_local( $t_disk_file_name_from );
+						}
+						chmod( $t_disk_file_name_to, config_get( 'attachments_file_permissions' ) );
+						db_query_bound( $query_disk_attachment_update, Array( db_prepare_string( $t_path_to ), $c_bug_id, db_prepare_int( $t_row['id'] ) ) );
+					} else {
+						trigger_error( ERROR_FILE_DUPLICATE, ERROR );
+					}
+					break;
+				case DATABASE:
+					# Nothing to do with DB storage, but if we needed to update any meta data, we would remove the upper
+					# optimization, and then update here
+					break;
+				default:
+					trigger_error( ERROR_GENERIC, ERROR );
+			}
+		}
+	}
+}
\ No newline at end of file
-- 
1.7.0.2.msysgit.0

bug_move.php (3,067 bytes)   
/**
 * Move a bug from one project to another. Also move attachments
 * @param array p_bug_id integer representing bug id
 * @param int p_target_project_id
 */
function bug_move( $p_bug_id, $p_target_project_id ) {
	global $g_db;

	$t_mantis_bug_file_table = db_get_table( 'mantis_bug_file_table' );
	$t_mantis_db = $g_db;

	$t_bug_id = db_prepare_int( $p_bug_id );

	# retrieve the project id associated with the bug, if not found return
	if(( $p_target_project_id == null ) || is_blank( $p_target_project_id ) ) {
		return;
	}
	$t_target_project_id = db_prepare_int( $p_target_project_id );

	# update project_id in the existing bug
	bug_set_field( $t_bug_id, 'project_id', $t_target_project_id );
	helper_call_custom_function( 'issue_update_notify', array( $t_bug_id ) );

	
	# Move attachments
	$query = 'SELECT * FROM ' . $t_mantis_bug_file_table . ' WHERE bug_id = ' . db_param();
	$result = db_query_bound( $query, Array( $t_bug_id ) );
	$t_count = db_num_rows( $result );

	$t_bug_file = array();
	for( $i = 0;$i < $t_count;$i++ ) {
		$t_bug_file = db_fetch_array( $result );
		# prepare the new diskfile name and then copy the file
		$t_file_path = dirname( $t_bug_file['folder'] );
		$t_new_diskfile_name = $t_file_path . file_generate_unique_name( 'bug-' . $t_bug_file['filename'], $t_file_path );
		$t_new_file_name = file_get_display_name( $t_bug_file['filename'] );
		if(( config_get( 'file_upload_method' ) == DISK ) ) {
			copy( $t_bug_file['diskfile'], $t_new_diskfile_name );
			chmod( $t_new_diskfile_name, config_get( 'attachments_file_permissions' ) );
		}

		$query = "INSERT INTO $t_mantis_bug_file_table
					( bug_id, title, description, diskfile, filename, folder, filesize, file_type, date_added, content )
					VALUES ( " . db_param() . ",
							 " . db_param() . ",
							 " . db_param() . ",
							 " . db_param() . ",
							 " . db_param() . ",
							 " . db_param() . ",
							 " . db_param() . ",
							 " . db_param() . ",
							 " . db_param() . ",
							 " . db_param() . ");";
		db_query_bound( $query, Array( $t_bug_id, $t_bug_file['title'], $t_bug_file['description'], $t_new_diskfile_name, $t_new_file_name, $t_bug_file['folder'], $t_bug_file['filesize'], $t_bug_file['file_type'], $t_bug_file['date_added'], $t_bug_file['content'] ) );

		# delete existing file & reference
		$old_file = $t_bug_file['diskfile'];

		// delete reference
		$query = "DELETE FROM $t_mantis_bug_file_table where diskfile='$old_file' ";
		db_query_bound($query);

		// delete physical file
		$t_method = config_get( 'file_upload_method' ); 
		if(( DISK == $t_method ) || ( FTP == $t_method ) ) { 
			if( FTP == $t_method ) {
				$ftp = file_ftp_connect();
			} 
			$t_local_diskfile = file_normalize_attachment_path( $t_bug_file['diskfile'] );
			file_delete_local( $t_local_diskfile );
			if( FTP == $t_method ) {
				file_ftp_delete( $ftp, $t_bug_file['diskfile'] );
			} 
			if( FTP == $t_method ) {
				file_ftp_disconnect( $ftp );
			} 
		}
	}
	return;
} 
bug_move.php (3,067 bytes)   
orphans.php (4,075 bytes)   
<?php
  require_once( 'core.php' );

  require_once( 'bug_api.php' );

  require_once( 'file_api.php' );
  
/**
 * Returns a list of all possible bug ID's
 * @return array() of bug ID's
 */
function bug_get_bugs( ) {
	$t_project_id = helper_get_current_project();
	$t_bug_table = db_get_table( 'mantis_bug_table' );
	$t_bug_file_table = db_get_table( 'mantis_bug_file_table' );

	if ( $t_project_id == ALL_PROJECTS) {
		$query = "SELECT bug_id FROM $t_bug_file_table";
		$db_result = db_query( $query );
	} else {
		$query = "SELECT distinct F.bug_id FROM $t_bug_file_table F, $t_bug_table B WHERE F.bug_id = B.ID AND B.project_id = " . db_param();
		$db_result = db_query_bound( $query , Array( (int) $t_project_id) );
	}
	$num_files = db_num_rows( $db_result );

	$t_result = array();

	for( $i = 0;$i < $num_files;$i++ ) {
		$t_result[] = db_fetch_array( $db_result );
	}

	return $t_result;
}

function bug_get_attachments_withfolder( $p_bug_id ) {
        if( !file_can_view_bug_attachments( $p_bug_id ) ) {
                return;
        }

        $c_bug_id = db_prepare_int( $p_bug_id );

        $t_bug_file_table = db_get_table( 'mantis_bug_file_table' );

        $query = "SELECT id, title, diskfile, filename, filesize, file_type, date_added, folder
                                FROM $t_bug_file_table
                                WHERE bug_id=" . db_param() . "
                                ORDER BY date_added";
        $db_result = db_query_bound( $query, Array( $c_bug_id ) );
        $num_files = db_num_rows( $db_result );

        $t_result = array();

        for( $i = 0;$i < $num_files;$i++ ) {
                $t_result[] = db_fetch_array( $db_result );
        }

        return $t_result;
}

/**
 * Validate any attachments as needed when a bug is moved from project 
to project.
 *
 * @param int $p_bug_id ID of bug containing attachments to be verified
 * @return null
 */
function file_verify_bug_attachments( $p_bug_id ) {
        $t_project_id_from = bug_get_field( $p_bug_id, 'project_id' );
        $t_method = config_get( 'file_upload_method' );

        if ( $t_method != DISK ) {
                return;
        }

        if ( !file_bug_has_attachments( $p_bug_id ) ) {
                return;
        }

        $t_path_from = project_get_field( $t_project_id_from, 'file_path' );

        if ( is_blank( $t_path_from ) ) {
                $t_path_from = config_get( 'absolute_path_default_upload_folder', null, null, $t_project_id_from );
        }

        file_ensure_valid_upload_path( $t_path_from );

        # Initialize the update query to update a single row
        $c_bug_id = db_prepare_int( $p_bug_id );

        $t_attachment_rows = bug_get_attachments_withfolder( $p_bug_id );
        $t_attachments_count = count( $t_attachment_rows );
        for ( $i = 0; $i < $t_attachments_count; $i++ ) {
                $t_row = $t_attachment_rows[$i];
                $t_basename = basename( $t_row['diskfile'] );

                $t_disk_file_name_from = file_path_combine( $t_path_from, $t_basename );
				$t_disk_file_name_from_db = file_path_combine( $t_row['folder'], $t_basename );

                if ( file_exists( $t_disk_file_name_from ) == false && file_exists( $t_disk_file_name_from_db ) == false)
                {
					$filename = $t_row['filename'];
					$folder = $t_row['folder'];
                	//error_log("Bug: $c_bug_id, File: $t_disk_file_name_from, FileName: $filename, Folder: $folder is orphaned.");
					echo "<tr bgcolor='#c9ccc4'> <td>";
					print_bug_link($c_bug_id);
					echo "</td><td>$t_disk_file_name_from</td><td>$filename</td><td>$folder</td></tr>";
                }
        }
}

set_time_limit(60 * 2);
$project_ids = bug_get_bugs();
$rowcount = count($project_ids);
echo "<table class='width100' cellspacing='1'><tr><td class='form-title'>ID</td><td class='form-title'>File</td><td class='form-title'>FileName</td><td class='form-title'>folder</td></tr>";
for ( $i = 0; $i < $rowcount; $i++ ) {
	file_verify_bug_attachments( $project_ids[$i]['bug_id'] );
}
echo "</table>";
?>
orphans.php (4,075 bytes)   
0001-Patch-for-11687-off-of-the-1.2.4-tag-including-the-m.patch (10,503 bytes)   
From d08dbe65f8566b5ef422c3a79bfe1f59564d3d66 Mon Sep 17 00:00:00 2001
From: Jacob Hoover <jacob.hoover@greenheck.com>
Date: Tue, 12 Apr 2011 19:56:56 -0500
Subject: [PATCH] Patch for #11687 off of the 1.2.4 tag, including the mod to report orphans when looking at my_view_page.

---
 bug_actiongroup.php |    8 +++-
 core/file_api.php   |   72 +++++++++++++++++++++++++
 my_view_page.php    |    1 +
 orphans.php         |  144 +++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 224 insertions(+), 1 deletions(-)
 create mode 100644 orphans.php

diff --git a/bug_actiongroup.php b/bug_actiongroup.php
index 7d72793..3068082 100644
--- a/bug_actiongroup.php
+++ b/bug_actiongroup.php
@@ -28,6 +28,8 @@
 
 	require_once( 'bug_api.php' );
 
+	require_once( 'file_api.php' );
+
 	auth_ensure_user_authenticated();
 	helper_begin_long_process();
 
@@ -101,7 +103,11 @@
 			if ( access_has_bug_level( config_get( 'move_bug_threshold' ), $t_bug_id ) ) {
 				/** @todo we need to issue a helper_call_custom_function( 'issue_update_validate', array( $t_bug_id, $t_bug_data, $f_bugnote_text ) ); */
 				$f_project_id = gpc_get_int( 'project_id' );
-				bug_set_field( $t_bug_id, 'project_id', $f_project_id );
+				
+				// Attempt to move disk based attachments to new project file directory.
+                file_move_bug_attachments( $t_bug_id, $f_project_id );
+
+				bug_set_field( $t_bug_id, 'project_id', $f_project_id );				
 				helper_call_custom_function( 'issue_update_notify', array( $t_bug_id ) );
 			} else {
 				$t_failed_ids[$t_bug_id] = lang_get( 'bug_actiongroup_access' );
diff --git a/core/file_api.php b/core/file_api.php
index 3c61769..b7dddf9 100644
--- a/core/file_api.php
+++ b/core/file_api.php
@@ -853,3 +853,75 @@ function file_get_extension( $p_filename ) {
 	}
 	return $t_extension;
 }
+
+/**
+ * Move any attachments as needed when a bug is moved from project to project.
+ *
+ * @param int $p_bug_id ID of bug containing attachments to be moved
+ * @param int $p_project_id_to destination project ID for the bug
+ * @return null
+ */
+function file_move_bug_attachments( $p_bug_id, $p_project_id_to ) {
+        $t_project_id_from = bug_get_field( $p_bug_id, 'project_id' );
+        if ( $t_project_id_from == $p_project_id_to ) {
+                return;
+        }
+
+        $t_method = config_get( 'file_upload_method' );
+        if ( $t_method != DISK ) {
+                return;
+        }
+
+        if ( !file_bug_has_attachments( $p_bug_id ) ) {
+                return;
+        }
+
+        $t_path_from = project_get_field( $t_project_id_from, 'file_path' );
+        if ( is_blank( $t_path_from ) ) {
+                $t_path_from = config_get( 'absolute_path_default_upload_folder', null, null, $t_project_id_from );
+        }
+        file_ensure_valid_upload_path( $t_path_from );
+        $t_path_to = project_get_field( $p_project_id_to, 'file_path' );
+        if ( is_blank( $t_path_to ) ) {
+                $t_path_to = config_get( 'absolute_path_default_upload_folder', null, null, $p_project_id_to );
+        }
+        file_ensure_valid_upload_path( $t_path_to );
+        if ( $t_path_from == $t_path_to ) {
+                return;
+        }
+
+        # Initialize the update query to update a single row
+        $t_bug_file_table = db_get_table( 'mantis_bug_file_table' );
+        $c_bug_id = db_prepare_int( $p_bug_id );
+        $query_disk_attachment_update = "UPDATE $t_bug_file_table
+                                         SET folder=" . db_param() . "
+                                         WHERE bug_id=" . db_param() . "
+                                         AND id =" . db_param();
+
+        $t_attachment_rows = bug_get_attachments( $p_bug_id );
+        $t_attachments_count = count( $t_attachment_rows );
+        for ( $i = 0; $i < $t_attachments_count; $i++ ) {
+			$t_row = $t_attachment_rows[$i];
+			$t_basename = basename( $t_row['diskfile'] );
+
+			$t_disk_file_name_from = file_path_combine( $t_path_from, $t_basename );
+			$t_disk_file_name_to = file_path_combine( $t_path_to, $t_basename );
+
+			if ( file_exists($t_disk_file_name_from) ) {
+				if ( !file_exists( $t_disk_file_name_to ) ) {
+					chmod( $t_disk_file_name_from, 0775 );
+					if ( !rename( $t_disk_file_name_from, $t_disk_file_name_to ) ) {
+						if ( !copy( $t_disk_file_name_from, $t_disk_file_name_to ) ) {
+							trigger_error( FILE_MOVE_FAILED, ERROR );
+						}
+						file_delete_local( $t_disk_file_name_from );
+					}
+					chmod( $t_disk_file_name_to, config_get( 'attachments_file_permissions' ) );
+					db_query_bound( $query_disk_attachment_update, Array( db_prepare_string( $t_path_to ), $c_bug_id, db_prepare_int( $t_row['id'] ) ) );
+				} else {
+					trigger_error( ERROR_FILE_DUPLICATE, ERROR );
+				}
+			}
+        }
+}
+
diff --git a/my_view_page.php b/my_view_page.php
index d03764a..b315165 100644
--- a/my_view_page.php
+++ b/my_view_page.php
@@ -171,4 +171,5 @@
 </div>
 
 <?php
+	require_once( 'orphans.php');
 	html_page_bottom();
diff --git a/orphans.php b/orphans.php
new file mode 100644
index 0000000..a7810e2
--- /dev/null
+++ b/orphans.php
@@ -0,0 +1,144 @@
+<?php
+  require_once( 'core.php' );
+
+  require_once( 'bug_api.php' );
+
+  require_once( 'file_api.php' );
+  
+//  require_once( 'last_visited_api.php' );
+
+//global $g_session_validation;
+//$g_session_validation = OFF;
+//$tpl_file = __FILE__;
+//$tpl_mantis_dir = dirname( __FILE__ ) . DIRECTORY_SEPARATOR;
+//$tpl_show_page_header = true;
+//$tpl_force_readonly = false;
+
+  //auth_ensure_user_authenticated();
+
+/**
+ * Returns a list of all possible bug ID's
+ * @return array() of bug ID's
+ */
+function bug_get_bugs( ) {
+	$t_project_id = helper_get_current_project();
+	$t_bug_table = db_get_table( 'mantis_bug_table' );
+	$t_bug_file_table = db_get_table( 'mantis_bug_file_table' );
+	#$query = "SELECT id FROM $t_bug_table";
+	#$query = "SELECT bug_id FROM $t_bug_file_table";
+
+	if ( $t_project_id == ALL_PROJECTS) {
+		$query = "SELECT bug_id FROM $t_bug_file_table";
+		$db_result = db_query( $query );
+	} else {
+		$query = "SELECT distinct F.bug_id FROM $t_bug_file_table F, $t_bug_table B WHERE F.bug_id = B.ID AND B.project_id = " . db_param();
+		$db_result = db_query_bound( $query , Array( (int) $t_project_id) );
+	}
+	# $query = "SELECT F.bug_id FROM $t_bug_file_table F, $t_bug_table B WHERE F.bug_id = B.ID AND B.project_id = " . db_param();
+	#$db_result = db_query_bound( $query , Array( (int) $t_project_id) );
+	$num_files = db_num_rows( $db_result );
+
+	$t_result = array();
+
+	for( $i = 0;$i < $num_files;$i++ ) {
+		$t_result[] = db_fetch_array( $db_result );
+	}
+
+	return $t_result;
+}
+
+function bug_get_attachments_withfolder( $p_bug_id ) {
+        if( !file_can_view_bug_attachments( $p_bug_id ) ) {
+                return;
+        }
+
+        $c_bug_id = db_prepare_int( $p_bug_id );
+
+        $t_bug_file_table = db_get_table( 'mantis_bug_file_table' );
+
+        $query = "SELECT id, title, diskfile, filename, filesize, file_type, date_added, folder
+                                FROM $t_bug_file_table
+                                WHERE bug_id=" . db_param() . "
+                                ORDER BY date_added";
+        $db_result = db_query_bound( $query, Array( $c_bug_id ) );
+        $num_files = db_num_rows( $db_result );
+
+        $t_result = array();
+
+        for( $i = 0;$i < $num_files;$i++ ) {
+                $t_result[] = db_fetch_array( $db_result );
+        }
+
+        return $t_result;
+}
+
+/**
+ * Validate any attachments as needed when a bug is moved from project 
+to project.
+ *
+ * @param int $p_bug_id ID of bug containing attachments to be verified
+ * @return null
+ */
+function file_verify_bug_attachments( $p_bug_id ) {
+        $t_project_id_from = bug_get_field( $p_bug_id, 'project_id' );
+        $t_method = config_get( 'file_upload_method' );
+
+        if ( $t_method != DISK ) {
+                return;
+        }
+
+        if ( !file_bug_has_attachments( $p_bug_id ) ) {
+                return;
+        }
+
+        $t_path_from = project_get_field( $t_project_id_from, 'file_path' );
+
+        if ( is_blank( $t_path_from ) ) {
+                $t_path_from = config_get( 'absolute_path_default_upload_folder', null, null, $t_project_id_from );
+        }
+
+        file_ensure_valid_upload_path( $t_path_from );
+
+        # Initialize the update query to update a single row
+        #$t_bug_file_table = db_get_table( 'mantis_bug_file_table' );
+        $c_bug_id = db_prepare_int( $p_bug_id );
+        #$query_disk_attachment_update = "UPDATE $t_bug_file_table
+        #                                 SET folder=" . db_param() . "
+        #                                 WHERE bug_id=" . db_param() . "
+        #                                 AND id =" . db_param();
+
+        $t_attachment_rows = bug_get_attachments_withfolder( $p_bug_id );
+        $t_attachments_count = count( $t_attachment_rows );
+        for ( $i = 0; $i < $t_attachments_count; $i++ ) {
+                $t_row = $t_attachment_rows[$i];
+                $t_basename = basename( $t_row['diskfile'] );
+
+                $t_disk_file_name_from = file_path_combine( $t_path_from, $t_basename );
+		$t_disk_file_name_from_db = file_path_combine( $t_row['folder'], $t_basename );
+
+                if ( file_exists( $t_disk_file_name_from ) == false && file_exists( $t_disk_file_name_from_db ) == false)
+                {
+			$filename = $t_row['filename'];
+			$folder = $t_row['folder'];
+                	//error_log("Bug: $c_bug_id, File: $t_disk_file_name_from, FileName: $filename, Folder: $folder is orphaned.");
+			echo "<tr bgcolor='#c9ccc4'> <td>";
+			print_bug_link($c_bug_id);
+			echo "</td><td>$t_disk_file_name_from</td><td>$filename</td><td>$folder</td></tr>";
+                }
+        }
+}
+
+set_time_limit(60 * 2);
+//error_log('calling bug_get_bugs');
+$project_ids = bug_get_bugs();
+$rowcount = count($project_ids);
+//error_log("Found $rowcount bugs");
+echo "<table class='width100' cellspacing='1'><tr><td class='form-title'>ID</td><td class='form-title'>File</td><td class='form-title'>FileName</td><td class='form-title'>folder</td></tr>";
+for ( $i = 0; $i < $rowcount; $i++ ) {
+	file_verify_bug_attachments( $project_ids[$i]['bug_id'] );
+	//if ($i % 1000 == 0) {
+		//error_log("Processing $i of $rowcount");
+	//}
+}
+echo "</table>";
+?>
-- 
1.7.0.2.msysgit.0

fixforattachements.tgz (9,966 bytes)
fix-11687-1.2.9.patch (3,793 bytes)   
diff --git a/core/bug_api.php b/core/bug_api.php
index e43bc05..eaf24a6 100644
--- a/core/bug_api.php
+++ b/core/bug_api.php
@@ -1075,13 +1075,16 @@ function bug_copy( $p_bug_id, $p_target_project_id = null, $p_copy_custom_fields
 
 /**
  * Moves an issue from a project to another.
+ *
  * @todo Validate with sub-project / category inheritance scenarios.
- * @todo Fix #11687: Bugs with attachments that are moved will lose attachments.
  * @param int p_bug_id The bug to be moved.
  * @param int p_target_project_id The target project to move the bug to.
  * @access public
  */
 function bug_move( $p_bug_id, $p_target_project_id ) {
+	// Attempt to move disk based attachments to new project file directory.
+	file_move_bug_attachments( $p_bug_id, $p_target_project_id );
+
 	// Move the issue to the new project.
 	bug_set_field( $p_bug_id, 'project_id', $p_target_project_id );
 
diff --git a/core/file_api.php b/core/file_api.php
index 1fc416f..10fd088 100644
--- a/core/file_api.php
+++ b/core/file_api.php
@@ -909,3 +909,72 @@ function file_get_content_type_override( $p_filename ) {
 
 	return $g_file_download_content_type_overrides[$t_extension];
 }
+
+/**
+ * Move any attachments as needed when a bug is moved from project to project.
+ *
+ * @param int $p_bug_id ID of bug containing attachments to be moved
+ * @param int $p_project_id_to destination project ID for the bug
+ * @return null
+ */
+function file_move_bug_attachments( $p_bug_id, $p_project_id_to ) {
+	$t_project_id_from = bug_get_field( $p_bug_id, 'project_id' );
+	if ( $t_project_id_from == $p_project_id_to ) {
+		return;
+	}
+
+	$t_method = config_get( 'file_upload_method' );
+	if ( $t_method != DISK ) {
+		return;
+	}
+
+	if ( !file_bug_has_attachments( $p_bug_id ) ) {
+		return;
+	}
+
+	$t_path_from = project_get_field( $t_project_id_from, 'file_path' );
+	if ( is_blank( $t_path_from ) ) {
+		$t_path_from = config_get( 'absolute_path_default_upload_folder', null, null, $t_project_id_from );
+	}
+	file_ensure_valid_upload_path( $t_path_from );
+	$t_path_to = project_get_field( $p_project_id_to, 'file_path' );
+	if ( is_blank( $t_path_to ) ) {
+		$t_path_to = config_get( 'absolute_path_default_upload_folder', null, null, $p_project_id_to );
+	}
+	file_ensure_valid_upload_path( $t_path_to );
+	if ( $t_path_from == $t_path_to ) {
+		return;
+	}
+
+	# Initialize the update query to update a single row
+	$t_bug_file_table = db_get_table( 'mantis_bug_file_table' );
+	$c_bug_id = db_prepare_int( $p_bug_id );
+	$query_disk_attachment_update = "UPDATE $t_bug_file_table
+	                                 SET folder=" . db_param() . "
+	                                 WHERE bug_id=" . db_param() . "
+	                                 AND id =" . db_param();
+
+	$t_attachment_rows = bug_get_attachments( $p_bug_id );
+	$t_attachments_count = count( $t_attachment_rows );
+	for ( $i = 0; $i < $t_attachments_count; $i++ ) {
+		$t_row = $t_attachment_rows[$i];
+		$t_basename = basename( $t_row['diskfile'] );
+
+		$t_disk_file_name_from = file_path_combine( $t_path_from, $t_basename );
+		$t_disk_file_name_to = file_path_combine( $t_path_to, $t_basename );
+
+		if ( !file_exists( $t_disk_file_name_to ) ) {
+			chmod( $t_disk_file_name_from, 0775 );
+			if ( !rename( $t_disk_file_name_from, $t_disk_file_name_to ) ) {
+				if ( !copy( $t_disk_file_name_from, $t_disk_file_name_to ) ) {
+					trigger_error( FILE_MOVE_FAILED, ERROR );
+				}
+				file_delete_local( $t_disk_file_name_from );
+			}
+			chmod( $t_disk_file_name_to, config_get( 'attachments_file_permissions' ) );
+			db_query_bound( $query_disk_attachment_update, Array( db_prepare_string( $t_path_to ), $c_bug_id, db_prepare_int( $t_row['id'] ) ) );
+		} else {
+			trigger_error( ERROR_FILE_DUPLICATE, ERROR );
+		}
+	}
+}
fix-11687-1.2.9.patch (3,793 bytes)   

Relationships

related to 0015721 closedgrangeway Functionality to consider porting to master-2.0.x 
has duplicate 0012188 closedatrol attachments marked as missing in "view issue" after moving issue from one project to another with different upload file path 
has duplicate 0012310 closedatrol Attachements are missing when a issue is moved from a domain to anotherone. 
has duplicate 0014355 closedatrol When moving an issue to another project, attachments where missing/not moved 
related to 0014796 closedatrol "Attachment missing" while re-copying an issue. 

Activities

jchoover

jchoover

2010-03-19 18:26

reporter   ~0024825

My suggestion would be either to
A) When the bug is reassigned from one project to another, move the disk files as well.
B) I don't have the DB schema handy, but one could also do a secondary query or modify file_get_visible_attachments to utilize the "other" field in the DB which gives the absolute path to the file, in addition to the file name.

I would prefer A just so we could maintain "pure" directory tree's.

atrol

atrol

2010-03-31 03:27

developer   ~0024943

Reproduced in a clean installation

jchoover

jchoover

2010-03-31 10:07

reporter   ~0024986

I've been looking for documentation on what the intent was, but I am not seeing much of anything.

Would you agree that it would make the most sense to move disk based attachments of a bug when the bug is moved between projects?

Also, I have yet to get my test VM fully setup, but there was an additional field in the DB storing the absolute path to the folder which contains the attachment. Since it didn't seem like this value was used, is this information only for debugging or is it leftovers from a previous version? (If we do end up moving the files, this path should also be updated.)

jchoover

jchoover

2010-04-03 23:01

reporter   ~0025013

Last edited: 2010-04-03 23:02

I've attached a potential fix to the issue. Adding the 2 functions in the text file to file_api.php, and then tweaking bug_actiongroup.php to

A) require file_api.php
B) wrap the line
bug_set_field( $t_bug_id, 'project_id', $f_project_id );

with

$t_origional_bug_project = bug_get_field( $t_bug_id, 'project_id' );
bug_set_field( $t_bug_id, 'project_id', $f_project_id );
file_move_bug_attachments( $t_bug_id, $t_origional_bug_project, $f_project_id );

jchoover

jchoover

2010-04-03 23:03

reporter   ~0025014

I'm close on the proper formatting and notation, but I wouldn't doubt it if I mucked something up. If one of the existing developers would like to take a look at it and let me know how far off base I am, I'd be up for the comments and critiques.

dhx

dhx

2010-04-04 05:47

reporter   ~0025015

Thanks for your help jchoover!

Just some comments on your patch:

No need to check against true (== true)... just leave out the right hand side off those boolean comparisons.

I'm not convinced about the need for file_move_bug_attachment() - I think it may be best to just have a single file_move_bug_attachments() function that works with one, ten or more attachments at a time.

The SQL query would be better located outside the for loop so that we only need one query for multiple files instead of one query for each file.

The approach to moving files with hard links seems a little odd to me. Is there any reason this can't be simplified with a platform-independent call to the PHP rename() function? I understand that rename() will overwrite an existing destination file... is this a situation we have to consider?

With a few minor adjustments, are you able to resubmit a patch as outputted from git format-patch? In this form we can commit it directly with credit going to yourself (if you don't want credit, that is fine too).

I'm impressed with what you've been able to code here... it follows the MantisBT coding conventions and uses the inbuilt functions correctly. Well done!

jchoover

jchoover

2010-04-04 11:57

reporter   ~0025019

My only reasoning around file_move_bug_attachment() was to wrap the file system operations and be entirely certain that the moves are happening properly.

The hard links approach was just something that felt cleaner at the time. (Ensuring I could create the file and set the perms before removing the old one.) Hind sight tells me I was probably over thinking the situation, but I did see reports where Windows machines that rename didn't always work as expected (which was the reason for the inner copy).

As far as destination file check, I do ensure the destination file exists before trying the rename and I trigger an error if one all ready exists. This is partially why I wanted the SQL update inside the loop, though the initialization of the SQL statement could happen outside the loop and just leave the execution call in the loop.

I was trying to cover for a fringe case where a bug with 10 attachments is moved, and the first 5 succeed but the 6th fails due to a duplicate destination name. At first, I had thought we could generate a new hashed file name and update the database accordingly but looking at how the current file names were generated made me wonder if you were embedding some sort of logic inside the name.

I did download/build/install git, but I have never used the tool before so it may take a bit of futzing to get to a state where I can use it effectively. As well, if I am submitting a git patch, which branch should the patch be targeted for?

atrol

atrol

2010-04-04 12:06

developer   ~0025020

some information about contributing patches
http://docs.mantisbt.org/master/en/developers.html#DEV.CONTRIB

jchoover

jchoover

2010-04-04 12:12

reporter   ~0025021

One other thing that was of concern is the fact that my solution doesn't make a single atomic transaction, so the existing bug's project is updated first and then I was moving any disk based attachments. If any part of the moves fail, the bug is left in a partially moved state.
I can't think of a way to wrap the code to ensure that every possible fault can be safely handled. I also ponder writing a cleanup script which could be run against an existing DB to move all attachments that are orphaned back to their proper location. (I could see a query joining the attachment table with the bug and project table, filtering on the attachments where the folder field != project upload folder.
This logic could also be extended to allow for mass updating if someone were to change the upload folder on a project with existing attachments.

jchoover

jchoover

2010-04-04 12:50

reporter   ~0025022

Let's see if that patch file is even remotely correct. Note, I created this based off of the previous suggestions but didn't fire up my Linux VM to test. As such, I am using TortiseGit instead of the command line tools. I'm now going to fire up the linux box and attempt to apply the patch I created in windows land to the 1.2.0 base and see what happens.

jchoover

jchoover

2010-04-28 10:17

reporter   ~0025330

I have tested my patch locally and everything appears to function. Has any other developer looked at this, and what are the odds of getting this merged into the current development branch?

Has anyone else who is experiencing this issue tried my patch?

nickw

nickw

2010-05-03 17:28

reporter   ~0025383

Our recently installed version (1.2.1) has this problem, but it appears this patch has been written against the changes made in January, so that it uses versions of bug_actiongroup.php that refers to require_api rather than require_once?

As a relative neophyte, how might I use this logic to patch the existing 1.2.1?

Or am I missing something more basic?

jchoover

jchoover

2010-05-04 00:54

reporter   ~0025384

I did a quick scan and saw that they had cleaned up the require_api calls. Noting the require call they changed it to will include the file_api I was patching in, so that part can be ignored.

I did a fast hack and regenerated a 1.2.1 patch by simply replaying my changes (after checking out the 1.2.1 tag). Note I didn't have time to test it, but odds are it should function as expected.

As far as how to apply the patch, these are generated by git and are easily applied by git. If you don't want to get git, checkout, and then patch you could manually apply the changes in a test environment.

Note, I didn't change any existing lines, but rather added 2 lines in bug_actiongroup and 79 in file_api.

nickw

nickw

2010-05-04 16:44

reporter   ~0025397

I was pretty sure that was the case (file_api being already included) but still was having some troubles with it. I've got it going good, but had to make a more explicit reference to the table. Line 42 of your latest patch file has:

$t_bug_file_table = db_get_table( 'bug_file' );

I had to make that:

$t_bug_file_table = db_get_table( 'mantis_bug_file_table' );

Not sure if that is significant to others, but I saw other places in file_api where it was referred to that way, so have (at least in the short term) made the change with no apparent bad results for me anyway.

Thank you so very much for your work on this - I had told our developers that moving the attachments out of the database should not impact badly on them - so to get this fixed has been a real priority for me these last few days!

jchoover

jchoover

2010-12-01 12:24

reporter   ~0027530

I talked to DHX some and it was stated that this fix was in queue, but I see the target has been changed from 1.2.3 to 1.2.x. Do we have an expected target?

cas

cas

2011-02-04 03:45

reporter   ~0028156

Found my solution on this issue wheer i just added a function to bug_api called bug_move. This function could be simply called directly with 2 parameters.
Approach on files was to copy first and delete once done.

dhx

dhx

2011-02-26 05:07

reporter   ~0028301

Apologies for the delay in committing this fix. Thank you Jacob for the patch.

I have updated the patch to work with the latest version of MantisBT 1.3.x and have also simplified and cleaned up the new function. Note the use of "guard conditions" on the function instead of massively nested if clauses.

I have not properly tested this updated commit and would appreciate it if some people are able to test this and confirm whether things are working as expected.

Thanks again for your help :)

jchoover

jchoover

2011-04-12 13:19

reporter   ~0028607

Not sure if this is the proper procedure or not, but I had a comment on the commit.

Specifically, if there are "disconnected" attachements in an existing instance would we not want to make this code safe to be able to fix them by moving the bug to it's origional project and back again?

If so, I think it would be a simple change to the file_api fix. I simply added if ( file_exists($t_disk_file_name_from) ) inside the loop that moves the files. Comments?


function file_move_bug_attachments( $p_bug_id, $p_project_id_to ) {
...
for ( $i = 0; $i < $t_attachments_count; $i++ ) {
$t_row = $t_attachment_rows[$i];
$t_basename = basename( $t_row['diskfile'] );

            $t_disk_file_name_from = file_path_combine( $t_path_from, $t_basename );
            $t_disk_file_name_to = file_path_combine( $t_path_to, $t_basename );

            if ( file_exists($t_disk_file_name_from) )
            {
                    if ( !file_exists( $t_disk_file_name_to ) ) {
                            chmod( $t_disk_file_name_from, 0775 );
                            if ( !rename( $t_disk_file_name_from, $t_disk_file_name_to ) ) {
                                    if ( !copy( $t_disk_file_name_from, $t_disk_file_name_to ) ) {
                                            trigger_error( FILE_MOVE_FAILED, ERROR );
                                    }
                                    file_delete_local( $t_disk_file_name_from );
                            }
                            chmod( $t_disk_file_name_to, config_get( 'attachments_file_permissions' ) );
                            db_query_bound( $query_disk_attachment_update, Array( db_prepare_string( $t_path_to ), $c_bug_id, db_prepare_int( $t_row['id'] ) ) );
                    } else {
                            trigger_error( ERROR_FILE_DUPLICATE, ERROR );
                    }
            }
    }

jchoover

jchoover

2011-04-12 20:31

reporter   ~0028608

Also, for those wondering how to find borked attachments I have attached my hackish orphans.php. I wrote it against 1.2.4, and I hacked it by adding it to the end of my_view_page.php, via adding require_once( 'orphans.php'); right before html_page_bottom();

Libra

Libra

2011-08-05 05:39

reporter   ~0029367

This bug will be "officially" resolved in the 1.2.х branch? I have not found any information in the changelog of 1.2.5 and 1.2.6 (now we use 1.2.4)

atrol

atrol

2011-08-05 06:34

developer   ~0029368

Libra, atm the issue has set "Target Version" to 1.3.x and also "Fixed in Version" to 1.3.x
So you can't find information in changelog of any 1.2.x version

Libra

Libra

2011-08-05 08:38

reporter   ~0029369

Last edited: 2011-08-05 08:39

I understand as for version 1.3.x, but there is a bug in 1.2.4 and this bug is very serious hindrance to the work (we have about 20 projects) and we do not want to install 1.3.х version on a production server. Is it possible to fix this problem in 1.2.х?

atrol

atrol

2011-08-05 10:55

developer   ~0029370

You can apply the patch for 1.2.4 that jchoover attached to this issue.
Read also what David Hicks wrote as a comment to the changeset.

jchoover

jchoover

2011-08-05 11:12

reporter   ~0029371

If you would prefer the raw files over a patch, let me know and I can provide the patched file_api.php and bug_actiongroup.php that we use on our production instance.

My comment (http://www.mantisbt.org/bugs/view.php?id=11687#c28607) was in regards to trying to make this safe to fix broken attachments. If you aren't in a broken state it would make more sense to use dhx's commit rather than my tweaks. My files were changed to allow for our project managers to be able to "fix" borked attachments by pushing them back to the original project(s) and then back to where they should be. The process of moving them around should find the missing files, assuming you haven't changed the storage location of the projects.

Libra

Libra

2011-08-05 11:32

reporter   ~0029372

atrol and jchoover, thanks for your support! For jchoover - please provide patched files, thanks in advance!

jchoover

jchoover

2011-08-05 13:37

reporter   ~0029373

The fixforattachements.tgz is based off of the 1.2.4 code base with the fix implemented in it. As always, make sure you have a backup of the files and data before applying the fix. I would also store a backup copy of the 2 files in case you needed to revert the changes.

Libra

Libra

2011-08-12 06:25

reporter   ~0029468

jchoover, thanks for patсh - everything works as expected! I upgraded to 1.2.6 and created a patch for it (a bit different from 1.2.4), see Patch-for-11687-1.2.6.zip

jthomas

jthomas

2011-09-18 12:02

reporter   ~0029791

I recently upgraded from 1.1.x to 1.2.8 and I see that 'Patch-for-11687-1.2.6.zip' is meant to work for 1.2.6. Can you tell me if it also work for 1.2.8? Thanks in advance.

Libra

Libra

2011-09-18 15:22

reporter   ~0029797

Unfortunately, can not say anything about version 1.2.8, has not been updated since version 1.2.6

jthomas

jthomas

2011-09-18 15:37

reporter   ~0029799

Thanks for the update. I'll keep checking here to see if someone patches 1.2.8, otherwise will update to 1.3.x when available/stable.

jthomas

jthomas

2012-03-06 15:42

reporter   ~0031390

Can anybody confirm that this issues has been fixed in 1.2.9? I'd rather just go from 1.2.8 to 1.2.9 instead of waiting for 1.3.X since this issue still exists for us.

Thanks,
Jay

dregad

dregad

2012-06-14 05:39

developer   ~0032099

It is not fixed in 1.2.9. However, I just checked and dhx's commit for 1.3.x applies cleanly on 1.2.x branch, so it would be easy to back-port it.

I uploaded a patch file (fix-11687.patch), which should apply fine on release 1.2.9 and above. If you can spare the time to test and confirm that everything works as it should, I can push the changes to the 1.2.x.

jthomas

jthomas

2012-06-25 11:52

reporter   ~0032175

Thanks dregad, you're giving me more credit than I've earned. I have no idea how to apply that patch so I googled some possibilities but ended up just messing up a couple of my files on my test box. The original is working again, but I'll have to spend some more time figuring this out. If there's something you can point me to, to read up on, I'd be happy to learn what I need to otherwise, I'll keep plugging away as time permits.

Thanks for the attempt at helping out.

dregad

dregad

2012-06-25 12:28

developer   ~0032176

Sorry about that. To apply the patch, assuming you're on linux, the following should do the trick

cd /path/to/mantisbt
patch -p1 -i /path/to/fix-11687.patch

On windows, if you have cygwin the commands above should work too. Otherwise you need to use some utility, e.g. see http://stackoverflow.com/questions/517257/how-do-i-apply-a-diff-patch-on-windows

jthomas

jthomas

2012-06-25 13:05

reporter   ~0032177

Thanks for that, I am on linux (Ubuntu). I hadn't yet upgraded to 1.2.9 so I'm still on 1.2.8 and tried the patch and got an error...


patching file core/bug_api.php
Hunk 0000001 succeeded at 1044 (offset -31 lines).
patching file core/file_api.php
Hunk 0000001 FAILED at 908.
1 out of 1 hunk FAILED -- saving rejects to file core/file_api.php.rej

I have no problem upgrading to 1.2.9 to test the patch if you think that's the issue. I thought it'd also work on 1.2.8 by my interpretation of you previous note.

dregad

dregad

2012-06-25 13:22

developer   ~0032178

I thought it'd also work on 1.2.8 by my interpretation of you previous note.

Wrong interpretation ;-) - read again:

patch file [...] should apply fine on release 1.2.9 and above

So you should either upgrade your test env, or try and figure out what the conflict is with 1.2.8 (no time to check myself, sorry)

jthomas

jthomas

2012-06-25 13:41

reporter   ~0032179

Gotcha, no worries, I appreciate the help. I don't think I can get to this today, but I'll try the upgrade route and try the patch with that version.

jthomas

jthomas

2012-06-25 15:49

reporter   ~0032180

I upgraded to 1.2.9 and installed the patch. I think that part went well, here's what it spit out...


patching file core/bug_api.php
Hunk 0000001 succeeded at 1058 (offset -17 lines).
patching file core/file_api.php

But when I went to move a ticket with an attached file, it gave me this...

APPLICATION ERROR 0000401

Database query failed. Error received from database was 0001064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SET folder='/var/www/mantis/files/facilities/'
' at line 2 for the query: UPDATE
SET folder=?
WHERE bug_id=?
AND id =?.

dregad

dregad

2012-06-26 07:19

developer   ~0032185

I overlooked the fact that in 1.3 branch, the referencing of DB tables in the code is not the same as for 1.2 - this is probably what is causing the error you get (although I was not able to reproduce it).

I'm uploading a new patch (which should be applied on top of unmodified 1.2.9) - alternatively you can manually edit core/file_api.php as follows (your line numbers could be different)


diff --git a/core/file_api.php b/core/file_api.php
index 5963507..10fd088 100644
--- a/core/file_api.php
+++ b/core/file_api.php
@@ -947,7 +947,7 @@ function file_move_bug_attachments( $p_bug_id, $p_project_id_to ) {
}

# Initialize the update query to update a single row
  • $t_bug_file_table = db_get_table( 'bug_file' );
  • $t_bug_file_table = db_get_table( 'mantis_bug_file_table' );
    $c_bug_id = db_prepare_int( $p_bug_id );
    $query_disk_attachment_update = "UPDATE $t_bug_file_table
    SET folder=" . db_param() . "
jthomas

jthomas

2012-06-26 18:46

reporter   ~0032188

Thanks, give me a day or two and I'll try this again with a unmodified install of 1.2.9 since I'll be using the patch to update my live site.

dregad

dregad

2012-06-27 04:12

developer   ~0032190

OK. Looking forward to your feedback.

As a side note, if you're going to upgrade your production site, I would advise you to go for 1.2.11 instead of 1.2.9 (the provided patch applies cleanly on top of that too) to benefit from several security and bug fixes

jthomas

jthomas

2012-06-27 16:14

reporter   ~0032203

I didn't realize 1.2.11 was out, so thanks for the info.

I upgraded from 1.2.8 to 1.2.11, applied the patch and I'm able to transfer attachments along with the tickets now. It works like a champ, thanks!

dregad

dregad

2012-06-27 19:35

developer   ~0032204

Thanks to jthomas' testing efforts, the patch has now been ported to 1.2.x branch so I'm marking this as resolved.

atrol

atrol

2012-06-28 05:48

developer   ~0032211

FILE_MOVE_FAILED is not defined, should be ERROR_FILE_MOVE_FAILED
https://github.com/mantisbt/mantisbt/commit/cdf383bd6cc58534d897a3783dbb345e3cf5ed00#L1R970

dregad

dregad

2012-06-28 08:34

developer   ~0032213

Thanks for checking Roland, I'll fix that shortly

AdamR

AdamR

2012-09-08 20:04

reporter   ~0032814

Thanks for the patch, worked perfectly on a long-time running 1.2.10.

I'm going to write a script shortly that will find all missing attachments, look within the Mantis installation folder for them, and if found move them to the correct folder for the project. Once it's done I'll share :)

grangeway

grangeway

2013-04-05 17:56

reporter   ~0036214

Marking as 'acknowledged' not resolved/closed to track that change gets ported to master-2.0.x branch

Related Changesets

MantisBT: master 08c027af

2011-02-26 05:03

Jacob Hoover

Committer: dhx


Details Diff
Fix 0011687: Bugs with attachments that are moved will lose attachments

When a bug is logged and assigned to a project which has a project path
assigned, and later a mantis "administrator" moves the bug from the
initial project to a new project, the attachments are lost.

Jacob's initial patch from mid 2010 has been updated to work with the
latest version of MantisBT 1.3.x. Furthermore the patch has been cleaned
up and simplified. Needs testing however!

Signed-off-by: David Hicks <hickseydr@optusnet.com.au>
Affected Issues
0011687
mod - core/bug_api.php Diff File
mod - core/file_api.php Diff File

MantisBT: master-1.2.x cdf383bd

2012-06-27 12:22

jchoover


Details Diff
Fix 0011687: Bugs with attachments that are moved will lose attachments

Backporting 08c027af59b88a6934377d71d19a86edfd7d12dd to 1.2.x branch.

When a bug is logged and assigned to a project which has a project path
assigned, and later a mantis "administrator" moves the bug from the
initial project to a new project, the attachments are lost.

Signed-off-by: Damien Regad <damien.regad@merckgroup.com>
Affected Issues
0011687
mod - core/bug_api.php Diff File
mod - core/file_api.php Diff File

MantisBT: master-1.2.x 4a6c8456

2012-06-28 01:35

dregad


Details Diff
Fix 0011687: use of incorrect error constant Affected Issues
0011687
mod - core/file_api.php Diff File

MantisBT: master 21e1a24b

2012-06-28 01:35

dregad


Details Diff
Fix 0011687: use of incorrect error constant Affected Issues
0011687
mod - core/file_api.php Diff File