importxml.pl 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406
  1. #!/usr/bin/env perl -wT
  2. # -*- Mode: perl; indent-tabs-mode: nil -*-
  3. #
  4. # The contents of this file are subject to the Mozilla Public
  5. # License Version 1.1 (the "License"); you may not use this file
  6. # except in compliance with the License. You may obtain a copy of
  7. # the License at http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS
  10. # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11. # implied. See the License for the specific language governing
  12. # rights and limitations under the License.
  13. #
  14. # The Original Code is the Bugzilla Bug Tracking System.
  15. #
  16. # The Initial Developer of the Original Code is Netscape Communications
  17. # Corporation. Portions created by Netscape are
  18. # Copyright (C) 1998 Netscape Communications Corporation. All
  19. # Rights Reserved.
  20. #
  21. # Contributor(s): Dawn Endico <endico@mozilla.org>
  22. # Gregary Hendricks <ghendricks@novell.com>
  23. # Vance Baarda <vrb@novell.com>
  24. # Guzman Braso <gbn@hqso.net>
  25. # Erik Purins <epurins@day1studios.com>
  26. # Frédéric Buclin <LpSolit@gmail.com>
  27. # This script reads in xml bug data from standard input and inserts
  28. # a new bug into bugzilla. Everything before the beginning <?xml line
  29. # is removed so you can pipe in email messages.
  30. use strict;
  31. #####################################################################
  32. #
  33. # This script is used to import bugs from another installation of bugzilla.
  34. # It can be used in two ways.
  35. # First using the move function of bugzilla
  36. # on another system will send mail to an alias provided by
  37. # the administrator of the target installation (you). Set up an alias
  38. # similar to the one given below so this mail will be automatically
  39. # run by this script and imported into your database. Run 'newaliases'
  40. # after adding this alias to your aliases file. Make sure your sendmail
  41. # installation is configured to allow mail aliases to execute code.
  42. #
  43. # bugzilla-import: "|/usr/bin/perl /opt/bugzilla/importxml.pl"
  44. #
  45. # Second it can be run from the command line with any xml file from
  46. # STDIN that conforms to the bugzilla DTD. In this case you can pass
  47. # an argument to set whether you want to send the
  48. # mail that will be sent to the exporter and maintainer normally.
  49. #
  50. # importxml.pl bugsfile.xml
  51. #
  52. #####################################################################
  53. use File::Basename qw(dirname);
  54. # MTAs may call this script from any directory, but it should always
  55. # run from this one so that it can find its modules.
  56. BEGIN {
  57. require File::Basename;
  58. my $dir = $0; $dir =~ /(.*)/; $dir = $1; # trick taint
  59. chdir(File::Basename::dirname($dir));
  60. }
  61. use lib qw(. lib);
  62. # Data dumber is used for debugging, I got tired of copying it back in
  63. # and then removing it.
  64. #use Data::Dumper;
  65. use Bugzilla;
  66. use Bugzilla::Bug;
  67. use Bugzilla::Product;
  68. use Bugzilla::Version;
  69. use Bugzilla::Component;
  70. use Bugzilla::Milestone;
  71. use Bugzilla::FlagType;
  72. use Bugzilla::BugMail;
  73. use Bugzilla::Mailer;
  74. use Bugzilla::User;
  75. use Bugzilla::Util;
  76. use Bugzilla::Constants;
  77. use Bugzilla::Keyword;
  78. use Bugzilla::Field;
  79. use Bugzilla::Status;
  80. use MIME::Base64;
  81. use MIME::Parser;
  82. use Date::Format;
  83. use Getopt::Long;
  84. use Pod::Usage;
  85. use XML::Twig;
  86. # We want to capture errors and handle them here rather than have the Template
  87. # code barf all over the place.
  88. Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_CMDLINE);
  89. my $debug = 0;
  90. my $mail = '';
  91. my $attach_path = '';
  92. my $help = 0;
  93. my $result = GetOptions(
  94. "verbose|debug+" => \$debug,
  95. "mail|sendmail!" => \$mail,
  96. "attach_path=s" => \$attach_path,
  97. "help|?" => \$help
  98. );
  99. pod2usage(0) if $help;
  100. use constant OK_LEVEL => 3;
  101. use constant DEBUG_LEVEL => 2;
  102. use constant ERR_LEVEL => 1;
  103. our @logs;
  104. our @attachments;
  105. our $bugtotal;
  106. my $xml;
  107. my $dbh = Bugzilla->dbh;
  108. my $params = Bugzilla->params;
  109. my ($timestamp) = $dbh->selectrow_array("SELECT NOW()");
  110. ###############################################################################
  111. # Helper sub routines #
  112. ###############################################################################
  113. sub MailMessage {
  114. return unless ($mail);
  115. my $subject = shift;
  116. my $message = shift;
  117. my @recipients = @_;
  118. my $from = $params->{"moved-from-address"};
  119. $from =~ s/@/\@/g;
  120. foreach my $to (@recipients){
  121. my $header = "To: $to\n";
  122. $header .= "From: Bugzilla <$from>\n";
  123. $header .= "Subject: $subject\n\n";
  124. my $sendmessage = $header . $message . "\n";
  125. MessageToMTA($sendmessage);
  126. }
  127. }
  128. sub Debug {
  129. return unless ($debug);
  130. my ( $message, $level ) = (@_);
  131. print STDERR "OK: $message \n" if ( $level == OK_LEVEL );
  132. print STDERR "ERR: $message \n" if ( $level == ERR_LEVEL );
  133. print STDERR "$message\n"
  134. if ( ( $debug == $level ) && ( $level == DEBUG_LEVEL ) );
  135. }
  136. sub Error {
  137. my ( $reason, $errtype, $exporter ) = @_;
  138. my $subject = "Bug import error: $reason";
  139. my $message = "Cannot import these bugs because $reason ";
  140. $message .= "\n\nPlease re-open the original bug.\n" if ($errtype);
  141. $message .= "For more info, contact " . $params->{"maintainer"} . ".\n";
  142. my @to = ( $params->{"maintainer"}, $exporter);
  143. Debug( $message, ERR_LEVEL );
  144. MailMessage( $subject, $message, @to );
  145. exit;
  146. }
  147. # This subroutine handles flags for process_bug. It is generic in that
  148. # it can handle both attachment flags and bug flags.
  149. sub flag_handler {
  150. my (
  151. $name, $status, $setter_login,
  152. $requestee_login, $exporterid, $bugid,
  153. $productid, $componentid, $attachid
  154. )
  155. = @_;
  156. my $type = ($attachid) ? "attachment" : "bug";
  157. my $err = '';
  158. my $setter = new Bugzilla::User({ name => $setter_login });
  159. my $requestee;
  160. my $requestee_id;
  161. unless ($setter) {
  162. $err = "Invalid setter $setter_login on $type flag $name\n";
  163. $err .= " Dropping flag $name\n";
  164. return $err;
  165. }
  166. if ( !$setter->can_see_bug($bugid) ) {
  167. $err .= "Setter is not a member of bug group\n";
  168. $err .= " Dropping flag $name\n";
  169. return $err;
  170. }
  171. my $setter_id = $setter->id;
  172. if ( defined($requestee_login) ) {
  173. $requestee = new Bugzilla::User({ name => $requestee_login });
  174. if ( $requestee ) {
  175. if ( !$requestee->can_see_bug($bugid) ) {
  176. $err .= "Requestee is not a member of bug group\n";
  177. $err .= " Requesting from the wind\n";
  178. }
  179. else{
  180. $requestee_id = $requestee->id;
  181. }
  182. }
  183. else {
  184. $err = "Invalid requestee $requestee_login on $type flag $name\n";
  185. $err .= " Requesting from the wind.\n";
  186. }
  187. }
  188. my $flag_types;
  189. # If this is an attachment flag we need to do some dirty work to look
  190. # up the flagtype ID
  191. if ($attachid) {
  192. $flag_types = Bugzilla::FlagType::match(
  193. {
  194. 'target_type' => 'attachment',
  195. 'product_id' => $productid,
  196. 'component_id' => $componentid
  197. } );
  198. }
  199. else {
  200. my $bug = new Bugzilla::Bug($bugid);
  201. $flag_types = $bug->flag_types;
  202. }
  203. unless ($flag_types){
  204. $err = "No flag types defined for this bug\n";
  205. $err .= " Dropping flag $name\n";
  206. return $err;
  207. }
  208. # We need to see if the imported flag is in the list of known flags
  209. # It is possible for two flags on the same bug have the same name
  210. # If this is the case, we will only match the first one.
  211. my $ftype;
  212. foreach my $f ( @{$flag_types} ) {
  213. if ( $f->name eq $name) {
  214. $ftype = $f;
  215. last;
  216. }
  217. }
  218. if ($ftype) { # We found the flag in the list
  219. my $grant_group = $ftype->grant_group;
  220. if (( $status eq '+' || $status eq '-' )
  221. && $grant_group && !$setter->in_group_id($grant_group->id)) {
  222. $err = "Setter $setter_login on $type flag $name ";
  223. $err .= "is not in the Grant Group\n";
  224. $err .= " Dropping flag $name\n";
  225. return $err;
  226. }
  227. my $request_group = $ftype->request_group;
  228. if ($request_group
  229. && $status eq '?' && !$setter->in_group_id($request_group->id)) {
  230. $err = "Setter $setter_login on $type flag $name ";
  231. $err .= "is not in the Request Group\n";
  232. $err .= " Dropping flag $name\n";
  233. return $err;
  234. }
  235. # Take the first flag_type that matches
  236. unless ($ftype->is_active) {
  237. $err = "Flag $name is not active in this database\n";
  238. $err .= " Dropping flag $name\n";
  239. return $err;
  240. }
  241. $dbh->do("INSERT INTO flags
  242. (type_id, status, bug_id, attach_id, creation_date,
  243. setter_id, requestee_id)
  244. VALUES (?, ?, ?, ?, ?, ?, ?)", undef,
  245. ($ftype->id, $status, $bugid, $attachid, $timestamp,
  246. $setter_id, $requestee_id));
  247. }
  248. else {
  249. $err = "Dropping unknown $type flag: $name\n";
  250. return $err;
  251. }
  252. return $err;
  253. }
  254. # Converts and returns the input data as an array.
  255. sub _to_array {
  256. my $value = shift;
  257. $value = [$value] if !ref($value);
  258. return @$value;
  259. }
  260. ###############################################################################
  261. # XML Handlers #
  262. ###############################################################################
  263. # This subroutine gets called only once - as soon as the <bugzilla> opening
  264. # tag is parsed. It simply checks to see that the all important exporter
  265. # maintainer and URL base are set.
  266. #
  267. # exporter: email address of the person moving the bugs
  268. # maintainer: the maintainer of the bugzilla installation
  269. # as set in the parameters file
  270. # urlbase: The urlbase parameter of the installation
  271. # bugs are being moved from
  272. #
  273. sub init() {
  274. my ( $twig, $bugzilla ) = @_;
  275. my $root = $twig->root;
  276. my $maintainer = $root->{'att'}->{'maintainer'};
  277. my $exporter = $root->{'att'}->{'exporter'};
  278. my $urlbase = $root->{'att'}->{'urlbase'};
  279. my $xmlversion = $root->{'att'}->{'version'};
  280. if ($xmlversion ne BUGZILLA_VERSION) {
  281. my $log = "Possible version conflict!\n";
  282. $log .= " XML was exported from Bugzilla version $xmlversion\n";
  283. $log .= " But this installation uses ";
  284. $log .= BUGZILLA_VERSION . "\n";
  285. Debug($log, OK_LEVEL);
  286. push(@logs, $log);
  287. }
  288. Error( "no maintainer", "REOPEN", $exporter ) unless ($maintainer);
  289. Error( "no exporter", "REOPEN", $exporter ) unless ($exporter);
  290. Error( "bug importing is disabled here", undef, $exporter ) unless ( $params->{"move-enabled"} );
  291. Error( "invalid exporter: $exporter", "REOPEN", $exporter ) if ( !login_to_id($exporter) );
  292. Error( "no urlbase set", "REOPEN", $exporter ) unless ($urlbase);
  293. my $def_product =
  294. new Bugzilla::Product( { name => $params->{"moved-default-product"} } )
  295. || Error("an invalid default product was defined for the target DB. " .
  296. $params->{"maintainer"} . " needs to fix the definitions of " .
  297. "moved-default-product. \n", "REOPEN", $exporter);
  298. my $def_component = new Bugzilla::Component(
  299. {
  300. product => $def_product,
  301. name => $params->{"moved-default-component"}
  302. })
  303. || Error("an invalid default component was defined for the target DB. " .
  304. $params->{"maintainer"} . " needs to fix the definitions of " .
  305. "moved-default-component.\n", "REOPEN", $exporter);
  306. }
  307. # Parse attachments.
  308. #
  309. # This subroutine is called once for each attachment in the xml file.
  310. # It is called as soon as the closing </attachment> tag is parsed.
  311. # Since attachments have the potential to be very large, and
  312. # since each attachment will be inside <bug>..</bug> tags we shove
  313. # the attachment onto an array which will be processed by process_bug
  314. # and then disposed of. The attachment array will then contain only
  315. # one bugs' attachments at a time.
  316. # The cycle will then repeat for the next <bug>
  317. #
  318. # The attach_id is ignored since mysql generates a new one for us.
  319. # The submitter_id gets filled in with $exporterid.
  320. sub process_attachment() {
  321. my ( $twig, $attach ) = @_;
  322. Debug( "Parsing attachments", DEBUG_LEVEL );
  323. my %attachment;
  324. $attachment{'date'} =
  325. format_time( $attach->field('date'), "%Y-%m-%d %R" ) || $timestamp;
  326. $attachment{'desc'} = $attach->field('desc');
  327. $attachment{'ctype'} = $attach->field('type') || "unknown/unknown";
  328. $attachment{'attachid'} = $attach->field('attachid');
  329. $attachment{'ispatch'} = $attach->{'att'}->{'ispatch'} || 0;
  330. $attachment{'isobsolete'} = $attach->{'att'}->{'isobsolete'} || 0;
  331. $attachment{'isprivate'} = $attach->{'att'}->{'isprivate'} || 0;
  332. $attachment{'filename'} = $attach->field('filename') || "file";
  333. $attachment{'attacher'} = $attach->field('attacher');
  334. # Attachment data is not exported in versions 2.20 and older.
  335. if (defined $attach->first_child('data') &&
  336. defined $attach->first_child('data')->{'att'}->{'encoding'}) {
  337. my $encoding = $attach->first_child('data')->{'att'}->{'encoding'};
  338. if ($encoding =~ /base64/) {
  339. # decode the base64
  340. my $data = $attach->field('data');
  341. my $output = decode_base64($data);
  342. $attachment{'data'} = $output;
  343. }
  344. elsif ($encoding =~ /filename/) {
  345. # read the attachment file
  346. Error("attach_path is required", undef) unless ($attach_path);
  347. my $filename = $attach->field('data');
  348. # Remove any leading path data from the filename
  349. $filename =~ s/(.*\/|.*\\)//gs;
  350. my $attach_filename = $attach_path . "/" . $filename;
  351. open(ATTACH_FH, "<", $attach_filename) or
  352. Error("cannot open $attach_filename", undef);
  353. $attachment{'data'} = do { local $/; <ATTACH_FH> };
  354. close ATTACH_FH;
  355. }
  356. }
  357. else {
  358. $attachment{'data'} = $attach->field('data');
  359. }
  360. # attachment flags
  361. my @aflags;
  362. foreach my $aflag ( $attach->children('flag') ) {
  363. my %aflag;
  364. $aflag{'name'} = $aflag->{'att'}->{'name'};
  365. $aflag{'status'} = $aflag->{'att'}->{'status'};
  366. $aflag{'setter'} = $aflag->{'att'}->{'setter'};
  367. $aflag{'requestee'} = $aflag->{'att'}->{'requestee'};
  368. push @aflags, \%aflag;
  369. }
  370. $attachment{'flags'} = \@aflags if (@aflags);
  371. # free up the memory for use by the rest of the script
  372. $attach->delete;
  373. if ($attachment{'attachid'}) {
  374. push @attachments, \%attachment;
  375. }
  376. else {
  377. push @attachments, "err";
  378. }
  379. }
  380. # This subroutine will be called once for each <bug> in the xml file.
  381. # It is called as soon as the closing </bug> tag is parsed.
  382. # If this bug had any <attachment> tags, they will have been processed
  383. # before we get to this point and their data will be in the @attachments
  384. # array.
  385. # As each bug is processed, it is inserted into the database and then
  386. # purged from memory to free it up for later bugs.
  387. sub process_bug {
  388. my ( $twig, $bug ) = @_;
  389. my $root = $twig->root;
  390. my $maintainer = $root->{'att'}->{'maintainer'};
  391. my $exporter_login = $root->{'att'}->{'exporter'};
  392. my $exporter = new Bugzilla::User({ name => $exporter_login });
  393. my $urlbase = $root->{'att'}->{'urlbase'};
  394. # We will store output information in this variable.
  395. my $log = "";
  396. if ( defined $bug->{'att'}->{'error'} ) {
  397. $log .= "\nError in bug " . $bug->field('bug_id') . "\@$urlbase: ";
  398. $log .= $bug->{'att'}->{'error'} . "\n";
  399. if ( $bug->{'att'}->{'error'} =~ /NotFound/ ) {
  400. $log .= "$exporter_login tried to move bug " . $bug->field('bug_id');
  401. $log .= " here, but $urlbase reports that this bug";
  402. $log .= " does not exist.\n";
  403. }
  404. elsif ( $bug->{'att'}->{'error'} =~ /NotPermitted/ ) {
  405. $log .= "$exporter_login tried to move bug " . $bug->field('bug_id');
  406. $log .= " here, but $urlbase reports that $exporter_login does ";
  407. $log .= " not have access to that bug.\n";
  408. }
  409. return;
  410. }
  411. $bugtotal++;
  412. # This list contains all other bug fields that we want to process.
  413. # If it is not in this list it will not be included.
  414. my %all_fields;
  415. foreach my $field (
  416. qw(long_desc attachment flag group), Bugzilla::Bug::fields() )
  417. {
  418. $all_fields{$field} = 1;
  419. }
  420. my %bug_fields;
  421. my $err = "";
  422. # Loop through all the xml tags inside a <bug> and compare them to the
  423. # lists of fields. If they match throw them into the hash. Otherwise
  424. # append it to the log, which will go into the comments when we are done.
  425. foreach my $bugchild ( $bug->children() ) {
  426. Debug( "Parsing field: " . $bugchild->name, DEBUG_LEVEL );
  427. # Skip the token if one is included. We don't want it included in
  428. # the comments, and it is not used by the importer.
  429. next if $bugchild->name eq 'token';
  430. if ( defined $all_fields{ $bugchild->name } ) {
  431. my @values = $bug->children_text($bugchild->name);
  432. if (scalar @values > 1) {
  433. $bug_fields{$bugchild->name} = \@values;
  434. }
  435. else {
  436. $bug_fields{$bugchild->name} = $values[0];
  437. }
  438. }
  439. else {
  440. $err .= "Unknown bug field \"" . $bugchild->name . "\"";
  441. $err .= " encountered while moving bug\n";
  442. $err .= " <" . $bugchild->name . ">";
  443. if ( $bugchild->children_count > 1 ) {
  444. $err .= "\n";
  445. foreach my $subchild ( $bugchild->children() ) {
  446. $err .= " <" . $subchild->name . ">";
  447. $err .= $subchild->field;
  448. $err .= "</" . $subchild->name . ">\n";
  449. }
  450. }
  451. else {
  452. $err .= $bugchild->field;
  453. }
  454. $err .= "</" . $bugchild->name . ">\n";
  455. }
  456. }
  457. my @long_descs;
  458. my $private = 0;
  459. # Parse long descriptions
  460. foreach my $comment ( $bug->children('long_desc') ) {
  461. Debug( "Parsing Long Description", DEBUG_LEVEL );
  462. my %long_desc;
  463. $long_desc{'who'} = $comment->field('who');
  464. $long_desc{'bug_when'} = $comment->field('bug_when');
  465. $long_desc{'isprivate'} = $comment->{'att'}->{'isprivate'} || 0;
  466. # if one of the comments is private we need to set this flag
  467. if ( $long_desc{'isprivate'} && $exporter->in_group($params->{'insidergroup'})) {
  468. $private = 1;
  469. }
  470. my $data = $comment->field('thetext');
  471. if ( defined $comment->first_child('thetext')->{'att'}->{'encoding'}
  472. && $comment->first_child('thetext')->{'att'}->{'encoding'} =~
  473. /base64/ )
  474. {
  475. $data = decode_base64($data);
  476. }
  477. # If we leave the attachment ID in the comment it will be made a link
  478. # to the wrong attachment. Since the new attachment ID is unknown yet
  479. # let's strip it out for now. We will make a comment with the right ID
  480. # later
  481. $data =~ s/Created an attachment \(id=\d+\)/Created an attachment/g;
  482. # Same goes for bug #'s Since we don't know if the referenced bug
  483. # is also being moved, lets make sure they know it means a different
  484. # bugzilla.
  485. my $url = $urlbase . "show_bug.cgi?id=";
  486. $data =~ s/([Bb]ugs?\s*\#?\s*(\d+))/$url$2/g;
  487. $long_desc{'thetext'} = $data;
  488. push @long_descs, \%long_desc;
  489. }
  490. # instead of giving each comment its own item in the longdescs
  491. # table like it should have, lets cat them all into one big
  492. # comment otherwise we would have to lie often about who
  493. # authored the comment since commenters in one bugzilla probably
  494. # don't have accounts in the other one.
  495. # If one of the comments is private the whole comment will be
  496. # private since we don't want to expose these unnecessarily
  497. sub by_date { my @a; my @b; $a->{'bug_when'} cmp $b->{'bug_when'}; }
  498. my @sorted_descs = sort by_date @long_descs;
  499. my $long_description = "";
  500. for ( my $z = 0 ; $z <= $#sorted_descs ; $z++ ) {
  501. if ( $z == 0 ) {
  502. $long_description .= "\n\n\n---- Reported by ";
  503. }
  504. else {
  505. $long_description .= "\n\n\n---- Additional Comments From ";
  506. }
  507. $long_description .= "$sorted_descs[$z]->{'who'} ";
  508. $long_description .= "$sorted_descs[$z]->{'bug_when'}";
  509. $long_description .= " ----";
  510. $long_description .= "\n\n";
  511. $long_description .= "THIS COMMENT IS PRIVATE \n"
  512. if ( $sorted_descs[$z]->{'isprivate'} );
  513. $long_description .= $sorted_descs[$z]->{'thetext'};
  514. $long_description .= "\n";
  515. }
  516. my $comments;
  517. $comments .= "\n\n--- Bug imported by $exporter_login ";
  518. $comments .= time2str( "%Y-%m-%d %H:%M", time ) . " ";
  519. $comments .= $params->{'timezone'};
  520. $comments .= " ---\n\n";
  521. $comments .= "This bug was previously known as _bug_ $bug_fields{'bug_id'} at ";
  522. $comments .= $urlbase . "show_bug.cgi?id=" . $bug_fields{'bug_id'} . "\n";
  523. if ( defined $bug_fields{'dependson'} ) {
  524. $comments .= "This bug depended on bug(s) " .
  525. join(' ', _to_array($bug_fields{'dependson'})) . ".\n";
  526. }
  527. if ( defined $bug_fields{'blocked'} ) {
  528. $comments .= "This bug blocked bug(s) " .
  529. join(' ', _to_array($bug_fields{'blocked'})) . ".\n";
  530. }
  531. # Now we process each of the fields in turn and make sure they contain
  532. # valid data. We will create two parallel arrays, one for the query
  533. # and one for the values. For every field we need to push an entry onto
  534. # each array.
  535. my @query = ();
  536. my @values = ();
  537. # Each of these fields we will check for newlines and shove onto the array
  538. foreach my $field (qw(status_whiteboard bug_file_loc short_desc)) {
  539. if ($bug_fields{$field}) {
  540. $bug_fields{$field} = clean_text( $bug_fields{$field} );
  541. push( @query, $field );
  542. push( @values, $bug_fields{$field} );
  543. }
  544. }
  545. # Alias
  546. if ( $bug_fields{'alias'} ) {
  547. my ($alias) = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs
  548. WHERE alias = ?", undef,
  549. $bug_fields{'alias'} );
  550. if ($alias) {
  551. $err .= "Dropping conflicting bug alias ";
  552. $err .= $bug_fields{'alias'} . "\n";
  553. }
  554. else {
  555. $alias = $bug_fields{'alias'};
  556. push @query, 'alias';
  557. push @values, $alias;
  558. }
  559. }
  560. # Timestamps
  561. push( @query, "creation_ts" );
  562. push( @values,
  563. format_time( $bug_fields{'creation_ts'}, "%Y-%m-%d %X" )
  564. || $timestamp );
  565. push( @query, "delta_ts" );
  566. push( @values,
  567. format_time( $bug_fields{'delta_ts'}, "%Y-%m-%d %X" )
  568. || $timestamp );
  569. # Bug Access
  570. push( @query, "cclist_accessible" );
  571. push( @values, $bug_fields{'cclist_accessible'} ? 1 : 0 );
  572. push( @query, "reporter_accessible" );
  573. push( @values, $bug_fields{'reporter_accessible'} ? 1 : 0 );
  574. # Product and Component if there is no valid default product and
  575. # component defined in the parameters, we wouldn't be here
  576. my $def_product =
  577. new Bugzilla::Product( { name => $params->{"moved-default-product"} } );
  578. my $def_component = new Bugzilla::Component(
  579. {
  580. product => $def_product,
  581. name => $params->{"moved-default-component"}
  582. }
  583. );
  584. my $product;
  585. my $component;
  586. if ( defined $bug_fields{'product'} ) {
  587. $product = new Bugzilla::Product( { name => $bug_fields{'product'} } );
  588. unless ($product) {
  589. $product = $def_product;
  590. $err .= "Unknown Product " . $bug_fields{'product'} . "\n";
  591. $err .= " Using default product set in Parameters \n";
  592. }
  593. }
  594. else {
  595. $product = $def_product;
  596. }
  597. if ( defined $bug_fields{'component'} ) {
  598. $component = new Bugzilla::Component(
  599. {
  600. product => $product,
  601. name => $bug_fields{'component'}
  602. }
  603. );
  604. unless ($component) {
  605. $component = $def_component;
  606. $product = $def_product;
  607. $err .= "Unknown Component " . $bug_fields{'component'} . "\n";
  608. $err .= " Using default product and component set ";
  609. $err .= "in Parameters \n";
  610. }
  611. }
  612. else {
  613. $component = $def_component;
  614. $product = $def_product;
  615. }
  616. my $prod_id = $product->id;
  617. my $comp_id = $component->id;
  618. push( @query, "product_id" );
  619. push( @values, $prod_id );
  620. push( @query, "component_id" );
  621. push( @values, $comp_id );
  622. # Since there is no default version for a product, we check that the one
  623. # coming over is valid. If not we will use the first one in @versions
  624. # and warn them.
  625. my $version = new Bugzilla::Version(
  626. { product => $product, name => $bug_fields{'version'} });
  627. push( @query, "version" );
  628. if ($version) {
  629. push( @values, $version->name );
  630. }
  631. else {
  632. my @versions = @{ $product->versions };
  633. my $v = $versions[0];
  634. push( @values, $v->name );
  635. $err .= "Unknown version \"";
  636. $err .= ( defined $bug_fields{'version'} )
  637. ? $bug_fields{'version'}
  638. : "unknown";
  639. $err .= " in product " . $product->name . ". \n";
  640. $err .= " Setting version to \"" . $v->name . "\".\n";
  641. }
  642. # Milestone
  643. if ( $params->{"usetargetmilestone"} ) {
  644. my $milestone;
  645. if (defined $bug_fields{'target_milestone'}
  646. && $bug_fields{'target_milestone'} ne "") {
  647. $milestone = new Bugzilla::Milestone(
  648. { product => $product, name => $bug_fields{'target_milestone'} });
  649. }
  650. if ($milestone) {
  651. push( @values, $milestone->name );
  652. }
  653. else {
  654. push( @values, $product->default_milestone );
  655. $err .= "Unknown milestone \"";
  656. $err .= ( defined $bug_fields{'target_milestone'} )
  657. ? $bug_fields{'target_milestone'}
  658. : "unknown";
  659. $err .= " in product " . $product->name . ". \n";
  660. $err .= " Setting to default milestone for this product, ";
  661. $err .= "\"" . $product->default_milestone . "\".\n";
  662. }
  663. push( @query, "target_milestone" );
  664. }
  665. # For priority, severity, opsys and platform we check that the one being
  666. # imported is valid. If it is not we use the defaults set in the parameters.
  667. if (defined( $bug_fields{'bug_severity'} )
  668. && check_field('bug_severity', scalar $bug_fields{'bug_severity'},
  669. undef, ERR_LEVEL) )
  670. {
  671. push( @values, $bug_fields{'bug_severity'} );
  672. }
  673. else {
  674. push( @values, $params->{'defaultseverity'} );
  675. $err .= "Unknown severity ";
  676. $err .= ( defined $bug_fields{'bug_severity'} )
  677. ? $bug_fields{'bug_severity'}
  678. : "unknown";
  679. $err .= ". Setting to default severity \"";
  680. $err .= $params->{'defaultseverity'} . "\".\n";
  681. }
  682. push( @query, "bug_severity" );
  683. if (defined( $bug_fields{'priority'} )
  684. && check_field('priority', scalar $bug_fields{'priority'},
  685. undef, ERR_LEVEL ) )
  686. {
  687. push( @values, $bug_fields{'priority'} );
  688. }
  689. else {
  690. push( @values, $params->{'defaultpriority'} );
  691. $err .= "Unknown priority ";
  692. $err .= ( defined $bug_fields{'priority'} )
  693. ? $bug_fields{'priority'}
  694. : "unknown";
  695. $err .= ". Setting to default priority \"";
  696. $err .= $params->{'defaultpriority'} . "\".\n";
  697. }
  698. push( @query, "priority" );
  699. if (defined( $bug_fields{'rep_platform'} )
  700. && check_field('rep_platform', scalar $bug_fields{'rep_platform'},
  701. undef, ERR_LEVEL ) )
  702. {
  703. push( @values, $bug_fields{'rep_platform'} );
  704. }
  705. else {
  706. push( @values, $params->{'defaultplatform'} );
  707. $err .= "Unknown platform ";
  708. $err .= ( defined $bug_fields{'rep_platform'} )
  709. ? $bug_fields{'rep_platform'}
  710. : "unknown";
  711. $err .=". Setting to default platform \"";
  712. $err .= $params->{'defaultplatform'} . "\".\n";
  713. }
  714. push( @query, "rep_platform" );
  715. if (defined( $bug_fields{'op_sys'} )
  716. && check_field('op_sys', scalar $bug_fields{'op_sys'},
  717. undef, ERR_LEVEL ) )
  718. {
  719. push( @values, $bug_fields{'op_sys'} );
  720. }
  721. else {
  722. push( @values, $params->{'defaultopsys'} );
  723. $err .= "Unknown operating system ";
  724. $err .= ( defined $bug_fields{'op_sys'} )
  725. ? $bug_fields{'op_sys'}
  726. : "unknown";
  727. $err .= ". Setting to default OS \"" . $params->{'defaultopsys'} . "\".\n";
  728. }
  729. push( @query, "op_sys" );
  730. # Process time fields
  731. if ( $params->{"timetrackinggroup"} ) {
  732. my $date = format_time( $bug_fields{'deadline'}, "%Y-%m-%d" )
  733. || undef;
  734. push( @values, $date );
  735. push( @query, "deadline" );
  736. if ( defined $bug_fields{'estimated_time'} ) {
  737. eval {
  738. Bugzilla::Bug::ValidateTime($bug_fields{'estimated_time'}, "e");
  739. };
  740. if (!$@){
  741. push( @values, $bug_fields{'estimated_time'} );
  742. push( @query, "estimated_time" );
  743. }
  744. }
  745. if ( defined $bug_fields{'remaining_time'} ) {
  746. eval {
  747. Bugzilla::Bug::ValidateTime($bug_fields{'remaining_time'}, "r");
  748. };
  749. if (!$@){
  750. push( @values, $bug_fields{'remaining_time'} );
  751. push( @query, "remaining_time" );
  752. }
  753. }
  754. if ( defined $bug_fields{'actual_time'} ) {
  755. eval {
  756. Bugzilla::Bug::ValidateTime($bug_fields{'actual_time'}, "a");
  757. };
  758. if ($@){
  759. $bug_fields{'actual_time'} = 0.0;
  760. $err .= "Invalid Actual Time. Setting to 0.0\n";
  761. }
  762. }
  763. else {
  764. $bug_fields{'actual_time'} = 0.0;
  765. $err .= "Actual time not defined. Setting to 0.0\n";
  766. }
  767. }
  768. # Reporter Assignee QA Contact
  769. my $exporterid = $exporter->id;
  770. my $reporterid = login_to_id( $bug_fields{'reporter'} )
  771. if $bug_fields{'reporter'};
  772. push( @query, "reporter" );
  773. if ( ( $bug_fields{'reporter'} ) && ($reporterid) ) {
  774. push( @values, $reporterid );
  775. }
  776. else {
  777. push( @values, $exporterid );
  778. $err .= "The original reporter of this bug does not have\n";
  779. $err .= " an account here. Reassigning to the person who moved\n";
  780. $err .= " it here: $exporter_login.\n";
  781. if ( $bug_fields{'reporter'} ) {
  782. $err .= " Previous reporter was $bug_fields{'reporter'}.\n";
  783. }
  784. else {
  785. $err .= " Previous reporter is unknown.\n";
  786. }
  787. }
  788. my $changed_owner = 0;
  789. my $owner;
  790. push( @query, "assigned_to" );
  791. if ( ( $bug_fields{'assigned_to'} )
  792. && ( $owner = login_to_id( $bug_fields{'assigned_to'} )) ) {
  793. push( @values, $owner );
  794. }
  795. else {
  796. push( @values, $component->default_assignee->id );
  797. $changed_owner = 1;
  798. $err .= "The original assignee of this bug does not have\n";
  799. $err .= " an account here. Reassigning to the default assignee\n";
  800. $err .= " for the component, ". $component->default_assignee->login .".\n";
  801. if ( $bug_fields{'assigned_to'} ) {
  802. $err .= " Previous assignee was $bug_fields{'assigned_to'}.\n";
  803. }
  804. else {
  805. $err .= " Previous assignee is unknown.\n";
  806. }
  807. }
  808. if ( $params->{"useqacontact"} ) {
  809. my $qa_contact;
  810. push( @query, "qa_contact" );
  811. if ( ( defined $bug_fields{'qa_contact'})
  812. && ( $qa_contact = login_to_id( $bug_fields{'qa_contact'} ) ) ) {
  813. push( @values, $qa_contact );
  814. }
  815. else {
  816. push( @values, $component->default_qa_contact->id || undef );
  817. if ($component->default_qa_contact->id){
  818. $err .= "Setting qa contact to the default for this product.\n";
  819. $err .= " This bug either had no qa contact or an invalid one.\n";
  820. }
  821. }
  822. }
  823. # Status & Resolution
  824. my $has_res = defined($bug_fields{'resolution'});
  825. my $has_status = defined($bug_fields{'bug_status'});
  826. my $valid_res = check_field('resolution',
  827. scalar $bug_fields{'resolution'},
  828. undef, ERR_LEVEL );
  829. my $valid_status = check_field('bug_status',
  830. scalar $bug_fields{'bug_status'},
  831. undef, ERR_LEVEL );
  832. my $is_open = is_open_state($bug_fields{'bug_status'});
  833. my $status = $bug_fields{'bug_status'} || undef;
  834. my $resolution = $bug_fields{'resolution'} || undef;
  835. # Check everconfirmed
  836. my $everconfirmed;
  837. if ($product->votes_to_confirm) {
  838. $everconfirmed = $bug_fields{'everconfirmed'} || 0;
  839. }
  840. else {
  841. $everconfirmed = 1;
  842. }
  843. push (@query, "everconfirmed");
  844. push (@values, $everconfirmed);
  845. # Sanity check will complain about having bugs marked duplicate but no
  846. # entry in the dup table. Since we can't tell the bug ID of bugs
  847. # that might not yet be in the database we have no way of populating
  848. # this table. Change the resolution instead.
  849. if ( $valid_res && ( $bug_fields{'resolution'} eq "DUPLICATE" ) ) {
  850. $resolution = "MOVED";
  851. $err .= "This bug was marked DUPLICATE in the database ";
  852. $err .= "it was moved from.\n Changing resolution to \"MOVED\"\n";
  853. }
  854. # If there is at least 1 initial bug status different from UNCO, use it,
  855. # else use the open bug status with the lowest sortkey (different from UNCO).
  856. my @bug_statuses = @{Bugzilla::Status->can_change_to()};
  857. @bug_statuses = grep { $_->name ne 'UNCONFIRMED' } @bug_statuses;
  858. my $initial_status;
  859. if (scalar(@bug_statuses)) {
  860. $initial_status = $bug_statuses[0]->name;
  861. }
  862. else {
  863. @bug_statuses = @{Bugzilla::Status->get_all()};
  864. # Exclude UNCO and inactive bug statuses.
  865. @bug_statuses = grep { $_->is_active && $_->name ne 'UNCONFIRMED'} @bug_statuses;
  866. my @open_statuses = grep { $_->is_open } @bug_statuses;
  867. if (scalar(@open_statuses)) {
  868. $initial_status = $open_statuses[0]->name;
  869. }
  870. else {
  871. # There is NO other open bug statuses outside UNCO???
  872. Error("no open bug statuses available.");
  873. }
  874. }
  875. if($has_status){
  876. if($valid_status){
  877. if($is_open){
  878. if($has_res){
  879. $err .= "Resolution set on an open status.\n";
  880. $err .= " Dropping resolution $resolution\n";
  881. $resolution = undef;
  882. }
  883. if($changed_owner){
  884. if($everconfirmed){
  885. $status = $initial_status;
  886. }
  887. else{
  888. $status = "UNCONFIRMED";
  889. }
  890. if ($status ne $bug_fields{'bug_status'}){
  891. $err .= "Bug reassigned, setting status to \"$status\".\n";
  892. $err .= " Previous status was \"";
  893. $err .= $bug_fields{'bug_status'} . "\".\n";
  894. }
  895. }
  896. if($everconfirmed){
  897. if($status eq "UNCONFIRMED"){
  898. $err .= "Bug Status was UNCONFIRMED but everconfirmed was true\n";
  899. $err .= " Setting status to $initial_status\n";
  900. $err .= "Resetting votes to 0\n" if ( $bug_fields{'votes'} );
  901. $status = $initial_status;
  902. }
  903. }
  904. else{ # $everconfirmed is false
  905. if($status ne "UNCONFIRMED"){
  906. $err .= "Bug Status was $status but everconfirmed was false\n";
  907. $err .= " Setting status to UNCONFIRMED\n";
  908. $status = "UNCONFIRMED";
  909. }
  910. }
  911. }
  912. else{ # $is_open is false
  913. if(!$has_res){
  914. $err .= "Missing Resolution. Setting status to ";
  915. if($everconfirmed){
  916. $status = $initial_status;
  917. $err .= "$initial_status\n";
  918. }
  919. else{
  920. $status = "UNCONFIRMED";
  921. $err .= "UNCONFIRMED\n";
  922. }
  923. }
  924. if(!$valid_res){
  925. $err .= "Unknown resolution \"$resolution\".\n";
  926. $err .= " Setting resolution to MOVED\n";
  927. $resolution = "MOVED";
  928. }
  929. }
  930. }
  931. else{ # $valid_status is false
  932. if($everconfirmed){
  933. $status = $initial_status;
  934. }
  935. else{
  936. $status = "UNCONFIRMED";
  937. }
  938. $err .= "Bug has invalid status, setting status to \"$status\".\n";
  939. $err .= " Previous status was \"";
  940. $err .= $bug_fields{'bug_status'} . "\".\n";
  941. $resolution = undef;
  942. }
  943. }
  944. else{ #has_status is false
  945. if($everconfirmed){
  946. $status = $initial_status;
  947. }
  948. else{
  949. $status = "UNCONFIRMED";
  950. }
  951. $err .= "Bug has no status, setting status to \"$status\".\n";
  952. $err .= " Previous status was unknown\n";
  953. $resolution = undef;
  954. }
  955. if (defined $resolution){
  956. push( @query, "resolution" );
  957. push( @values, $resolution );
  958. }
  959. # Bug status
  960. push( @query, "bug_status" );
  961. push( @values, $status );
  962. # Custom fields - Multi-select fields have their own table.
  963. my %multi_select_fields;
  964. foreach my $field (Bugzilla->active_custom_fields) {
  965. my $custom_field = $field->name;
  966. my $value = $bug_fields{$custom_field};
  967. next unless defined $value;
  968. if ($field->type == FIELD_TYPE_FREETEXT) {
  969. push(@query, $custom_field);
  970. push(@values, clean_text($value));
  971. } elsif ($field->type == FIELD_TYPE_TEXTAREA) {
  972. push(@query, $custom_field);
  973. push(@values, $value);
  974. } elsif ($field->type == FIELD_TYPE_SINGLE_SELECT) {
  975. my $is_well_formed = check_field($custom_field, $value, undef, ERR_LEVEL);
  976. if ($is_well_formed) {
  977. push(@query, $custom_field);
  978. push(@values, $value);
  979. } else {
  980. $err .= "Skipping illegal value \"$value\" in $custom_field.\n" ;
  981. }
  982. } elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
  983. my @legal_values;
  984. foreach my $item (_to_array($value)) {
  985. my $is_well_formed = check_field($custom_field, $item, undef, ERR_LEVEL);
  986. if ($is_well_formed) {
  987. push(@legal_values, $item);
  988. } else {
  989. $err .= "Skipping illegal value \"$item\" in $custom_field.\n" ;
  990. }
  991. }
  992. if (scalar @legal_values) {
  993. $multi_select_fields{$custom_field} = \@legal_values;
  994. }
  995. } elsif ($field->type == FIELD_TYPE_DATETIME) {
  996. eval { $value = Bugzilla::Bug->_check_datetime_field($value); };
  997. if ($@) {
  998. $err .= "Skipping illegal value \"$value\" in $custom_field.\n" ;
  999. }
  1000. else {
  1001. push(@query, $custom_field);
  1002. push(@values, $value);
  1003. }
  1004. } else {
  1005. $err .= "Type of custom field $custom_field is an unhandled FIELD_TYPE: " .
  1006. $field->type . "\n";
  1007. }
  1008. }
  1009. # For the sake of sanitycheck.cgi we do this.
  1010. # Update lastdiffed if you do not want to have mail sent
  1011. unless ($mail) {
  1012. push @query, "lastdiffed";
  1013. push @values, $timestamp;
  1014. }
  1015. # INSERT the bug
  1016. my $query = "INSERT INTO bugs (" . join( ", ", @query ) . ") VALUES (";
  1017. $query .= '?,' foreach (@values);
  1018. chop($query); # Remove the last comma.
  1019. $query .= ")";
  1020. $dbh->do( $query, undef, @values );
  1021. my $id = $dbh->bz_last_key( 'bugs', 'bug_id' );
  1022. # We are almost certain to get some uninitialized warnings
  1023. # Since this is just for debugging the query, let's shut them up
  1024. eval {
  1025. no warnings 'uninitialized';
  1026. Debug(
  1027. "Bug Query: INSERT INTO bugs (\n"
  1028. . join( ",\n", @query )
  1029. . "\n) VALUES (\n"
  1030. . join( ",\n", @values ),
  1031. DEBUG_LEVEL
  1032. );
  1033. };
  1034. # Handle CC's
  1035. if ( defined $bug_fields{'cc'} ) {
  1036. my %ccseen;
  1037. my $sth_cc = $dbh->prepare("INSERT INTO cc (bug_id, who) VALUES (?,?)");
  1038. foreach my $person (_to_array($bug_fields{'cc'})) {
  1039. next unless $person;
  1040. my $uid;
  1041. if ($uid = login_to_id($person)) {
  1042. if ( !$ccseen{$uid} ) {
  1043. $sth_cc->execute( $id, $uid );
  1044. $ccseen{$uid} = 1;
  1045. }
  1046. }
  1047. else {
  1048. $err .= "CC member $person does not have an account here\n";
  1049. }
  1050. }
  1051. }
  1052. # Handle keywords
  1053. if ( defined( $bug_fields{'keywords'} ) ) {
  1054. my %keywordseen;
  1055. my $key_sth = $dbh->prepare(
  1056. "INSERT INTO keywords
  1057. (bug_id, keywordid) VALUES (?,?)"
  1058. );
  1059. foreach my $keyword ( split( /[\s,]+/, $bug_fields{'keywords'} )) {
  1060. next unless $keyword;
  1061. my $keyword_obj = new Bugzilla::Keyword({name => $keyword});
  1062. if (!$keyword_obj) {
  1063. $err .= "Skipping unknown keyword: $keyword.\n";
  1064. next;
  1065. }
  1066. if (!$keywordseen{$keyword_obj->id}) {
  1067. $key_sth->execute($id, $keyword_obj->id);
  1068. $keywordseen{$keyword_obj->id} = 1;
  1069. }
  1070. }
  1071. my ($keywordarray) = $dbh->selectcol_arrayref(
  1072. "SELECT d.name FROM keyworddefs d
  1073. INNER JOIN keywords k
  1074. ON d.id = k.keywordid
  1075. WHERE k.bug_id = ?
  1076. ORDER BY d.name", undef, $id);
  1077. my $keywordstring = join( ", ", @{$keywordarray} );
  1078. $dbh->do( "UPDATE bugs SET keywords = ? WHERE bug_id = ?",
  1079. undef, $keywordstring, $id )
  1080. }
  1081. # Insert values of custom multi-select fields. They have already
  1082. # been validated.
  1083. foreach my $custom_field (keys %multi_select_fields) {
  1084. my $sth = $dbh->prepare("INSERT INTO bug_$custom_field
  1085. (bug_id, value) VALUES (?, ?)");
  1086. foreach my $value (@{$multi_select_fields{$custom_field}}) {
  1087. $sth->execute($id, $value);
  1088. }
  1089. }
  1090. # Parse bug flags
  1091. foreach my $bflag ( $bug->children('flag')) {
  1092. next unless ( defined($bflag) );
  1093. $err .= flag_handler(
  1094. $bflag->{'att'}->{'name'}, $bflag->{'att'}->{'status'},
  1095. $bflag->{'att'}->{'setter'}, $bflag->{'att'}->{'requestee'},
  1096. $exporterid, $id,
  1097. $comp_id, $prod_id,
  1098. undef
  1099. );
  1100. }
  1101. # Insert Attachments for the bug
  1102. foreach my $att (@attachments) {
  1103. if ($att eq "err"){
  1104. $err .= "No attachment ID specified, dropping attachment\n";
  1105. next;
  1106. }
  1107. if (!$exporter->in_group($params->{'insidergroup'}) && $att->{'isprivate'}){
  1108. $err .= "Exporter not in insidergroup and attachment marked private.\n";
  1109. $err .= " Marking attachment public\n";
  1110. $att->{'isprivate'} = 0;
  1111. }
  1112. my $attacher_id = $att->{'attacher'} ? login_to_id($att->{'attacher'}) : undef;
  1113. $dbh->do("INSERT INTO attachments
  1114. (bug_id, creation_ts, modification_time, filename, description,
  1115. mimetype, ispatch, isprivate, isobsolete, submitter_id)
  1116. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
  1117. undef, $id, $att->{'date'}, $att->{'date'}, $att->{'filename'},
  1118. $att->{'desc'}, $att->{'ctype'}, $att->{'ispatch'},
  1119. $att->{'isprivate'}, $att->{'isobsolete'}, $attacher_id || $exporterid);
  1120. my $att_id = $dbh->bz_last_key( 'attachments', 'attach_id' );
  1121. my $att_data = $att->{'data'};
  1122. my $sth = $dbh->prepare("INSERT INTO attach_data (id, thedata)
  1123. VALUES ($att_id, ?)" );
  1124. trick_taint($att_data);
  1125. $sth->bind_param( 1, $att_data, $dbh->BLOB_TYPE );
  1126. $sth->execute();
  1127. $comments .= "Imported an attachment (id=$att_id)\n";
  1128. if (!$attacher_id) {
  1129. if ($att->{'attacher'}) {
  1130. $err .= "The original submitter of attachment $att_id was\n ";
  1131. $err .= $att->{'attacher'} . ", but he doesn't have an account here.\n";
  1132. }
  1133. else {
  1134. $err .= "The original submitter of attachment $att_id is unknown.\n";
  1135. }
  1136. $err .= " Reassigning to the person who moved it here: $exporter_login.\n";
  1137. }
  1138. # Process attachment flags
  1139. foreach my $aflag (@{ $att->{'flags'} }) {
  1140. next unless defined($aflag) ;
  1141. $err .= flag_handler(
  1142. $aflag->{'name'}, $aflag->{'status'},
  1143. $aflag->{'setter'}, $aflag->{'requestee'},
  1144. $exporterid, $id,
  1145. $comp_id, $prod_id,
  1146. $att_id
  1147. );
  1148. }
  1149. }
  1150. # Clear the attachments array for the next bug
  1151. @attachments = ();
  1152. # Insert longdesc and append any errors
  1153. my $worktime = $bug_fields{'actual_time'} || 0.0;
  1154. $worktime = 0.0 if (!$exporter->in_group($params->{'timetrackinggroup'}));
  1155. $long_description .= "\n" . $comments;
  1156. if ($err) {
  1157. $long_description .= "\n$err\n";
  1158. }
  1159. trick_taint($long_description);
  1160. $dbh->do("INSERT INTO longdescs
  1161. (bug_id, who, bug_when, work_time, isprivate, thetext)
  1162. VALUES (?,?,?,?,?,?)", undef,
  1163. $id, $exporterid, $timestamp, $worktime, $private, $long_description
  1164. );
  1165. Bugzilla::Bug->new($id)->_sync_fulltext('new_bug');
  1166. # Add this bug to each group of which its product is a member.
  1167. my $sth_group = $dbh->prepare("INSERT INTO bug_group_map (bug_id, group_id)
  1168. VALUES (?, ?)");
  1169. foreach my $group_id ( keys %{ $product->group_controls } ) {
  1170. if ($product->group_controls->{$group_id}->{'membercontrol'} != CONTROLMAPNA
  1171. && $product->group_controls->{$group_id}->{'othercontrol'} != CONTROLMAPNA){
  1172. $sth_group->execute( $id, $group_id );
  1173. }
  1174. }
  1175. $log .= "Bug ${urlbase}show_bug.cgi?id=$bug_fields{'bug_id'} ";
  1176. $log .= "imported as bug $id.\n";
  1177. $log .= $params->{"urlbase"} . "show_bug.cgi?id=$id\n\n";
  1178. if ($err) {
  1179. $log .= "The following problems were encountered while creating bug $id.\n";
  1180. $log .= $err;
  1181. $log .= "You may have to set certain fields in the new bug by hand.\n\n";
  1182. }
  1183. Debug( $log, OK_LEVEL );
  1184. push(@logs, $log);
  1185. Bugzilla::BugMail::Send( $id, { 'changer' => $exporter_login } ) if ($mail);
  1186. # done with the xml data. Lets clear it from memory
  1187. $twig->purge;
  1188. }
  1189. Debug( "Reading xml", DEBUG_LEVEL );
  1190. # Read STDIN in slurp mode. VERY dangerous, but we live on the wild side ;-)
  1191. local ($/);
  1192. $xml = <>;
  1193. # If there's anything except whitespace before <?xml then we guess it's a mail
  1194. # and MIME::Parser should parse it. Else don't.
  1195. if ($xml =~ m/\S.*<\?xml/s ) {
  1196. # If the email was encoded (Mailer::MessageToMTA() does it when using UTF-8),
  1197. # we have to decode it first, else the XML parsing will fail.
  1198. my $parser = MIME::Parser->new;
  1199. $parser->output_to_core(1);
  1200. $parser->tmp_to_core(1);
  1201. my $entity = $parser->parse_data($xml);
  1202. my $bodyhandle = $entity->bodyhandle;
  1203. $xml = $bodyhandle->as_string;
  1204. }
  1205. # remove everything in file before xml header
  1206. $xml =~ s/^.+(<\?xml version.+)$/$1/s;
  1207. Debug( "Parsing tree", DEBUG_LEVEL );
  1208. my $twig = XML::Twig->new(
  1209. twig_handlers => {
  1210. bug => \&process_bug,
  1211. attachment => \&process_attachment
  1212. },
  1213. start_tag_handlers => { bugzilla => \&init }
  1214. );
  1215. $twig->parse($xml);
  1216. my $root = $twig->root;
  1217. my $maintainer = $root->{'att'}->{'maintainer'};
  1218. my $exporter = $root->{'att'}->{'exporter'};
  1219. my $urlbase = $root->{'att'}->{'urlbase'};
  1220. # It is time to email the result of the import.
  1221. my $log = join("\n\n", @logs);
  1222. $log .= "\n\nImported $bugtotal bug(s) from $urlbase,\n sent by $exporter.\n";
  1223. my $subject = "$bugtotal Bug(s) successfully moved from $urlbase to "
  1224. . $params->{"urlbase"};
  1225. my @to = ($exporter, $maintainer);
  1226. MailMessage( $subject, $log, @to );
  1227. __END__
  1228. =head1 NAME
  1229. importxml - Import bugzilla bug data from xml.
  1230. =head1 SYNOPSIS
  1231. importxml.pl [options] [file ...]
  1232. Options:
  1233. -? --help brief help message
  1234. -v --verbose print error and debug information.
  1235. Multiple -v increases verbosity
  1236. -m --sendmail send mail to recipients with log of bugs imported
  1237. --attach_path The path to the attachment files.
  1238. (Required if encoding="filename" is used for attachments.)
  1239. =head1 OPTIONS
  1240. =over 8
  1241. =item B<-?>
  1242. Print a brief help message and exits.
  1243. =item B<-v>
  1244. Print error and debug information. Mulltiple -v increases verbosity
  1245. =item B<-m>
  1246. Send mail to exporter with a log of bugs imported and any errors.
  1247. =back
  1248. =head1 DESCRIPTION
  1249. This script is used to import bugs from another installation of bugzilla.
  1250. It can be used in two ways.
  1251. First using the move function of bugzilla
  1252. on another system will send mail to an alias provided by
  1253. the administrator of the target installation (you). Set up an alias
  1254. similar to the one given below so this mail will be automatically
  1255. run by this script and imported into your database. Run 'newaliases'
  1256. after adding this alias to your aliases file. Make sure your sendmail
  1257. installation is configured to allow mail aliases to execute code.
  1258. bugzilla-import: "|/usr/bin/perl /opt/bugzilla/importxml.pl --mail"
  1259. Second it can be run from the command line with any xml file from
  1260. STDIN that conforms to the bugzilla DTD. In this case you can pass
  1261. an argument to set whether you want to send the
  1262. mail that will be sent to the exporter and maintainer normally.
  1263. importxml.pl [options] bugsfile.xml
  1264. =cut