View Issue Details

IDProjectCategoryView StatusLast Update
0012013mantisbtplug-inspublic2014-12-08 00:33
Reporterdominik Assigned Todregad  
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Product Version1.2.1 
Target Version1.3.0-beta.1Fixed in Version1.3.0-beta.1 
Summary0012013: Improvements for plugin ImportExportXml (and required core changes)
Description

Attached a patch file with some ImportExportXml plugin improvements...
Unfortunately I had private files in theses commits so I had to manually remove files -> if you have problems to apply the patch please let me know (or if you know how I can exlude files when creating patches please let me know as well ;-))

Improvements done:

Added support for custom fields, bugnotes and attachments

Added support for dates (date submitted, last updated) - keep dates as given in import file

Added function to easily retrieve the contents of a file (file_api.php)

Tagspatch
Attached Files
2010-06-09_improvements_for_plugin_ImportExportXml.patch (25,825 bytes)   
From 63d96cebfed6db935e0deae47025d931c1c54f61 Mon Sep 17 00:00:00 2001
From: Dominik Blunk <dominik@blunk.ch>
Date: Mon, 7 Jun 2010 16:37:30 +0200
Subject: [PATCH 18/19] Set default for profile_id in constructor

---
 core/bug_api.php |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/core/bug_api.php b/core/bug_api.php
index 7d6e030..02dcc96 100644
--- a/core/bug_api.php
+++ b/core/bug_api.php
@@ -100,7 +100,7 @@ class BugData {
 
 	# omitted:
 	# var $bug_text_id
-	protected $profile_id;
+	protected $profile_id = 0;
 
 	# extended info
 	protected $description = '';
-- 
1.7.0.2.msysgit.0


From 1a13b8fcb4f54fec2a0d1e74b675e69010204418 Mon Sep 17 00:00:00 2001
From: Dominik Blunk <dominik@blunk.ch>
Date: Tue, 8 Jun 2010 16:48:42 +0200
Subject: [PATCH 19/19] Plugin ImportXml enhanced with bugnotes, custom fields and attachments

---
 core/bug_api.php                            |   13 ++-
 core/bugnote_api.php                        |   15 ++-
 core/constant_inc.php                       |    1 +
 core/file_api.php                           |  117 +++++++++++++++++++++-
 lang/strings_english.txt                    |    1 +
 plugins/XmlImportExport/ImportXml.php       |   32 +++++-
 plugins/XmlImportExport/ImportXml/Issue.php |  143 +++++++++++++++++++++++++--
 plugins/XmlImportExport/pages/export.php    |   87 ++++++++++++++++
 8 files changed, 383 insertions(+), 26 deletions(-)

diff --git a/core/bug_api.php b/core/bug_api.php
index 02dcc96..7a2b5d4 100644
--- a/core/bug_api.php
+++ b/core/bug_api.php
@@ -302,7 +302,14 @@ class BugData {
 
 		# check due_date format
 		if( is_blank( $this->due_date ) ) {
-			$this_due_date = date_get_null();
+			$this->due_date = date_get_null();
+		}
+		# check date submitted and last modified
+		if( is_blank( $this->date_submitted ) ) {
+			$this->date_submitted = db_now();
+		}
+		if( is_blank( $this->last_updated ) ) {
+			$this->last_updated = db_now();
 		}
 
 		$t_bug_table = db_get_table( 'mantis_bug_table' );
@@ -366,7 +373,7 @@ class BugData {
 					      " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ",
 					      " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ')';
 
-		db_query_bound( $query, Array( $this->project_id, $this->reporter_id, $this->handler_id, $this->duplicate_id, $this->priority, $this->severity, $this->reproducibility, $t_status, $this->resolution, $this->projection, $this->category_id, db_now(), db_now(), $this->eta, $t_text_id, $this->os, $this->os_build, $this->platform, $this->version, $this->build, $this->profile_id, $this->summary, $this->view_state, $this->sponsorship_total, $this->sticky, $this->fixed_in_version, $this->target_version, $this->due_date ) );
+		db_query_bound( $query, Array( $this->project_id, $this->reporter_id, $this->handler_id, $this->duplicate_id, $this->priority, $this->severity, $this->reproducibility, $t_status, $this->resolution, $this->projection, $this->category_id, $this->date_submitted, $this->last_updated, $this->eta, $t_text_id, $this->os, $this->os_build, $this->platform, $this->version, $this->build, $this->profile_id, $this->summary, $this->view_state, $this->sponsorship_total, $this->sticky, $this->fixed_in_version, $this->target_version, $this->due_date ) );
 
 		$this->id = db_insert_id( $t_bug_table );
 
@@ -1683,7 +1690,7 @@ function bug_reopen( $p_bug_id, $p_bugnote_text = '', $p_time_tracking = '0:00',
  */
 function bug_update_date( $p_bug_id ) {
 	$c_bug_id = (int) $p_bug_id;
-
+	
 	$t_bug_table = db_get_table( 'mantis_bug_table' );
 
 	$query = "UPDATE $t_bug_table
diff --git a/core/bugnote_api.php b/core/bugnote_api.php
index 230eff7..fde2f76 100644
--- a/core/bugnote_api.php
+++ b/core/bugnote_api.php
@@ -120,14 +120,19 @@ function bugnote_is_user_reporter( $p_bugnote_id, $p_user_id ) {
  * @param string $p_attr
  * @param int $p_user_id user id
  * @param bool $p_send_email generate email?
+ * @param int $p_date_submitted date submitted (defaults to now())
+ * @param int $p_last_modified last modification date (defaults to now())
+ * @param bool $p_skip_bug_update skip bug last modification update (useful when importing bugs/bugnotes)
  * @return false|int false or indicating bugnote id added
  * @access public
  */
-function bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking = '0:00', $p_private = false, $p_type = 0, $p_attr = '', $p_user_id = null, $p_send_email = TRUE ) {
+function bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking = '0:00', $p_private = false, $p_type = 0, $p_attr = '', $p_user_id = null, $p_send_email = TRUE, $p_date_submitted = 0, $p_last_modified = 0, $p_skip_bug_update = FALSE ) {
 	$c_bug_id = db_prepare_int( $p_bug_id );
 	$c_time_tracking = helper_duration_to_minutes( $p_time_tracking );
 	$c_private = db_prepare_bool( $p_private );
 	$c_type = db_prepare_int( $p_type );
+	$c_date_submitted = $p_date_submitted <= 0 ? db_now() : db_prepare_int( $p_date_submitted );
+	$c_last_modified = $p_last_modified <= 0 ? db_now() : db_prepare_int( $p_last_modified );
 
 	$t_bugnote_text_table = db_get_table( 'mantis_bugnote_text_table' );
 	$t_bugnote_table = db_get_table( 'mantis_bugnote_table' );
@@ -166,7 +171,7 @@ function bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking = '0:00', $p_
 
 	# Check for private bugnotes.
 	# @@@ VB: Should we allow users to report private bugnotes, and possibly see only their own private ones
-	if( $p_private && access_has_bug_level( config_get( 'private_bugnote_threshold' ), $p_bug_id, $c_user_id ) ) {
+	if( $c_private && access_has_bug_level( config_get( 'private_bugnote_threshold' ), $p_bug_id, $c_user_id ) ) {
 		$t_view_state = VS_PRIVATE;
 	} else {
 		$t_view_state = VS_PUBLIC;
@@ -177,13 +182,15 @@ function bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking = '0:00', $p_
 				(bug_id, reporter_id, bugnote_text_id, view_state, date_submitted, last_modified, note_type, note_attr, time_tracking )
 			VALUES
 				(" . db_param() . ', ' . db_param() . ',' . db_param() . ', ' . db_param() . ', ' . db_param() . ',' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ' )';
-	db_query_bound( $query, Array( $c_bug_id, $c_user_id, $t_bugnote_text_id, $t_view_state, db_now(), db_now(), $c_type, $p_attr, $c_time_tracking ) );
+	db_query_bound( $query, Array( $c_bug_id, $c_user_id, $t_bugnote_text_id, $t_view_state, $c_date_submitted, $c_last_modified, $c_type, $p_attr, $c_time_tracking ) );
 
 	# get bugnote id
 	$t_bugnote_id = db_insert_id( $t_bugnote_table );
 
 	# update bug last updated
-	bug_update_date( $p_bug_id );
+	if ( !$p_skip_bug_update ) {
+		bug_update_date( $p_bug_id );
+	}
 
 	# log new bug
 	history_log_event_special( $p_bug_id, BUGNOTE_ADDED, bugnote_format_id( $t_bugnote_id ) );
diff --git a/core/constant_inc.php b/core/constant_inc.php
index 3dc5a91..6f17b41 100644
--- a/core/constant_inc.php
+++ b/core/constant_inc.php
@@ -303,6 +303,7 @@ define( 'ERROR_CUSTOM_FIELD_NAME_NOT_UNIQUE', 1301 );
 define( 'ERROR_CUSTOM_FIELD_IN_USE', 1302 );
 define( 'ERROR_CUSTOM_FIELD_INVALID_VALUE', 1303 );
 define( 'ERROR_CUSTOM_FIELD_INVALID_DEFINITION', 1304 );
+define( 'ERROR_CUSTOM_FIELD_NOT_LINKED_TO_PROJECT', 1305 );
 
 # ERROR_LDAP_*
 define( 'ERROR_LDAP_AUTH_FAILED', 1400 );
diff --git a/core/file_api.php b/core/file_api.php
index 6fafe2b..6ff2d85 100644
--- a/core/file_api.php
+++ b/core/file_api.php
@@ -615,12 +615,21 @@ function file_is_name_unique( $p_name, $p_bug_id ) {
  *
  * @param integer $p_bug_id the bug id
  * @param array $p_file the uploaded file info, as retrieved from gpc_get_file()
+ * @param array $p_file the uploaded file info, as retrieved from gpc_get_file()
+ * @param string $p_table table ('bug' or 'doc')
+ * @param string $p_title file title
+ * @param string $p_desc file description
+ * @param int $p_user_id user id
+ * @param int $p_date_added date added
+ * @param bool $p_skip_bug_update skip bug last modification update (useful when importing bug attachments)
  */
-function file_add( $p_bug_id, $p_file, $p_table = 'bug', $p_title = '', $p_desc = '', $p_user_id = null ) {
+function file_add( $p_bug_id, $p_file, $p_table = 'bug', $p_title = '', $p_desc = '', $p_user_id = null, $p_date_added = 0, $p_skip_bug_update = false ) {
 
 	file_ensure_uploaded( $p_file );
 	$t_file_name = $p_file['name'];
 	$t_tmp_file = $p_file['tmp_name'];
+	
+	$c_date_added = $p_date_added <= 0 ? db_now() : db_prepare_int( $p_date_added );
 
 	if( !file_type_check( $t_file_name ) ) {
 		trigger_error( ERROR_FILE_NOT_ALLOWED, ERROR );
@@ -715,13 +724,15 @@ function file_add( $p_bug_id, $p_file, $p_table = 'bug', $p_title = '', $p_desc
 	$query = "INSERT INTO $t_file_table
 						(" . $p_table . "_id, title, description, diskfile, filename, folder, filesize, file_type, date_added, content, user_id)
 					  VALUES
-						($c_id, '$c_title', '$c_desc', '$c_unique_name', '$c_new_file_name', '$c_file_path', $c_file_size, '$c_file_type', '" . db_now() . "', $c_content, $c_user_id)";
+						($c_id, '$c_title', '$c_desc', '$c_unique_name', '$c_new_file_name', '$c_file_path', $c_file_size, '$c_file_type', '" . $c_date_added . "', $c_content, $c_user_id)";
 	db_query( $query );
 
 	if( 'bug' == $p_table ) {
 
 		# updated the last_updated date
-		$result = bug_update_date( $p_bug_id );
+		if ( !$p_skip_bug_update ) {
+			$result = bug_update_date( $p_bug_id );
+		}
 
 		# log new bug
 		history_log_event_special( $p_bug_id, FILE_ADDED, $t_file_name );
@@ -856,3 +867,103 @@ function file_get_extension( $p_filename ) {
 	}
 	return $t_extension;
 }
+
+/**
+ * Get file content
+ *
+ * @param int $p_file_id file id
+ * @param string $p_type file type
+ * @return array file type and content
+ */
+function file_get_content( $p_file_id, $p_type = 'bug' ) {
+	# we handle the case where the file is attached to a bug
+	# or attached to a project as a project doc.
+	$query = '';
+	switch ( $p_type ) {
+		case 'bug':
+			$t_bug_file_table = db_get_table( 'mantis_bug_file_table' );
+			$query = "SELECT *
+				FROM $t_bug_file_table
+				WHERE id=" . db_param();
+			break;
+		case 'doc':
+			$t_project_file_table = db_get_table( 'mantis_project_file_table' );
+			$query = "SELECT *
+				FROM $t_project_file_table
+				WHERE id=" . db_param();
+			break;
+		default:
+			return false;
+	}
+	$result = db_query_bound( $query, Array( $p_file_id ) );
+	$row = db_fetch_array( $result );
+	
+	if ( $f_type == 'bug' ) {
+		$t_project_id = bug_get_field( $row['bug_id'], 'project_id' );
+	} else {
+		$t_project_id = $row['bug_id'];
+	}
+	
+	# If finfo is available (always true for PHP >= 5.3.0) we can use it to determine the MIME type of files
+	$finfo_available = false;
+	if ( class_exists( 'finfo' ) ) {
+		$t_info_file = config_get( 'fileinfo_magic_db_file' );
+
+		if ( is_blank( $t_info_file ) ) {
+			$finfo = new finfo( FILEINFO_MIME );
+		} else {
+			$finfo = new finfo( FILEINFO_MIME, $t_info_file );
+		}
+
+		if ( $finfo ) {
+			$finfo_available = true;
+		}
+	}
+
+	$t_content_type = $row['file_type'];
+	
+	# dump file content to the connection.
+	switch ( config_get( 'file_upload_method' ) ) {
+		case DISK:
+			$t_local_disk_file = file_normalize_attachment_path( $row['diskfile'], $t_project_id );
+
+			if ( file_exists( $t_local_disk_file ) ) {
+				if ( $finfo_available ) {
+					$t_file_info_type = $finfo->file( $t_local_disk_file );
+
+					if ( $t_file_info_type !== false ) {
+						$t_content_type = $t_file_info_type;
+					}
+				}
+				return array( 'type' => $t_content_type, 'content' => file_get_contents( $t_local_disk_file ) );
+			}
+			break;
+		case FTP:
+			$t_local_disk_file = file_normalize_attachment_path( $row['diskfile'], $t_project_id );
+
+			if ( !file_exists( $t_local_disk_file ) ) {
+				$ftp = file_ftp_connect();
+				file_ftp_get ( $ftp, $t_local_disk_file, $row['diskfile'] );
+				file_ftp_disconnect( $ftp );
+			}
+
+			if ( $finfo_available ) {
+				$t_file_info_type = $finfo->file( $t_local_disk_file );
+
+				if ( $t_file_info_type !== false ) {
+					$t_content_type = $t_file_info_type;
+				}
+			}
+			return array( 'type' => $t_content_type, 'content' => file_get_contents( $t_local_disk_file ) );
+			break;
+		default:
+			if ( $finfo_available ) {
+				$t_file_info_type = $finfo->buffer( $row['content'] );
+
+				if ( $t_file_info_type !== false ) {
+					$t_content_type = $t_file_info_type;
+				}
+			}
+			return array( 'type' => $t_content_type, 'content' => $row['content'] );
+	}
+}
diff --git a/lang/strings_english.txt b/lang/strings_english.txt
index ed7534d..9ac4ebf 100644
--- a/lang/strings_english.txt
+++ b/lang/strings_english.txt
@@ -239,6 +239,7 @@ $MANTIS_ERROR[ERROR_CUSTOM_FIELD_NAME_NOT_UNIQUE] = 'This is a duplicate name.';
 $MANTIS_ERROR[ERROR_CUSTOM_FIELD_IN_USE] = 'At least one project still uses this field.';
 $MANTIS_ERROR[ERROR_CUSTOM_FIELD_INVALID_VALUE] = 'Invalid value for field "%1$s".';
 $MANTIS_ERROR[ERROR_CUSTOM_FIELD_INVALID_DEFINITION] = 'Invalid custom field definition.';
+$MANTIS_ERROR[ERROR_CUSTOM_FIELD_NOT_LINKED_TO_PROJECT] = 'Custom field "%1$s" (id %2$s) not linked to currently active project.';
 $MANTIS_ERROR[ERROR_LDAP_AUTH_FAILED] = 'LDAP Authentication Failed.';
 $MANTIS_ERROR[ERROR_LDAP_SERVER_CONNECT_FAILED] = 'LDAP Server Connection Failed.';
 $MANTIS_ERROR[ERROR_LDAP_UPDATE_FAILED] = 'LDAP Record Update has failed.';
diff --git a/plugins/XmlImportExport/ImportXml.php b/plugins/XmlImportExport/ImportXml.php
index 9b0ebc9..fa25252 100644
--- a/plugins/XmlImportExport/ImportXml.php
+++ b/plugins/XmlImportExport/ImportXml.php
@@ -101,18 +101,38 @@ class ImportXML {
 		}
 
 		echo " Done\n";
-
+		
+		// replace references in bug description and additional information
 		$importedIssues = $this->itemsMap_->getall( 'issue' );
 		printf( "Processing cross-references for %s issues...", count( $importedIssues ) );
 		foreach( $importedIssues as $oldId => $newId ) {
 			$bugData = bug_get( $newId, true );
-
+			
+			$content_replaced = false;
 			$bugLinkRegexp = '/(^|[^\w])(' . preg_quote( $this->source_->issuelink, '/' ) . ')(\d+)\b/e';
-			$replacement = '"\\1" . $this->getReplacementString( "\\2", "\\3" )';
-
-			$bugData->description = preg_replace( $bugLinkRegexp, $replacement, $bugData->description );
-			bug_update( $newId, $bugData, true, true );
+			// replace links in description
+			preg_match_all( $bugLinkRegexp, $bugData->description, $matches );
+			if ( is_array( $matches[3] && count( $matches[3] ) > 0 ) ) {
+				$content_replaced = true;
+				foreach ( $matches[3] as $old_id ) {
+					$bugData->description = str_replace( $this->source_->issuelink . $old_id, $this->getReplacementString( $this->source_->issuelink, $old_id ), $bugData->description);
+				}
+			}
+			// replace links in additional information
+			preg_match_all( $bugLinkRegexp, $bugData->additional_information, $matches );
+			if ( is_array( $matches[3] && count( $matches[3] ) > 0 ) ) {
+				$content_replaced = true;
+				foreach ( $matches[3] as $old_id ) {
+					$bugData->additional_information = str_replace( $this->source_->issuelink . $old_id, $this->getReplacementString( $this->source_->issuelink, $old_id ), $bugData->additional_information);
+				}
+			}
+			if ( $content_replaced ) {
+				// only update bug if necessary (otherwise last update date would be unnecessarily overwritten)
+				$bugData->update( true );
+			}
 		}
+		
+		// @todo: replace references within bugnotes
 		echo " Done\n";
 	}
 
diff --git a/plugins/XmlImportExport/ImportXml/Issue.php b/plugins/XmlImportExport/ImportXml/Issue.php
index ba1220a..23bd7fe 100644
--- a/plugins/XmlImportExport/ImportXml/Issue.php
+++ b/plugins/XmlImportExport/ImportXml/Issue.php
@@ -37,12 +37,12 @@ class ImportXml_Issue implements ImportXml_Interface {
 
 	// Read stream until current item finishes, processing
 	// the data found
-	public function process( XMLreader$reader ) {
+	public function process( XMLreader $reader ) {
 
 		//print "\nImportIssue process()\n";
 		$t_project_id = helper_get_current_project(); // TODO: category_get_id_by_name could work by default on current project
 		$userId = auth_get_current_user_id( );
-
+		
 		$depth = $reader->depth;
 		while( $reader->read() &&
 				($reader->depth > $depth ||
@@ -108,10 +108,91 @@ class ImportXml_Issue implements ImportXml_Interface {
 						break;
 
 					case 'project';
-
-					// ignore original value, use current project
-					$this->newbug_->project_id = $t_project_id;
+						// ignore original value, use current project
+						$this->newbug_->project_id = $t_project_id;
 					break;
+					
+					case 'custom_fields':
+						// store custom fields
+						$i = -1;
+						$depth_cf = $reader->depth;
+						while ( $reader->read() &&
+							  ( $reader->depth > $depth_cf ||
+							   $reader->nodeType != XMLReader::END_ELEMENT ) )
+						{
+							
+							if ( $reader->nodeType == XMLReader::ELEMENT ) {
+								if ($reader->localName == 'custom_field') {
+									$i++;
+								}
+								switch ( $reader->localName ) {
+									default:
+										$field = $reader->localName;
+										$reader->read( );
+										$t_custom_fields[$i]->$field = $reader->value;
+								}
+							}
+						}
+						break;
+						
+					case 'bugnotes':
+						// store bug notes
+						$i = -1;
+						$depth_bn = $reader->depth;
+						while ( $reader->read() &&
+							  ( $reader->depth > $depth_bn ||
+							   $reader->nodeType != XMLReader::END_ELEMENT ) )
+						{
+							
+							if ( $reader->nodeType == XMLReader::ELEMENT ) {
+								if ($reader->localName == 'bugnote') {
+									$i++;
+								}
+								switch ( $reader->localName ) {
+									case 'reporter':
+										$t_old_id = $reader->getAttribute( 'id' );
+										$reader->read( );
+										$t_bugnotes[$i]->reporter_id = $this->get_user_id( $reader->value, $userId );
+										break;
+										
+									case 'view_state':
+										$t_old_id = $reader->getAttribute( 'id' );
+										$reader->read( );
+										$t_bugnotes[$i]->private = $reader->value == VS_PRIVATE ? true : false;
+										break;
+										
+									default:
+										$field = $reader->localName;
+										$reader->read( );
+										$t_bugnotes[$i]->$field = $reader->value;
+								}
+							}
+						}
+						break;
+						
+					case 'attachments':
+						// store attachments
+						$i = -1;
+						$depth_att = $reader->depth;
+						while ( $reader->read() &&
+							  ( $reader->depth > $depth_att ||
+							   $reader->nodeType != XMLReader::END_ELEMENT ) )
+						{
+							
+							if ( $reader->nodeType == XMLReader::ELEMENT ) {
+								if ($reader->localName == 'attachment') {
+									$i++;
+								}
+								switch ( $reader->localName ) {
+									default:
+										$field = $reader->localName;
+										$reader->read( );
+										$t_attachments[$i]->$field = $reader->value;
+								}
+							}
+						}
+						break;
+						
 				default:
 					$field = $reader->localName;
 
@@ -124,11 +205,49 @@ class ImportXml_Issue implements ImportXml_Interface {
 
 		// now save the new bug
 		$this->new_id_ = $this->newbug_->create();
+		
+		// add custom fields
+		if ( $this->new_id_ > 0 && is_array( $t_custom_fields ) && count( $t_custom_fields ) > 0 ) {
+			foreach ( $t_custom_fields as $t_custom_field) {
+				$t_custom_field_id = custom_field_get_id_from_name( $t_custom_field->name );
+				if ( custom_field_ensure_exists( $t_custom_field_id ) && custom_field_is_linked( $t_custom_field_id, $t_project_id ) ) {
+					custom_field_set_value( $t_custom_field->id, $this->new_id_, $t_custom_field->value );
+				}
+				else {
+					error_parameters( $t_custom_field->name, $t_custom_field_id );
+					trigger_error( ERROR_CUSTOM_FIELD_NOT_LINKED_TO_PROJECT, ERROR );
+				}
+			}
+		}
+		
+		// add bugnotes
+		if ( $this->new_id_ > 0 && is_array( $t_bugnotes ) && count( $t_bugnotes ) > 0 ) {
+			foreach ( $t_bugnotes as $t_bugnote) {
+				bugnote_add( $this->new_id_, $t_bugnote->note, $t_bugnote->time_tracking, $t_bugnote->private, $t_bugnote->note_type, $t_bugnote_>note_attr, $t_bugnote->reporter_id, false, $t_bugnote->date_submitted, $t_bugnote->last_modified, true );
+			}
+		}
+		
+		// add attachments
+		if ( $this->new_id_ > 0 && is_array( $t_attachments ) && count( $t_attachments ) > 0 ) {
+			foreach ( $t_attachments as $t_attachment) {
+				// Create a temporary file in the temporary files directory using sys_get_temp_dir()
+				$temp_file_name = tempnam( sys_get_temp_dir(), 'MantisImport' );
+				file_put_contents( $temp_file_name, base64_decode( $t_attachment->content ) );
+				$file_data = array( 'name' 		=> $t_attachment->filename,
+									'type' 		=> $t_attachment->file_type,
+									'tmp_name' 	=> $temp_file_name,
+									'size' 		=> filesize( $temp_file_name ) );
+				// unfortunately we have no clue who has added the attachment (this could only be fetched from history -> feel free to implement this)
+				// also I have no clue where description should come from...
+				file_add( $this->new_id_, $file_data, 'bug', $t_attachment->title, $p_desc = '', $p_user_id = null, $t_attachment->date_added, true );
+				unlink( $temp_file_name );
+			}
+		}
 
 		//echo "\nnew bug: $this->new_id_\n";
 	}
 
-	public function update_map( Mapper$mapper ) {
+	public function update_map( Mapper $mapper ) {
 		$mapper->add( 'issue', $this->old_id_, $this->new_id_ );
 	}
 
@@ -150,10 +269,14 @@ class ImportXml_Issue implements ImportXml_Interface {
 	*/
 	private function get_user_id( $username, $squash_userid = 0 ) {
 		$t_user_id = user_get_id_by_name( $username );
-		if( $t_user_id === false ) {
-
-			//not found
-			$t_user_id = $squash_userid;
+		if ( $t_user_id === false ) {
+			// user not found by username -> check real name
+			// keep in mind that the setting config_get( 'show_realname' ) may differ between import and export system
+			$t_user_id = user_get_id_by_realname( $username );
+			if ( $t_user_id === false ) {
+				//not found
+				$t_user_id = $squash_userid;
+			}
 		}
 		return $t_user_id;
 	}
diff --git a/plugins/XmlImportExport/pages/export.php b/plugins/XmlImportExport/pages/export.php
index 135127e..ab1444c 100644
--- a/plugins/XmlImportExport/pages/export.php
+++ b/plugins/XmlImportExport/pages/export.php
@@ -146,6 +146,93 @@ foreach( $t_result as $t_row ) {
 				$writer->writeElement( $t_element, $t_value );
 		}
 	}
+	
+	# fetch and export custom fields
+	$t_custom_fields = custom_field_get_all_linked_fields( $t_row->id );
+	if ( is_array( $t_custom_fields ) && count( $t_custom_fields ) > 0 ) {
+		$writer->startElement( 'custom_fields' );
+		foreach ( $t_custom_fields as $custom_field_name => $t_custom_field ) {
+			$writer->startElement( 'custom_field' );
+			# id
+			$writer->writeElement( 'id', custom_field_get_id_from_name( $custom_field_name ) );
+			# title
+			$writer->writeElement( 'name', $custom_field_name );
+			# filename
+			$writer->writeElement( 'type', $t_custom_field['type'] );
+			# filesize
+			$writer->writeElement( 'value', $t_custom_field['value'] );
+			# file_type
+			$writer->writeElement( 'access_level_r', $t_custom_field['access_level_r'] );
+			
+			$writer->endElement(); # custom_field
+		}
+		$writer->endElement(); # custom_fields
+	}
+	
+	# fetch and export bugnotes
+	$t_bugnotes = bugnote_get_all_bugnotes( $t_row->id );
+	if ( is_array( $t_bugnotes ) && count( $t_bugnotes ) > 0 ) {
+		$writer->startElement( 'bugnotes' );
+		foreach ( $t_bugnotes as $t_bugnote ) {
+			$writer->startElement( 'bugnote' );
+			# id
+			$writer->writeElement( 'id', $t_bugnote->id );
+			# reporter
+			$writer->startElement( 'reporter' );
+			$writer->writeAttribute( 'id', $t_bugnote->reporter_id );
+			$writer->text( user_get_name( $t_bugnote->reporter_id ) );
+			$writer->endElement( );
+			# bug note
+			$writer->writeElement( 'note', $t_bugnote->note );
+			# view state
+			$writer->startElement( 'view_state' );
+			$writer->writeAttribute( 'id', $t_bugnote->view_state );
+			$writer->text( get_enum_element( 'view_state', $t_bugnote->view_state ) );
+			$writer->endElement( );
+			# date submitted
+			$writer->writeElement( 'date_submitted', $t_bugnote->date_submitted );
+			# last modified
+			$writer->writeElement( 'last_modified', $t_bugnote->last_modified );
+			# note type
+			$writer->writeElement( 'note_type', $t_bugnote->note_type );
+			# note attr
+			$writer->writeElement( 'note_attr', $t_bugnote->note_attr );
+			# time tracking
+			$writer->writeElement( 'time_tracking', $t_bugnote->time_tracking );
+			
+			$writer->endElement(); # bugnote
+		}
+		$writer->endElement(); # bugnotes
+	}
+	
+	# fetch and export attachments
+	$t_attachments = bug_get_attachments( $t_row->id );
+	if ( is_array( $t_attachments ) && count( $t_attachments ) > 0 ) {
+		$writer->startElement( 'attachments' );
+		foreach ( $t_attachments as $t_attachment ) {
+			$writer->startElement( 'attachment' );
+			# id
+			$writer->writeElement( 'id', $t_attachment['id'] );
+			# title
+			$writer->writeElement( 'title', $t_attachment['title'] );
+			# filename
+			$writer->writeElement( 'filename', $t_attachment['filename'] );
+			# filesize
+			$writer->writeElement( 'filesize', $t_attachment['filesize'] );
+			# file_type
+			$writer->writeElement( 'file_type', $t_attachment['file_type'] );
+			# last added
+			$writer->writeElement( 'date_added',  $t_attachment['date_added'] );
+			# content
+			$content = file_get_content( $t_attachment['id'] );
+			
+			$writer->writeElement( 'content',  base64_encode( $content['content'] ) );
+			
+			$writer->endElement(); # attachment
+		}
+		$writer->endElement(); # bugnotes
+	}
+	
 	$writer->endElement(); # issue
 
 	// Save memory by clearing cache
-- 
1.7.0.2.msysgit.0

XmlImportExport.zip (38,898 bytes)
XmlImportExport_1.3.x.patch (24,841 bytes)   
From 515cd551c8a2c3490a77b06b21602e5d4ef9ceec Mon Sep 17 00:00:00 2001
From: Dominik Blunk <dominik@blunk.ch>
Date: Wed, 23 Jun 2010 15:29:37 +0200
Subject: [PATCH] Patch based on master (1.3.x)

---
 core/bug_api.php                            |   14 ++-
 core/bugnote_api.php                        |   15 ++-
 core/constant_inc.php                       |    1 +
 core/file_api.php                           |  115 +++++++++++++++++++++-
 lang/strings_english.txt                    |    1 +
 plugins/XmlImportExport/ImportXml.php       |   31 +++++-
 plugins/XmlImportExport/ImportXml/Issue.php |  140 +++++++++++++++++++++++++--
 plugins/XmlImportExport/pages/export.php    |   87 +++++++++++++++++
 8 files changed, 380 insertions(+), 24 deletions(-)

diff --git a/core/bug_api.php b/core/bug_api.php
index ef12ccf..a8d974b 100644
--- a/core/bug_api.php
+++ b/core/bug_api.php
@@ -108,7 +108,7 @@ class BugData {
 
 	# omitted:
 	# var $bug_text_id
-	protected $profile_id;
+	protected $profile_id = 0;
 
 	# extended info
 	protected $description = '';
@@ -310,8 +310,15 @@ class BugData {
 
 		# check due_date format
 		if( is_blank( $this->due_date ) ) {
-			$this_due_date = date_get_null();
+			$this->due_date = date_get_null();
+		}
+		# check date submitted and last modified
+		if( is_blank( $this->date_submitted ) ) {
+			$this->date_submitted = db_now();
 		}
+		if( is_blank( $this->last_updated ) ) {
+			$this->last_updated = db_now();
+ 		}
 
 		$t_bug_table = db_get_table( 'bug' );
 		$t_bug_text_table = db_get_table( 'bug_text' );
@@ -374,7 +381,8 @@ class BugData {
 					      " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ",
 					      " . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ')';
 
-		db_query_bound( $query, Array( $this->project_id, $this->reporter_id, $this->handler_id, $this->duplicate_id, $this->priority, $this->severity, $this->reproducibility, $t_status, $this->resolution, $this->projection, $this->category_id, db_now(), db_now(), $this->eta, $t_text_id, $this->os, $this->os_build, $this->platform, $this->version, $this->build, $this->profile_id, $this->summary, $this->view_state, $this->sponsorship_total, $this->sticky, $this->fixed_in_version, $this->target_version, $this->due_date ) );
+		db_query_bound( $query, Array( $this->project_id, $this->reporter_id, $this->handler_id, $this->duplicate_id, $this->priority, $this->severity, $this->reproducibility, $t_status, $this->resolution, $this->projection, $this->category_id, $this->date_submitted, $this->last_updated, $this->eta, $t_text_id, $this->os, $this->os_build, $this->platform, $this->version, $this->build, $this->profile_id, $this->summary, $this->view_state, $this->sponsorship_total, $this->sticky, $this->fixed_in_version, $this->target_version, $this->due_date ) );
+		
 
 		$this->id = db_insert_id( $t_bug_table );
 
diff --git a/core/bugnote_api.php b/core/bugnote_api.php
index 88d3987..2b046a7 100644
--- a/core/bugnote_api.php
+++ b/core/bugnote_api.php
@@ -136,14 +136,19 @@ function bugnote_is_user_reporter( $p_bugnote_id, $p_user_id ) {
  * @param string $p_attr
  * @param int $p_user_id user id
  * @param bool $p_send_email generate email?
+ * @param int $p_date_submitted date submitted (defaults to now())
+ * @param int $p_last_modified last modification date (defaults to now())
+ * @param bool $p_skip_bug_update skip bug last modification update (useful when importing bugs/bugnotes)
  * @return false|int false or indicating bugnote id added
  * @access public
  */
-function bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking = '0:00', $p_private = false, $p_type = 0, $p_attr = '', $p_user_id = null, $p_send_email = TRUE ) {
+function bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking = '0:00', $p_private = false, $p_type = 0, $p_attr = '', $p_user_id = null, $p_send_email = TRUE, $p_date_submitted = 0, $p_last_modified = 0, $p_skip_bug_update = FALSE ) {
 	$c_bug_id = db_prepare_int( $p_bug_id );
 	$c_time_tracking = helper_duration_to_minutes( $p_time_tracking );
 	$c_private = db_prepare_bool( $p_private );
 	$c_type = db_prepare_int( $p_type );
+	$c_date_submitted = $p_date_submitted <= 0 ? db_now() : db_prepare_int( $p_date_submitted );
+	$c_last_modified = $p_last_modified <= 0 ? db_now() : db_prepare_int( $p_last_modified );
 
 	$t_bugnote_text_table = db_get_table( 'bugnote_text' );
 	$t_bugnote_table = db_get_table( 'bugnote' );
@@ -181,7 +186,7 @@ function bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking = '0:00', $p_
 	}
 
 	# Check for private bugnotes.
-	if( $p_private && access_has_bug_level( config_get( 'set_view_status_threshold' ), $p_bug_id, $c_user_id ) ) {
+	if( $c_private && access_has_bug_level( config_get( 'set_view_status_threshold' ), $p_bug_id, $c_user_id ) ) {
 		$t_view_state = VS_PRIVATE;
 	} else {
 		$t_view_state = VS_PUBLIC;
@@ -192,13 +197,15 @@ function bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking = '0:00', $p_
 				(bug_id, reporter_id, bugnote_text_id, view_state, date_submitted, last_modified, note_type, note_attr, time_tracking )
 			VALUES
 				(" . db_param() . ', ' . db_param() . ',' . db_param() . ', ' . db_param() . ', ' . db_param() . ',' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ' )';
-	db_query_bound( $query, Array( $c_bug_id, $c_user_id, $t_bugnote_text_id, $t_view_state, db_now(), db_now(), $c_type, $p_attr, $c_time_tracking ) );
+	db_query_bound( $query, Array( $c_bug_id, $c_user_id, $t_bugnote_text_id, $t_view_state, $c_date_submitted, $c_last_modified, $c_type, $p_attr, $c_time_tracking ) );
 
 	# get bugnote id
 	$t_bugnote_id = db_insert_id( $t_bugnote_table );
 
 	# update bug last updated
-	bug_update_date( $p_bug_id );
+	if ( !$p_skip_bug_update ) {
+		bug_update_date( $p_bug_id );
+	}
 
 	# log new bug
 	history_log_event_special( $p_bug_id, BUGNOTE_ADDED, bugnote_format_id( $t_bugnote_id ) );
diff --git a/core/constant_inc.php b/core/constant_inc.php
index a598aa7..e906d2f 100644
--- a/core/constant_inc.php
+++ b/core/constant_inc.php
@@ -302,6 +302,7 @@ define( 'ERROR_CUSTOM_FIELD_NAME_NOT_UNIQUE', 1301 );
 define( 'ERROR_CUSTOM_FIELD_IN_USE', 1302 );
 define( 'ERROR_CUSTOM_FIELD_INVALID_VALUE', 1303 );
 define( 'ERROR_CUSTOM_FIELD_INVALID_DEFINITION', 1304 );
+define( 'ERROR_CUSTOM_FIELD_NOT_LINKED_TO_PROJECT', 1305 );
 
 # ERROR_LDAP_*
 define( 'ERROR_LDAP_AUTH_FAILED', 1400 );
diff --git a/core/file_api.php b/core/file_api.php
index ca89e95..ed4a79d 100644
--- a/core/file_api.php
+++ b/core/file_api.php
@@ -628,12 +628,19 @@ function file_is_name_unique( $p_name, $p_bug_id ) {
  *
  * @param integer $p_bug_id the bug id
  * @param array $p_file the uploaded file info, as retrieved from gpc_get_file()
+ * @param string $p_table table ('bug' or 'doc')
+ * @param string $p_title file title
+ * @param string $p_desc file description
+ * @param int $p_user_id user id
+ * @param int $p_date_added date added
+ * @param bool $p_skip_bug_update skip bug last modification update (useful when importing bug attachments)
  */
-function file_add( $p_bug_id, $p_file, $p_table = 'bug', $p_title = '', $p_desc = '', $p_user_id = null ) {
+function file_add( $p_bug_id, $p_file, $p_table = 'bug', $p_title = '', $p_desc = '', $p_user_id = null, $p_date_added = 0, $p_skip_bug_update = false ) {
 
 	file_ensure_uploaded( $p_file );
 	$t_file_name = $p_file['name'];
 	$t_tmp_file = $p_file['tmp_name'];
+	$c_date_added = $p_date_added <= 0 ? db_now() : db_prepare_int( $p_date_added );
 
 	if( !file_type_check( $t_file_name ) ) {
 		trigger_error( ERROR_FILE_NOT_ALLOWED, ERROR );
@@ -728,13 +735,15 @@ function file_add( $p_bug_id, $p_file, $p_table = 'bug', $p_title = '', $p_desc
 	$query = "INSERT INTO $t_file_table
 						(" . $p_table . "_id, title, description, diskfile, filename, folder, filesize, file_type, date_added, content, user_id)
 					  VALUES
-						($c_id, '$c_title', '$c_desc', '$c_unique_name', '$c_new_file_name', '$c_file_path', $c_file_size, '$c_file_type', '" . db_now() . "', $c_content, $c_user_id)";
+						($c_id, '$c_title', '$c_desc', '$c_unique_name', '$c_new_file_name', '$c_file_path', $c_file_size, '$c_file_type', '" . $c_date_added . "', $c_content, $c_user_id)";
 	db_query( $query );
 
 	if( 'bug' == $p_table ) {
 
 		# updated the last_updated date
-		$result = bug_update_date( $p_bug_id );
+		if ( !$p_skip_bug_update ) {
+			$result = bug_update_date( $p_bug_id );
+		}
 
 		# log new bug
 		history_log_event_special( $p_bug_id, FILE_ADDED, $t_file_name );
@@ -868,3 +877,103 @@ function file_get_extension( $p_filename ) {
 	}
 	return $t_extension;
 }
+
+/**
+ * Get file content
+ *
+ * @param int $p_file_id file id
+ * @param string $p_type file type
+ * @return array file type and content
+ */
+function file_get_content( $p_file_id, $p_type = 'bug' ) {
+	# we handle the case where the file is attached to a bug
+	# or attached to a project as a project doc.
+	$query = '';
+	switch ( $p_type ) {
+		case 'bug':
+			$t_bug_file_table = db_get_table( 'mantis_bug_file_table' );
+			$query = "SELECT *
+				FROM $t_bug_file_table
+				WHERE id=" . db_param();
+			break;
+		case 'doc':
+			$t_project_file_table = db_get_table( 'mantis_project_file_table' );
+			$query = "SELECT *
+				FROM $t_project_file_table
+				WHERE id=" . db_param();
+			break;
+		default:
+			return false;
+	}
+	$result = db_query_bound( $query, Array( $p_file_id ) );
+	$row = db_fetch_array( $result );
+	
+	if ( $f_type == 'bug' ) {
+		$t_project_id = bug_get_field( $row['bug_id'], 'project_id' );
+	} else {
+		$t_project_id = $row['bug_id'];
+	}
+	
+	# If finfo is available (always true for PHP >= 5.3.0) we can use it to determine the MIME type of files
+	$finfo_available = false;
+	if ( class_exists( 'finfo' ) ) {
+		$t_info_file = config_get( 'fileinfo_magic_db_file' );
+
+		if ( is_blank( $t_info_file ) ) {
+			$finfo = new finfo( FILEINFO_MIME );
+		} else {
+			$finfo = new finfo( FILEINFO_MIME, $t_info_file );
+		}
+
+		if ( $finfo ) {
+			$finfo_available = true;
+		}
+	}
+
+	$t_content_type = $row['file_type'];
+	
+	# dump file content to the connection.
+	switch ( config_get( 'file_upload_method' ) ) {
+		case DISK:
+			$t_local_disk_file = file_normalize_attachment_path( $row['diskfile'], $t_project_id );
+
+			if ( file_exists( $t_local_disk_file ) ) {
+				if ( $finfo_available ) {
+					$t_file_info_type = $finfo->file( $t_local_disk_file );
+
+					if ( $t_file_info_type !== false ) {
+						$t_content_type = $t_file_info_type;
+					}
+				}
+				return array( 'type' => $t_content_type, 'content' => file_get_contents( $t_local_disk_file ) );
+			}
+			break;
+		case FTP:
+			$t_local_disk_file = file_normalize_attachment_path( $row['diskfile'], $t_project_id );
+
+			if ( !file_exists( $t_local_disk_file ) ) {
+				$ftp = file_ftp_connect();
+				file_ftp_get ( $ftp, $t_local_disk_file, $row['diskfile'] );
+				file_ftp_disconnect( $ftp );
+			}
+
+			if ( $finfo_available ) {
+				$t_file_info_type = $finfo->file( $t_local_disk_file );
+
+				if ( $t_file_info_type !== false ) {
+					$t_content_type = $t_file_info_type;
+				}
+			}
+			return array( 'type' => $t_content_type, 'content' => file_get_contents( $t_local_disk_file ) );
+			break;
+		default:
+			if ( $finfo_available ) {
+				$t_file_info_type = $finfo->buffer( $row['content'] );
+
+				if ( $t_file_info_type !== false ) {
+					$t_content_type = $t_file_info_type;
+				}
+			}
+			return array( 'type' => $t_content_type, 'content' => $row['content'] );
+	}
+}
diff --git a/lang/strings_english.txt b/lang/strings_english.txt
index 038c178..f4bb7a0 100644
--- a/lang/strings_english.txt
+++ b/lang/strings_english.txt
@@ -238,6 +238,7 @@ $MANTIS_ERROR[ERROR_CUSTOM_FIELD_NAME_NOT_UNIQUE] = 'This is a duplicate name.';
 $MANTIS_ERROR[ERROR_CUSTOM_FIELD_IN_USE] = 'At least one project still uses this field.';
 $MANTIS_ERROR[ERROR_CUSTOM_FIELD_INVALID_VALUE] = 'Invalid value for field "%1$s".';
 $MANTIS_ERROR[ERROR_CUSTOM_FIELD_INVALID_DEFINITION] = 'Invalid custom field definition.';
+$MANTIS_ERROR[ERROR_CUSTOM_FIELD_NOT_LINKED_TO_PROJECT] = 'Custom field "%1$s" (id %2$s) not linked to currently active project.';
 $MANTIS_ERROR[ERROR_LDAP_AUTH_FAILED] = 'LDAP Authentication Failed.';
 $MANTIS_ERROR[ERROR_LDAP_SERVER_CONNECT_FAILED] = 'LDAP Server Connection Failed.';
 $MANTIS_ERROR[ERROR_LDAP_UPDATE_FAILED] = 'LDAP Record Update has failed.';
diff --git a/plugins/XmlImportExport/ImportXml.php b/plugins/XmlImportExport/ImportXml.php
index 203d35e..83f974d 100644
--- a/plugins/XmlImportExport/ImportXml.php
+++ b/plugins/XmlImportExport/ImportXml.php
@@ -102,23 +102,42 @@ class ImportXML {
 
 		echo " Done\n";
 
+		// replace references in bug description and additional information
 		$importedIssues = $this->itemsMap_->getall( 'issue' );
 		printf( "Processing cross-references for %s issues...", count( $importedIssues ) );
 		foreach( $importedIssues as $oldId => $newId ) {
 			$bugData = bug_get( $newId, true );
 
 			$bugLinkRegexp = '/(^|[^\w])(' . preg_quote( $this->source_->issuelink, '/' ) . ')(\d+)\b/e';
-			$replacement = '"\\1" . $this->getReplacementString( "\\2", "\\3" )';
-
-			$bugData->description = preg_replace( $bugLinkRegexp, $replacement, $bugData->description );
-			bug_update( $newId, $bugData, true, true );
-		}
+			// replace links in description
+			preg_match_all( $bugLinkRegexp, $bugData->description, $matches );
+			if ( is_array( $matches[3] && count( $matches[3] ) > 0 ) ) {
+				$content_replaced = true;
+				foreach ( $matches[3] as $old_id ) {
+					$bugData->description = str_replace( $this->source_->issuelink . $old_id, $this->getReplacementString( $this->source_->issuelink, $old_id ), $bugData->description);
+				}
+			}
+			// replace links in additional information
+			preg_match_all( $bugLinkRegexp, $bugData->additional_information, $matches );
+			if ( is_array( $matches[3] && count( $matches[3] ) > 0 ) ) {
+				$content_replaced = true;
+				foreach ( $matches[3] as $old_id ) {
+					$bugData->additional_information = str_replace( $this->source_->issuelink . $old_id, $this->getReplacementString( $this->source_->issuelink, $old_id ), $bugData->additional_information);
+				}
+			}
+			if ( $content_replaced ) {
+				// only update bug if necessary (otherwise last update date would be unnecessarily overwritten)
+				$bugData->update( true );
+			}
+ 		}
+		
+		// @todo: replace references within bugnotes
 		echo " Done\n";
 	}
 
 	/**
 	 * Compute and return the new link
- *
+	 *
 	 */
 	private function getReplacementString( $oldLinkTag, $oldId ) {
 		$linkTag = config_get( 'bug_link_tag' );
diff --git a/plugins/XmlImportExport/ImportXml/Issue.php b/plugins/XmlImportExport/ImportXml/Issue.php
index 78541ce..20d5a7e 100644
--- a/plugins/XmlImportExport/ImportXml/Issue.php
+++ b/plugins/XmlImportExport/ImportXml/Issue.php
@@ -37,7 +37,7 @@ class ImportXml_Issue implements ImportXml_Interface {
 
 	// Read stream until current item finishes, processing
 	// the data found
-	public function process( XMLreader$reader ) {
+	public function process( XMLreader $reader ) {
 
 		//print "\nImportIssue process()\n";
 		$t_project_id = helper_get_current_project(); // TODO: category_get_id_by_name could work by default on current project
@@ -109,9 +109,91 @@ class ImportXml_Issue implements ImportXml_Interface {
 
 					case 'project';
 
-					// ignore original value, use current project
-					$this->newbug_->project_id = $t_project_id;
-					break;
+						// ignore original value, use current project
+						$this->newbug_->project_id = $t_project_id;
+						break;
+					
+					case 'custom_fields':
+						// store custom fields
+						$i = -1;
+						$depth_cf = $reader->depth;
+						while ( $reader->read() &&
+							  ( $reader->depth > $depth_cf ||
+							   $reader->nodeType != XMLReader::END_ELEMENT ) )
+						{
+							
+							if ( $reader->nodeType == XMLReader::ELEMENT ) {
+								if ($reader->localName == 'custom_field') {
+									$i++;
+								}
+								switch ( $reader->localName ) {
+									default:
+										$field = $reader->localName;
+										$reader->read( );
+										$t_custom_fields[$i]->$field = $reader->value;
+								}
+							}
+						}
+						break;
+						
+					case 'bugnotes':
+						// store bug notes
+						$i = -1;
+						$depth_bn = $reader->depth;
+						while ( $reader->read() &&
+							  ( $reader->depth > $depth_bn ||
+							   $reader->nodeType != XMLReader::END_ELEMENT ) )
+						{
+							
+							if ( $reader->nodeType == XMLReader::ELEMENT ) {
+								if ($reader->localName == 'bugnote') {
+									$i++;
+								}
+								switch ( $reader->localName ) {
+									case 'reporter':
+										$t_old_id = $reader->getAttribute( 'id' );
+										$reader->read( );
+										$t_bugnotes[$i]->reporter_id = $this->get_user_id( $reader->value, $userId );
+										break;
+										
+									case 'view_state':
+										$t_old_id = $reader->getAttribute( 'id' );
+										$reader->read( );
+										$t_bugnotes[$i]->private = $reader->value == VS_PRIVATE ? true : false;
+										break;
+										
+									default:
+										$field = $reader->localName;
+										$reader->read( );
+										$t_bugnotes[$i]->$field = $reader->value;
+								}
+							}
+						}
+						break;
+						
+					case 'attachments':
+						// store attachments
+						$i = -1;
+						$depth_att = $reader->depth;
+						while ( $reader->read() &&
+							  ( $reader->depth > $depth_att ||
+							   $reader->nodeType != XMLReader::END_ELEMENT ) )
+						{
+							
+							if ( $reader->nodeType == XMLReader::ELEMENT ) {
+								if ($reader->localName == 'attachment') {
+									$i++;
+								}
+								switch ( $reader->localName ) {
+									default:
+										$field = $reader->localName;
+										$reader->read( );
+										$t_attachments[$i]->$field = $reader->value;
+								}
+							}
+						}
+						break;
+						
 				default:
 					$field = $reader->localName;
 
@@ -124,11 +206,49 @@ class ImportXml_Issue implements ImportXml_Interface {
 
 		// now save the new bug
 		$this->new_id_ = $this->newbug_->create();
+		
+		// add custom fields
+		if ( $this->new_id_ > 0 && is_array( $t_custom_fields ) && count( $t_custom_fields ) > 0 ) {
+			foreach ( $t_custom_fields as $t_custom_field) {
+				$t_custom_field_id = custom_field_get_id_from_name( $t_custom_field->name );
+				if ( custom_field_ensure_exists( $t_custom_field_id ) && custom_field_is_linked( $t_custom_field_id, $t_project_id ) ) {
+					custom_field_set_value( $t_custom_field->id, $this->new_id_, $t_custom_field->value );
+				}
+				else {
+					error_parameters( $t_custom_field->name, $t_custom_field_id );
+					trigger_error( ERROR_CUSTOM_FIELD_NOT_LINKED_TO_PROJECT, ERROR );
+				}
+			}
+		}
+		
+		// add bugnotes
+		if ( $this->new_id_ > 0 && is_array( $t_bugnotes ) && count( $t_bugnotes ) > 0 ) {
+			foreach ( $t_bugnotes as $t_bugnote) {
+				bugnote_add( $this->new_id_, $t_bugnote->note, $t_bugnote->time_tracking, $t_bugnote->private, $t_bugnote->note_type, $t_bugnote_>note_attr, $t_bugnote->reporter_id, false, $t_bugnote->date_submitted, $t_bugnote->last_modified, true );
+			}
+		}
+		
+		// add attachments
+		if ( $this->new_id_ > 0 && is_array( $t_attachments ) && count( $t_attachments ) > 0 ) {
+			foreach ( $t_attachments as $t_attachment) {
+				// Create a temporary file in the temporary files directory using sys_get_temp_dir()
+				$temp_file_name = tempnam( sys_get_temp_dir(), 'MantisImport' );
+				file_put_contents( $temp_file_name, base64_decode( $t_attachment->content ) );
+				$file_data = array( 'name' 		=> $t_attachment->filename,
+									'type' 		=> $t_attachment->file_type,
+									'tmp_name' 	=> $temp_file_name,
+									'size' 		=> filesize( $temp_file_name ) );
+				// unfortunately we have no clue who has added the attachment (this could only be fetched from history -> feel free to implement this)
+				// also I have no clue where description should come from...
+				file_add( $this->new_id_, $file_data, 'bug', $t_attachment->title, $p_desc = '', $p_user_id = null, $t_attachment->date_added, true );
+				unlink( $temp_file_name );
+			}
+		}
 
 		//echo "\nnew bug: $this->new_id_\n";
 	}
 
-	public function update_map( Mapper$mapper ) {
+	public function update_map( Mapper $mapper ) {
 		$mapper->add( 'issue', $this->old_id_, $this->new_id_ );
 	}
 
@@ -151,9 +271,13 @@ class ImportXml_Issue implements ImportXml_Interface {
 	private function get_user_id( $username, $squash_userid = 0 ) {
 		$t_user_id = user_get_id_by_name( $username );
 		if( $t_user_id === false ) {
-
-			//not found
-			$t_user_id = $squash_userid;
+			// user not found by username -> check real name
+			// keep in mind that the setting config_get( 'show_realname' ) may differ between import and export system!
+			$t_user_id = user_get_id_by_realname( $username );
+			if ( $t_user_id === false ) {
+				//not found
+				$t_user_id = $squash_userid;
+			}
 		}
 		return $t_user_id;
 	}
diff --git a/plugins/XmlImportExport/pages/export.php b/plugins/XmlImportExport/pages/export.php
index e9a2926..0eb024e 100644
--- a/plugins/XmlImportExport/pages/export.php
+++ b/plugins/XmlImportExport/pages/export.php
@@ -146,6 +146,93 @@ foreach( $t_result as $t_row ) {
 				$writer->writeElement( $t_element, $t_value );
 		}
 	}
+	
+	# fetch and export custom fields
+	$t_custom_fields = custom_field_get_all_linked_fields( $t_row->id );
+	if ( is_array( $t_custom_fields ) && count( $t_custom_fields ) > 0 ) {
+		$writer->startElement( 'custom_fields' );
+		foreach ( $t_custom_fields as $custom_field_name => $t_custom_field ) {
+			$writer->startElement( 'custom_field' );
+			# id
+			$writer->writeElement( 'id', custom_field_get_id_from_name( $custom_field_name ) );
+			# title
+			$writer->writeElement( 'name', $custom_field_name );
+			# filename
+			$writer->writeElement( 'type', $t_custom_field['type'] );
+			# filesize
+			$writer->writeElement( 'value', $t_custom_field['value'] );
+			# file_type
+			$writer->writeElement( 'access_level_r', $t_custom_field['access_level_r'] );
+			
+			$writer->endElement(); # custom_field
+		}
+		$writer->endElement(); # custom_fields
+	}
+	
+	# fetch and export bugnotes
+	$t_bugnotes = bugnote_get_all_bugnotes( $t_row->id );
+	if ( is_array( $t_bugnotes ) && count( $t_bugnotes ) > 0 ) {
+		$writer->startElement( 'bugnotes' );
+		foreach ( $t_bugnotes as $t_bugnote ) {
+			$writer->startElement( 'bugnote' );
+			# id
+			$writer->writeElement( 'id', $t_bugnote->id );
+			# reporter
+			$writer->startElement( 'reporter' );
+			$writer->writeAttribute( 'id', $t_bugnote->reporter_id );
+			$writer->text( user_get_name( $t_bugnote->reporter_id ) );
+			$writer->endElement( );
+			# bug note
+			$writer->writeElement( 'note', $t_bugnote->note );
+			# view state
+			$writer->startElement( 'view_state' );
+			$writer->writeAttribute( 'id', $t_bugnote->view_state );
+			$writer->text( get_enum_element( 'view_state', $t_bugnote->view_state ) );
+			$writer->endElement( );
+			# date submitted
+			$writer->writeElement( 'date_submitted', $t_bugnote->date_submitted );
+			# last modified
+			$writer->writeElement( 'last_modified', $t_bugnote->last_modified );
+			# note type
+			$writer->writeElement( 'note_type', $t_bugnote->note_type );
+			# note attr
+			$writer->writeElement( 'note_attr', $t_bugnote->note_attr );
+			# time tracking
+			$writer->writeElement( 'time_tracking', $t_bugnote->time_tracking );
+			
+			$writer->endElement(); # bugnote
+		}
+		$writer->endElement(); # bugnotes
+	}
+	
+	# fetch and export attachments
+	$t_attachments = bug_get_attachments( $t_row->id );
+	if ( is_array( $t_attachments ) && count( $t_attachments ) > 0 ) {
+		$writer->startElement( 'attachments' );
+		foreach ( $t_attachments as $t_attachment ) {
+			$writer->startElement( 'attachment' );
+			# id
+			$writer->writeElement( 'id', $t_attachment['id'] );
+			# title
+			$writer->writeElement( 'title', $t_attachment['title'] );
+			# filename
+			$writer->writeElement( 'filename', $t_attachment['filename'] );
+			# filesize
+			$writer->writeElement( 'filesize', $t_attachment['filesize'] );
+			# file_type
+			$writer->writeElement( 'file_type', $t_attachment['file_type'] );
+			# last added
+			$writer->writeElement( 'date_added',  $t_attachment['date_added'] );
+			# content
+			$content = file_get_content( $t_attachment['id'] );
+			
+			$writer->writeElement( 'content',  base64_encode( $content['content'] ) );
+			
+			$writer->endElement(); # attachment
+		}
+		$writer->endElement(); # bugnotes
+	}
+	
 	$writer->endElement(); # issue
 
 	// Save memory by clearing cache
-- 
1.7.0.2.msysgit.0

XmlImportExport_1.3.x.patch (24,841 bytes)   
XmlImportExport_v2.zip (88,037 bytes)

Relationships

related to 0015721 closedgrangeway Functionality to consider porting to master-2.0.x 
has duplicate 0012316 closeddhx XMLImport plug-in patch to allow custom_fields 
has duplicate 0011876 closedatrol XML Import/Export doesn't support custom fields. 
has duplicate 0014694 closeddregad Cannot import xml/csv files 
related to 0012091 closedsiebrand Bugnotes not in CSV Export anymore 
related to 0015739 feedback Improvements for plugin ImportExportXml to support import from Bugzilla 
related to 0016117 acknowledged XML Import never update my issues but create new one 
related to 0017075 closedvboctor Import plugins should be able to set last_updated field to a date in the past 
related to 0017725 closeddregad CVE-2014-7146 : PHP Code Injection Vulnerability in XmlImportExport plugin 
related to 0017774 closeddregad XML import plugin does not process links 
related to 0017775 closeddregad XML import plugin does not replace links in 'steps to reproduce' 

Activities

dmagalen

dmagalen

2010-06-22 09:02

reporter   ~0025946

Please forgive my ignorance. I have not patched Mantis before.

So I just need to drop the XMLImportExport files into the plugins folder.

What do i need to run to get the patch applied? I am running MAMP on my test system and will be running this off a Mac server in production.

dmagalen

dmagalen

2010-06-22 13:19

reporter   ~0025949

Well I ran the patch on the files and it seemed to change them properly. But then I tried to export or view the CSV file and there is no change to them. What else do I need to do? I didn't see any additional column options in the config pages.

Thanks for your help.

dominik

dominik

2010-06-22 18:04

reporter   ~0025950

Last edited: 2010-06-22 18:16

Sorry maybe I didnt point out this one properly: my patch does only affect the xml export/import. I used that one to move issues between two Mantis instances and that worked quite good...

dhx

dhx

2010-06-22 22:44

reporter   ~0025952

Thanks for the patchset, it looks good.

I was going to commit it to the 1.3.x branch however I came across some merge conflicts.

Plugin ImportXml enhanced with bugnotes, custom fields and attachments
error: patch failed: core/bug_api.php:302
error: core/bug_api.php: patch does not apply
error: patch failed: core/bugnote_api.php:120
error: core/bugnote_api.php: patch does not apply
Patch failed at 0002 Plugin ImportXml enhanced with bugnotes, custom fields and attachments

Is there any chance you could rebase your patchset against the latest 1.3.x head? If not I'll try and do it myself later when I have some free time.

Thanks again for your help :)

dominik

dominik

2010-06-23 09:33

reporter   ~0025965

Uploaded patch for 1.3.x (actually I couldn't find any master-1.3.x do I assumed that "master" is master-1.3.x).

I have not yet a test environment with Mantis 1.3.x available so this patch is NOT tested.

dhx

dhx

2010-08-10 10:12

reporter   ~0026265

Great patch Dominik!

I apologise for the delay in responding to your latest patch.

Your patch has been committed to the 1.3.x branch ready for the next major release. The only changes made were minor formatting fixes (hanging white space).

Thank you for the contribution.

pyro

pyro

2011-02-23 08:40

reporter   ~0028287

Last edited: 2011-02-23 08:42

Hi, I've installed mantis 1.2.4. I applied the patch 2010-06-09_improvements_for_plugin_ImportExportXml.patch, but nothing change. Whe I export an issue or a set of issues doesn't export the bug notes. I'm missing something?

schoppi71

schoppi71

2011-03-09 03:27

reporter   ~0028391

There is a Bug in the Patch within this line:

case 'view_state':
$t_old_id = $reader->getAttribute( 'id' );
$reader->read( );
--> $t_bugnotes[$i]->private = $reader->value == VS_PRIVATE ? true : false;
break;

Correct:
$t_bugnotes[$i]->private = $t_old_id == VS_PRIVATE ? true : false;

ninguno

ninguno

2011-05-26 22:14

reporter   ~0028855

Thanks dominik, the patch works great in my 1.3.x! I have to make some modifications though. I explain them here cause I don't know how to make a patch.

First in core/file_api.php, function file_get_content, I had to change line
$t_bug_file_table = db_get_table( 'mantis_bug_file_table' );
with
$t_bug_file_table = db_get_table( 'bug_file' );

and line
$t_project_file_table = db_get_table( 'mantis_project_file_table' );
with
$t_project_file_table = db_get_table( 'project_file' );

(Both of them removing mantis_ prefix and _table suffix in both of them).

I also had to change ImportXml/Issue.php cause it was not finding the core api files
So I change this lines in the top
require_once( 'bug_api.php' );
require_once( 'user_api.php' );
with
$t_core_path = config_get( 'core_path' );
require_once( $t_core_path.'bug_api.php' );
require_once( $t_core_path.'user_api.php' );

By the way I upload the plugin modified with these modifications, it also includes schoppi71 comment (before mine, about private status i think), and of course with dominik patch for 1.3.x applied. Hope it's useful!

MPA

MPA

2011-08-18 04:13

reporter   ~0029509

Who is able to adjust the file "print_all_bug_page_excel.php" from a a previous version (1.1.x) to make it work with the current version 1.2.x?

It seems that many people are missing the feature to export bugnotes to CSV or Excel. And this seems (to me) the easiest way.

amby-adm

amby-adm

2012-09-17 05:50

reporter   ~0032853

Hi the support for custom fields that you added, do we already need to have the particular custom fields in the project that we are importing the file into, or it will create automatically?
I tried both ways, but its not working for me. please help.

grangeway

grangeway

2013-04-05 17:57

reporter   ~0036237

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

dregad

dregad

2013-06-27 14:30

developer   ~0037326

Reopening due to the fact that some additional fixes were submitted after dhx committed dominik's original patch.

kydow

kydow

2013-06-28 05:01

reporter   ~0037329

Last edited: 2013-06-28 05:08

Hello,
I've installed mantis 1.3.0dev. I applied the patch XmlImportExport_1.3.x.patch, but i have a problem whith updating an existing issue. When I export an issue (XML) or a set of issues and i re-import them, New issues are created and not updatde like expected. I'm missing something?

(I have no problem with the bug_notes, custum_field, date_submitted thanks to the patch)

I would like to Import with an XML file a new issue (create) when the <id> is set to 0 and update an issue with an specific <id>.

I saw that the function update() exist in the file : mantis/www/core/bug_api.php
but it's doesn't call in in the file : mantis/www/plugins/XmlImportExport/ImportXml/Issue.php

jpointe

jpointe

2013-10-02 12:25

reporter   ~0038183

Hello,

How could i apply this patch to the last release (1.2.15) ?

It seems that the 2 patchs attached does not work for 1.2.15

Do I have to generate myself a dedicated patch with a diff between 1.2.15 source files and last ImportExportXML_V2.zip attached ?

Thanks in advance.

dregad

dregad

2013-10-03 08:18

developer   ~0038186

You're welcome to try (at your own risk), but I don't recommend backporting this patch due to the huge differences between the master and 1.2.x branches on some of the affected files (particularly bug_update.php).

jpointe

jpointe

2013-10-07 03:34

reporter   ~0038216

Thanks Degrad.

We made a patch based on difference between first patch published (without bug_update) and last release (1.2.15).
We made the patch manually.
That seems to work without side effects on core functions.
Only relationships are missed while exporting.

Kulssaka

Kulssaka

2014-10-09 05:20

reporter   ~0041473

Last edited: 2014-10-09 05:55

Hello,

I am currently using the 1.2.17 version, and xml export is crashing when there is an attachment file.

I updated the XmlImportExport with this file:
XmlImportExport.zip [^] (38,898 bytes) 2010-06-09 02:49

I can see the bugnotes, this feature is working properly. But when there is a file attachment the following error occur:

http://pastebin.com/4WyiAfX0

Thanks for helping me :)

PS: For the moment I commented these lines so it is not crashing:

#$content = file_get_content( $t_attachment['id'] );

#$writer->writeElement( 'content', base64_encode( $content['content'] ) );

atrol

atrol

2014-10-09 06:14

developer   ~0041476

Kulssaka,

the issues will be fixed in version 1.3 and there is no plan to implement the changes in 1.2.
Patching 1.2 is at your own risk and is not supported, see 0012013:0038186

Please use the forums, the mantisbt-help mailing list or IRC to discuss such kind of questions (refer to http://www.mantisbt.org/support.php for links and further details).

dregad

dregad

2014-10-09 07:54

developer   ~0041478

As atrol said, you're patching 1.2 at your own risk, not to mention that said patch was not developed by us... I suggest you try and contact the original author for further assistance.

That said, there is a standard php function called file_get_contents() (with an 's'), so it could just be a typo.

Kulssaka

Kulssaka

2014-10-09 08:08

reporter   ~0041481

Thanks for the advice.

I will just disable the file attachment into the xml for now, and investigate further about it. I never did php before so I guess i should have a look at the documentation about function file_get_contents().

Related Changesets

MantisBT: master 84017535

2010-06-23 09:29

dominik

Committer: dhx


Details Diff
Issue 0012013: Improved ImportExportXml plugin

Improvements made to the importing and exporting features of this
plugin:
* Added support for custom fields, bugnotes and attachments
* Added support for dates (date submitted, last updated) - keep dates as
given in import file
* Added function to easily retrieve the contents of a file
(file_api.php)

Signed-off-by: David Hicks <hickseydr@optusnet.com.au>
Affected Issues
0012013
mod - core/bugnote_api.php Diff File
mod - core/bug_api.php Diff File
mod - plugins/XmlImportExport/pages/export.php Diff File
mod - plugins/XmlImportExport/ImportXml.php Diff File
mod - core/file_api.php Diff File
mod - plugins/XmlImportExport/ImportXml/Issue.php Diff File
mod - bug_update.php Diff File
mod - core/constant_inc.php Diff File
mod - lang/strings_english.txt Diff File

MantisBT: master de607de3

2013-10-27 15:59

dregad


Details Diff
XML import/export plugin fixes

This resolves a number of warnings and notices with the plugin when
strict error reporting.

As far as I can tell, the fixes mentioned in issue 0012013 after dhx
committed the changes (84017535f8718685d755d58af7a39d80f52ffca8), i.e.
bugnotes are not needed:
- 0012013:0028391 by schoppi71: don't think this is a bug
- 0012013:0028855 by ninguno: already included in dhx's commit
Affected Issues
0012013
mod - core/file_api.php Diff File
mod - plugins/XmlImportExport/ImportXml.php Diff File
mod - plugins/XmlImportExport/ImportXml/Issue.php Diff File
mod - plugins/XmlImportExport/lang/strings_english.txt Diff File
mod - plugins/XmlImportExport/pages/import.php Diff File
mod - plugins/XmlImportExport/pages/import_action.php Diff File