1<?php
2/**
3 * MantisBT - A PHP based bugtracking system
4 *
5 * MantisBT is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * MantisBT is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with MantisBT.  If not, see <http://www.gnu.org/licenses/>.
17 *
18 * @copyright Copyright 2002  MantisBT Team - mantisbt-dev@lists.sourceforge.net
19 */
20
21/**
22 * Import XML issue class
23 */
24
25
26require_api( 'bug_api.php' );
27require_api( 'user_api.php' );
28require_once( 'Interface.php' );
29
30/**
31 * Import XML issue class
32 */
33class ImportXml_Issue implements ImportXml_Interface {
34	/**
35	 * old issue id
36	 */
37	private $old_id_;
38	/**
39	 * new issue id
40	 */
41	private $new_id_;
42
43	/**
44	 * new bug object
45	 */
46	private $newbug_;
47
48	/**
49	 * keep existing category
50	 * @var bool
51	 */
52	private $keepCategory_;
53	/**
54	 * default category
55	 * @var int
56	 */
57	private $defaultCategory_;
58
59	/**
60	 * Default Constructor
61	 * @param boolean $p_keep_category    Whether to keep existing category.
62	 * @param integer $p_default_category Identifier of default category.
63	 */
64	public function __construct( $p_keep_category, $p_default_category ) {
65		$this->newbug_ = new BugData;
66		$this->keepCategory_ = $p_keep_category;
67		$this->defaultCategory_ = $p_default_category;
68	}
69
70	/**
71	 * Read stream until current item finishes, processing the data found
72	 * @param XMLreader $t_reader XMLReader being processed.
73	 * @return void
74	 */
75	public function process( XMLreader $t_reader ) {
76		# print "\nImportIssue process()\n";
77		$t_project_id = helper_get_current_project(); # TODO: category_get_id_by_name could work by default on current project
78		$t_user_id = auth_get_current_user_id( );
79
80		$t_custom_fields = array();
81		$t_bugnotes = array();
82		$t_attachments = array();
83
84		$t_depth = $t_reader->depth;
85		while( $t_reader->read() &&
86				($t_reader->depth > $t_depth ||
87				 $t_reader->nodeType != XMLReader::END_ELEMENT)) {
88			if( $t_reader->nodeType == XMLReader::ELEMENT ) {
89				switch( $t_reader->localName ) {
90					case 'reporter':
91						$t_old_id = $t_reader->getAttribute( 'id' );
92						$t_reader->read( );
93						$this->newbug_->reporter_id = $this->get_user_id( $t_reader->value, $t_user_id );
94
95						# echo "reporter: old id = $t_old_id - new id = {$this->newbug_->reporter_id}\n";
96						break;
97
98					case 'handler':
99						$t_old_id = $t_reader->getAttribute( 'id' );
100						$t_reader->read( );
101						$this->newbug_->handler_id = $this->get_user_id( $t_reader->value, $t_user_id );
102
103						# echo "handler: old id = $t_old_id - new id = {$this->newbug_->handler_id}\n";
104						break;
105
106					case 'category':
107						$this->newbug_->category_id = $this->defaultCategory_;
108
109						if( version_compare( MANTIS_VERSION, '1.2', '>' ) === true ) {
110							$t_reader->read( );
111
112							if( $this->keepCategory_ ) {
113								# Check for the category's existence in the current project
114								# well as its parents (if any)
115								$t_projects_hierarchy = project_hierarchy_inheritance( $t_project_id );
116								foreach( $t_projects_hierarchy as $t_project ) {
117									$t_category_id = category_get_id_by_name( $t_reader->value, $t_project, false );
118									if( $t_category_id !== false ) {
119										$this->newbug_->category_id = $t_category_id;
120										break;
121									}
122								}
123							}
124
125							# echo "new id = {$this->newbug_->category_id}\n";
126						}
127						break;
128
129					case 'eta':
130					case 'priority':
131					case 'projection':
132					case 'reproducibility':
133					case 'resolution':
134					case 'severity':
135					case 'status':
136					case 'view_state':
137						$t_field = $t_reader->localName;
138						$t_id = $t_reader->getAttribute( 'id' );
139						$t_reader->read( );
140						$t_value = $t_reader->value;
141
142						# Here we assume ids have the same meaning in both installations
143						# TODO add a check for customized values
144						$this->newbug_->$t_field = $t_id;
145						break;
146
147					case 'id':
148						$t_reader->read( );
149						$this->old_id_ = $t_reader->value;
150						break;
151
152					case 'project';
153						# ignore original value, use current project
154						$this->newbug_->project_id = $t_project_id;
155						break;
156
157					case 'custom_fields':
158						# store custom fields
159						$i = -1;
160						$t_depth_cf = $t_reader->depth;
161						while( $t_reader->read() &&
162						        ( $t_reader->depth > $t_depth_cf ||
163						          $t_reader->nodeType != XMLReader::END_ELEMENT ) ) {
164							if( $t_reader->nodeType == XMLReader::ELEMENT ) {
165								if( $t_reader->localName == 'custom_field' ) {
166									$t_custom_fields[++$i] = new stdClass();
167								}
168								switch( $t_reader->localName ) {
169									default:
170										$t_field = $t_reader->localName;
171										$t_reader->read( );
172										$t_custom_fields[$i]->$t_field = $t_reader->value;
173								}
174							}
175						}
176						break;
177
178					case 'bugnotes':
179						# store bug notes
180						$i = -1;
181						$t_depth_bn = $t_reader->depth;
182						while( $t_reader->read() &&
183						        ( $t_reader->depth > $t_depth_bn ||
184						          $t_reader->nodeType != XMLReader::END_ELEMENT ) ) {
185							if( $t_reader->nodeType == XMLReader::ELEMENT ) {
186								if( $t_reader->localName == 'bugnote' ) {
187									$t_bugnotes[++$i] = new stdClass();
188								}
189								switch( $t_reader->localName ) {
190									case 'reporter':
191										$t_old_id = $t_reader->getAttribute( 'id' );
192										$t_reader->read( );
193										$t_bugnotes[$i]->reporter_id = $this->get_user_id( $t_reader->value, $t_user_id );
194										break;
195
196									case 'view_state':
197										$t_old_id = $t_reader->getAttribute( 'id' );
198										$t_reader->read( );
199										$t_bugnotes[$i]->private = $t_reader->value == VS_PRIVATE ? true : false;
200										break;
201
202									default:
203										$t_field = $t_reader->localName;
204										$t_reader->read( );
205										$t_bugnotes[$i]->$t_field = $t_reader->value;
206								}
207							}
208						}
209						break;
210
211					case 'attachments':
212						# store attachments
213						$i = -1;
214						$t_depth_att = $t_reader->depth;
215						while( $t_reader->read() &&
216						        ( $t_reader->depth > $t_depth_att ||
217						          $t_reader->nodeType != XMLReader::END_ELEMENT ) ) {
218							if( $t_reader->nodeType == XMLReader::ELEMENT ) {
219								if( $t_reader->localName == 'attachment' ) {
220									$t_attachments[++$i] = new stdClass();
221								}
222								switch( $t_reader->localName ) {
223									default:
224										$t_field = $t_reader->localName;
225										$t_reader->read( );
226										$t_attachments[$i]->$t_field = $t_reader->value;
227								}
228							}
229						}
230						break;
231					default:
232						$t_field = $t_reader->localName;
233
234						# echo "using default handler for field: $field\n";
235						$t_reader->read( );
236						$this->newbug_->$t_field = $t_reader->value;
237				}
238			}
239		}
240
241		# now save the new bug
242		$this->new_id_ = $this->newbug_->create();
243
244		# add custom fields
245		if( $this->new_id_ > 0 && is_array( $t_custom_fields ) && count( $t_custom_fields ) > 0 ) {
246			foreach( $t_custom_fields as $t_custom_field ) {
247				$t_custom_field_id = custom_field_get_id_from_name( $t_custom_field->name );
248				if( custom_field_ensure_exists( $t_custom_field_id ) && custom_field_is_linked( $t_custom_field_id, $t_project_id ) ) {
249					custom_field_set_value( $t_custom_field->id, $this->new_id_, $t_custom_field->value );
250				} else {
251					error_parameters( $t_custom_field->name, $t_custom_field_id );
252					trigger_error( ERROR_CUSTOM_FIELD_NOT_LINKED_TO_PROJECT, ERROR );
253				}
254			}
255		}
256
257		# add bugnotes
258		if( $this->new_id_ > 0 && is_array( $t_bugnotes ) && count( $t_bugnotes ) > 0 ) {
259			foreach( $t_bugnotes as $t_bugnote ) {
260				bugnote_add(
261					$this->new_id_,
262					$t_bugnote->note,
263					$t_bugnote->time_tracking,
264					$t_bugnote->private,
265					$t_bugnote->note_type,
266					$t_bugnote->note_attr,
267					$t_bugnote->reporter_id,
268					false,
269					$t_bugnote->date_submitted,
270					$t_bugnote->last_modified,
271					true );
272			}
273		}
274
275		# add attachments
276		if( $this->new_id_ > 0 && is_array( $t_attachments ) && count( $t_attachments ) > 0 ) {
277			foreach ( $t_attachments as $t_attachment ) {
278				# Create a temporary file in the temporary files directory using sys_get_temp_dir()
279				$t_temp_file_name = tempnam( sys_get_temp_dir(), 'MantisImport' );
280				file_put_contents( $t_temp_file_name, base64_decode( $t_attachment->content ) );
281				$t_file_data = array(
282					'name'     => $t_attachment->filename,
283					'type'     => $t_attachment->file_type,
284					'tmp_name' => $t_temp_file_name,
285					'size'     => filesize( $t_temp_file_name ),
286					'error'    => UPLOAD_ERR_OK,
287				);
288				# unfortunately we have no clue who has added the attachment (this could only be fetched from history -> feel free to implement this)
289				# also I have no clue where description should come from...
290				file_add( $this->new_id_, $t_file_data, 'bug', $t_attachment->title, $p_desc = '', $p_user_id = null, $t_attachment->date_added, true );
291				unlink( $t_temp_file_name );
292			}
293		}
294
295		#echo "\nnew bug: $this->new_id_\n";
296	}
297
298	/**
299	 * update mapper
300	 * @param ImportXml_Mapper $p_mapper Mapper.
301	 * @return void
302	 */
303	public function update_map( ImportXml_Mapper $p_mapper ) {
304		$p_mapper->add( 'issue', $this->old_id_, $this->new_id_ );
305	}
306
307	/**
308	 * Dump Diagnostic information
309	 * @return void
310	 */
311	public function dumpbug() {
312		var_dump( $this->newbug_ );
313		var_dump( $this->issueMap );
314	}
315
316	/**
317	* Return the user id in the destination tracker
318	*
319	* Current logic is: try to find the same user by username;
320	 * if it fails, use $p_squash_userid
321	*
322	 * @param string  $p_username      Username as imported.
323	 * @param integer $p_squash_userid Fallback userid.
324	 * @return integer
325	*/
326	private function get_user_id( $p_username, $p_squash_userid = 0 ) {
327		$t_user_id = user_get_id_by_name( $p_username );
328		if( $t_user_id === false ) {
329			# user not found by username -> check real name
330			# keep in mind that the setting config_get( 'show_user_realname_threshold' ) may differ between import and export system!
331			$t_user_id = user_get_id_by_realname( $p_username );
332			if( $t_user_id === false ) {
333				# not found
334				$t_user_id = $p_squash_userid;
335			}
336		}
337		return $t_user_id;
338	}
339}
340