From 8ec8da0491ad89604700b3e29a227966f6d84ba1 Mon Sep 17 00:00:00 2001 From: Perl Tidy Date: Wed, 5 Dec 2018 15:38:52 -0500 Subject: no bug - reformat all the code using the new perltidy rules --- Bugzilla/Install/DB.pm | 6695 ++++++++++++++++++++------------------ Bugzilla/Install/Filesystem.pm | 1279 ++++---- Bugzilla/Install/Localconfig.pm | 577 ++-- Bugzilla/Install/Requirements.pm | 382 +-- Bugzilla/Install/Util.pm | 907 +++--- 5 files changed, 5050 insertions(+), 4790 deletions(-) (limited to 'Bugzilla/Install') diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm index 8b3d4b8cc..a8db6bb75 100644 --- a/Bugzilla/Install/DB.pm +++ b/Bugzilla/Install/DB.pm @@ -33,95 +33,100 @@ use URI::QueryParam; # NOTE: This is NOT the function for general table updates. See # update_table_definitions for that. This is only for the fielddefs table. sub update_fielddefs_definition { - my $dbh = Bugzilla->dbh; - - # 2005-02-21 - LpSolit@gmail.com - Bug 279910 - # qacontact_accessible and assignee_accessible field names no longer exist - # in the 'bugs' table. Their corresponding entries in the 'bugs_activity' - # table should therefore be marked as obsolete, meaning that they cannot - # be used anymore when querying the database - they are not deleted in - # order to keep track of these fields in the activity table. - if (!$dbh->bz_column_info('fielddefs', 'obsolete')) { - $dbh->bz_add_column('fielddefs', 'obsolete', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - print "Marking qacontact_accessible and assignee_accessible as", - " obsolete fields...\n"; - $dbh->do("UPDATE fielddefs SET obsolete = 1 + my $dbh = Bugzilla->dbh; + + # 2005-02-21 - LpSolit@gmail.com - Bug 279910 + # qacontact_accessible and assignee_accessible field names no longer exist + # in the 'bugs' table. Their corresponding entries in the 'bugs_activity' + # table should therefore be marked as obsolete, meaning that they cannot + # be used anymore when querying the database - they are not deleted in + # order to keep track of these fields in the activity table. + if (!$dbh->bz_column_info('fielddefs', 'obsolete')) { + $dbh->bz_add_column('fielddefs', 'obsolete', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + print "Marking qacontact_accessible and assignee_accessible as", + " obsolete fields...\n"; + $dbh->do( + "UPDATE fielddefs SET obsolete = 1 WHERE name = 'qacontact_accessible' - OR name = 'assignee_accessible'"); - } - - # 2005-08-10 Myk Melez bug 287325 - # Record each field's type and whether or not it's a custom field, - # in fielddefs. - $dbh->bz_add_column('fielddefs', 'type', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); - $dbh->bz_add_column('fielddefs', 'custom', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - - $dbh->bz_add_column('fielddefs', 'enter_bug', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - - # Change the name of the fieldid column to id, so that fielddefs - # can use Bugzilla::Object easily. We have to do this up here, because - # otherwise adding these field definitions will fail. - $dbh->bz_rename_column('fielddefs', 'fieldid', 'id'); - - # If the largest fielddefs sortkey is less than 100, then - # we're using the old sorting system, and we should convert - # it to the new one before adding any new definitions. - if (!$dbh->selectrow_arrayref( - 'SELECT COUNT(id) FROM fielddefs WHERE sortkey >= 100')) - { - print "Updating the sortkeys for the fielddefs table...\n"; - my $field_ids = $dbh->selectcol_arrayref( - 'SELECT id FROM fielddefs ORDER BY sortkey'); - my $sortkey = 100; - foreach my $field_id (@$field_ids) { - $dbh->do('UPDATE fielddefs SET sortkey = ? WHERE id = ?', - undef, $sortkey, $field_id); - $sortkey += 100; - } + OR name = 'assignee_accessible'" + ); + } + + # 2005-08-10 Myk Melez bug 287325 + # Record each field's type and whether or not it's a custom field, + # in fielddefs. + $dbh->bz_add_column('fielddefs', 'type', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); + $dbh->bz_add_column('fielddefs', 'custom', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + + $dbh->bz_add_column('fielddefs', 'enter_bug', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + + # Change the name of the fieldid column to id, so that fielddefs + # can use Bugzilla::Object easily. We have to do this up here, because + # otherwise adding these field definitions will fail. + $dbh->bz_rename_column('fielddefs', 'fieldid', 'id'); + + # If the largest fielddefs sortkey is less than 100, then + # we're using the old sorting system, and we should convert + # it to the new one before adding any new definitions. + if (!$dbh->selectrow_arrayref( + 'SELECT COUNT(id) FROM fielddefs WHERE sortkey >= 100')) + { + print "Updating the sortkeys for the fielddefs table...\n"; + my $field_ids + = $dbh->selectcol_arrayref('SELECT id FROM fielddefs ORDER BY sortkey'); + my $sortkey = 100; + foreach my $field_id (@$field_ids) { + $dbh->do('UPDATE fielddefs SET sortkey = ? WHERE id = ?', + undef, $sortkey, $field_id); + $sortkey += 100; } + } - $dbh->bz_add_column('fielddefs', 'visibility_field_id', {TYPE => 'INT3'}); - $dbh->bz_add_column('fielddefs', 'value_field_id', {TYPE => 'INT3'}); - $dbh->bz_add_index('fielddefs', 'fielddefs_value_field_id_idx', - ['value_field_id']); + $dbh->bz_add_column('fielddefs', 'visibility_field_id', {TYPE => 'INT3'}); + $dbh->bz_add_column('fielddefs', 'value_field_id', {TYPE => 'INT3'}); + $dbh->bz_add_index('fielddefs', 'fielddefs_value_field_id_idx', + ['value_field_id']); - # Bug 344878 - if (!$dbh->bz_column_info('fielddefs', 'buglist')) { - $dbh->bz_add_column('fielddefs', 'buglist', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - # Set non-multiselect custom fields as valid buglist fields - # Note that default fields will be handled in Field.pm - $dbh->do('UPDATE fielddefs SET buglist = 1 WHERE custom = 1 AND type != ' . FIELD_TYPE_MULTI_SELECT); - } + # Bug 344878 + if (!$dbh->bz_column_info('fielddefs', 'buglist')) { + $dbh->bz_add_column('fielddefs', 'buglist', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + + # Set non-multiselect custom fields as valid buglist fields + # Note that default fields will be handled in Field.pm + $dbh->do('UPDATE fielddefs SET buglist = 1 WHERE custom = 1 AND type != ' + . FIELD_TYPE_MULTI_SELECT); + } - #2008-08-26 elliotte_martin@yahoo.com - Bug 251556 - $dbh->bz_add_column('fielddefs', 'reverse_desc', {TYPE => 'TINYTEXT'}); + #2008-08-26 elliotte_martin@yahoo.com - Bug 251556 + $dbh->bz_add_column('fielddefs', 'reverse_desc', {TYPE => 'TINYTEXT'}); - $dbh->do('UPDATE fielddefs SET buglist = 1 - WHERE custom = 1 AND type = ' . FIELD_TYPE_MULTI_SELECT); + $dbh->do( + 'UPDATE fielddefs SET buglist = 1 + WHERE custom = 1 AND type = ' . FIELD_TYPE_MULTI_SELECT + ); - $dbh->bz_add_column('fielddefs', 'is_mandatory', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - $dbh->bz_add_index('fielddefs', 'fielddefs_is_mandatory_idx', - ['is_mandatory']); + $dbh->bz_add_column('fielddefs', 'is_mandatory', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + $dbh->bz_add_index('fielddefs', 'fielddefs_is_mandatory_idx', ['is_mandatory']); - # 2010-04-05 dkl@redhat.com - Bug 479400 - _migrate_field_visibility_value(); + # 2010-04-05 dkl@redhat.com - Bug 479400 + _migrate_field_visibility_value(); - $dbh->bz_add_column('fielddefs', 'is_numeric', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - $dbh->do('UPDATE fielddefs SET is_numeric = 1 WHERE type = ' - . FIELD_TYPE_BUG_ID); + $dbh->bz_add_column('fielddefs', 'is_numeric', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + $dbh->do( + 'UPDATE fielddefs SET is_numeric = 1 WHERE type = ' . FIELD_TYPE_BUG_ID); - Bugzilla::Hook::process('install_update_db_fielddefs'); + Bugzilla::Hook::process('install_update_db_fielddefs'); - # Remember, this is not the function for adding general table changes. - # That is below. Add new changes to the fielddefs table above this - # comment. + # Remember, this is not the function for adding general table changes. + # That is below. Add new changes to the fielddefs table above this + # comment. } # Small changes can be put directly into this function. @@ -147,1110 +152,1136 @@ sub update_fielddefs_definition { # the purpose of a column. # sub update_table_definitions { - my $old_params = shift; - my $dbh = Bugzilla->dbh; - _update_pre_checksetup_bugzillas(); - - $dbh->bz_add_column('attachments', 'submitter_id', - {TYPE => 'INT3', NOTNULL => 1}, 0); - - $dbh->bz_rename_column('bugs_activity', 'when', 'bug_when'); - - _add_bug_vote_cache(); - _update_product_name_definition(); - - $dbh->bz_add_column('profiles', 'disabledtext', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); - - _populate_longdescs(); - _update_bugs_activity_field_to_fieldid(); - - if (!$dbh->bz_column_info('bugs', 'lastdiffed')) { - $dbh->bz_add_column('bugs', 'lastdiffed', {TYPE =>'DATETIME'}); - $dbh->do('UPDATE bugs SET lastdiffed = NOW()'); - } - - _add_unique_login_name_index_to_profiles(); - - $dbh->bz_add_column('profiles', 'mybugslink', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - - _update_component_user_fields_to_ids(); - - $dbh->bz_add_column('bugs', 'everconfirmed', - {TYPE => 'BOOLEAN', NOTNULL => 1}, 1); - - _populate_milestones_table(); - - # 2000-03-22 Changed the default value for target_milestone to be "---" - # (which is still not quite correct, but much better than what it was - # doing), and made the size of the value field in the milestones table match - # the size of the target_milestone field in the bugs table. - $dbh->bz_alter_column('bugs', 'target_milestone', - {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"}); - $dbh->bz_alter_column('milestones', 'value', - {TYPE => 'varchar(20)', NOTNULL => 1}); - - _add_products_defaultmilestone(); - - # 2000-03-24 Added unique indexes into the cc and keyword tables. This - # prevents certain database inconsistencies, and, moreover, is required for - # new generalized list code to work. - if (!$dbh->bz_index_info('cc', 'cc_bug_id_idx') - || !$dbh->bz_index_info('cc', 'cc_bug_id_idx')->{TYPE}) - { - $dbh->bz_drop_index('cc', 'cc_bug_id_idx'); - $dbh->bz_add_index('cc', 'cc_bug_id_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(bug_id who)]}); - } - if (!$dbh->bz_index_info('keywords', 'keywords_bug_id_idx') - || !$dbh->bz_index_info('keywords', 'keywords_bug_id_idx')->{TYPE}) - { - $dbh->bz_drop_index('keywords', 'keywords_bug_id_idx'); - $dbh->bz_add_index('keywords', 'keywords_bug_id_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(bug_id keywordid)]}); - } - - _copy_from_comments_to_longdescs(); - _populate_duplicates_table(); - - if (!$dbh->bz_column_info('email_setting', 'user_id')) { - $dbh->bz_add_column('profiles', 'emailflags', {TYPE => 'MEDIUMTEXT'}); - } - - if (!$dbh->bz_index_info('email_rates', 'email_rates_message_ts_idx')) { - $dbh->bz_add_index( 'email_rates', 'email_rates_message_ts_idx', ['message_ts'] ); - } - - $dbh->bz_add_column('groups', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - - $dbh->bz_add_column('attachments', 'isobsolete', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - - $dbh->bz_drop_column("profiles", "emailnotification"); - $dbh->bz_drop_column("profiles", "newemailtech"); - - # 2003-11-19; chicks@chicks.net; bug 225973: fix field size to accommodate - # wider algorithms such as Blowfish. Note that this needs to be run - # before recrypting passwords in the following block. - $dbh->bz_alter_column('profiles', 'cryptpassword', - {TYPE => 'varchar(128)'}); - - _recrypt_plaintext_passwords(); - - # 2001-06-15 kiko@async.com.br - Change bug:version size to avoid - # truncates re http://bugzilla.mozilla.org/show_bug.cgi?id=9352 - $dbh->bz_alter_column('bugs', 'version', - {TYPE => 'varchar(64)', NOTNULL => 1}); - - _update_bugs_activity_to_only_record_changes(); - - # bug 90933: Make disabledtext NOT NULL - if (!$dbh->bz_column_info('profiles', 'disabledtext')->{NOTNULL}) { - $dbh->bz_alter_column("profiles", "disabledtext", - {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); - } - - $dbh->bz_add_column("bugs", "reporter_accessible", - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - $dbh->bz_add_column("bugs", "cclist_accessible", - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - - $dbh->bz_add_column("bugs_activity", "attach_id", {TYPE => 'INT5'}); - - _delete_logincookies_cryptpassword_and_handle_invalid_cookies(); - - # qacontact/assignee should always be able to see bugs: bug 97471 - $dbh->bz_drop_column("bugs", "qacontact_accessible"); - $dbh->bz_drop_column("bugs", "assignee_accessible"); - - # 2002-02-20 jeff.hedlund@matrixsi.com - bug 24789 time tracking - $dbh->bz_add_column("longdescs", "work_time", - {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'}); - $dbh->bz_add_column("bugs", "estimated_time", - {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'}); - $dbh->bz_add_column("bugs", "remaining_time", - {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'}); - $dbh->bz_add_column("bugs", "deadline", {TYPE => 'DATETIME'}); - - _use_ip_instead_of_hostname_in_logincookies(); - - $dbh->bz_add_column('longdescs', 'isprivate', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - $dbh->bz_add_column('attachments', 'isprivate', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - - $dbh->bz_add_column("bugs", "alias", {TYPE => "varchar(20)"}); - $dbh->bz_add_index('bugs', 'bugs_alias_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(alias)]}); - - _move_quips_into_db(); - - $dbh->bz_drop_column("namedqueries", "watchfordiffs"); - - _use_ids_for_products_and_components(); - _convert_groups_system_from_groupset(); - _convert_attachment_statuses_to_flags(); - _remove_spaces_and_commas_from_flagtypes(); - _setup_usebuggroups_backward_compatibility(); - _remove_user_series_map(); - - # 2006-08-03 remi_zara@mac.com bug 346241, make series.creator nullable - # This must happen before calling _copy_old_charts_into_database(). - if ($dbh->bz_column_info('series', 'creator')->{NOTNULL}) { - $dbh->bz_alter_column('series', 'creator', {TYPE => 'INT3'}); - $dbh->do("UPDATE series SET creator = NULL WHERE creator = 0"); - } - - _copy_old_charts_into_database(); - - _add_user_group_map_grant_type(); - _add_group_group_map_grant_type(); - - $dbh->bz_add_column("profiles", "extern_id", {TYPE => 'varchar(64)'}); - - $dbh->bz_add_column('flagtypes', 'grant_group_id', {TYPE => 'INT3'}); - $dbh->bz_add_column('flagtypes', 'request_group_id', {TYPE => 'INT3'}); - - # mailto is no longer just userids - $dbh->bz_rename_column('whine_schedules', 'mailto_userid', 'mailto'); - $dbh->bz_add_column('whine_schedules', 'mailto_type', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'}); - - _add_longdescs_already_wrapped(); - - # Moved enum types to separate tables so we need change the old enum - # types to standard varchars in the bugs table. - $dbh->bz_alter_column('bugs', 'bug_status', - {TYPE => 'varchar(64)', NOTNULL => 1}); - # 2005-03-23 Tomas.Kopal@altap.cz - add default value to resolution, - # bug 286695 - $dbh->bz_alter_column('bugs', 'resolution', - {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"}); - $dbh->bz_alter_column('bugs', 'priority', - {TYPE => 'varchar(64)', NOTNULL => 1}); - $dbh->bz_alter_column('bugs', 'bug_severity', - {TYPE => 'varchar(64)', NOTNULL => 1}); - $dbh->bz_alter_column('bugs', 'rep_platform', - {TYPE => 'varchar(64)', NOTNULL => 1}, ''); - $dbh->bz_alter_column('bugs', 'op_sys', - {TYPE => 'varchar(64)', NOTNULL => 1}); - - # When migrating quips from the '$datadir/comments' file to the DB, - # the user ID should be NULL instead of 0 (which is an invalid user ID). - if ($dbh->bz_column_info('quips', 'userid')->{NOTNULL}) { - $dbh->bz_alter_column('quips', 'userid', {TYPE => 'INT3'}); - print "Changing owner to NULL for quips where the owner is", - " unknown...\n"; - $dbh->do('UPDATE quips SET userid = NULL WHERE userid = 0'); - } - - _convert_attachments_filename_from_mediumtext(); - - $dbh->bz_add_column('quips', 'approved', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - - # 2002-12-20 Bug 180870 - remove manual shadowdb replication code - $dbh->bz_drop_table("shadowlog"); - - _rename_votes_count_and_force_group_refresh(); - - # 2004/02/15 - Summaries shouldn't be null - see bug 220232 - if (!exists $dbh->bz_column_info('bugs', 'short_desc')->{NOTNULL}) { - $dbh->bz_alter_column('bugs', 'short_desc', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); - } + my $old_params = shift; + my $dbh = Bugzilla->dbh; + _update_pre_checksetup_bugzillas(); - $dbh->bz_add_column('products', 'classification_id', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '1'}); + $dbh->bz_add_column('attachments', 'submitter_id', + {TYPE => 'INT3', NOTNULL => 1}, 0); - _fix_group_with_empty_name(); + $dbh->bz_rename_column('bugs_activity', 'when', 'bug_when'); - $dbh->bz_add_index('bugs_activity', 'bugs_activity_who_idx', [qw(who)]); + _add_bug_vote_cache(); + _update_product_name_definition(); - # Add defaults for some fields that should have them but didn't. - $dbh->bz_alter_column('bugs', 'status_whiteboard', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); - if ($dbh->bz_column_info('bugs', 'votes')) { - $dbh->bz_alter_column('bugs', 'votes', - {TYPE => 'INT3', NOTNULL => 1, DEFAULT => '0'}); - } + $dbh->bz_add_column('profiles', 'disabledtext', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); - $dbh->bz_alter_column('bugs', 'lastdiffed', {TYPE => 'DATETIME'}); + _populate_longdescs(); + _update_bugs_activity_field_to_fieldid(); - # 2005-03-09 qa_contact should be NULL instead of 0, bug 285534 - if ($dbh->bz_column_info('bugs', 'qa_contact')->{NOTNULL}) { - $dbh->bz_alter_column('bugs', 'qa_contact', {TYPE => 'INT3'}); - $dbh->do("UPDATE bugs SET qa_contact = NULL WHERE qa_contact = 0"); - } + if (!$dbh->bz_column_info('bugs', 'lastdiffed')) { + $dbh->bz_add_column('bugs', 'lastdiffed', {TYPE => 'DATETIME'}); + $dbh->do('UPDATE bugs SET lastdiffed = NOW()'); + } - # 2005-03-27 initialqacontact should be NULL instead of 0, bug 287483 - if ($dbh->bz_column_info('components', 'initialqacontact')->{NOTNULL}) { - $dbh->bz_alter_column('components', 'initialqacontact', - {TYPE => 'INT3'}); - } - $dbh->do("UPDATE components SET initialqacontact = NULL " . - "WHERE initialqacontact = 0"); + _add_unique_login_name_index_to_profiles(); - _migrate_email_prefs_to_new_table(); - _initialize_new_email_prefs(); - _change_all_mysql_booleans_to_tinyint(); + $dbh->bz_add_column('profiles', 'mybugslink', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - # make classification_id field type be consistent with DB:Schema - $dbh->bz_alter_column('products', 'classification_id', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '1'}); + _update_component_user_fields_to_ids(); - # initialowner was accidentally NULL when we checked-in Schema, - # when it really should be NOT NULL. - $dbh->bz_alter_column('components', 'initialowner', - {TYPE => 'INT3', NOTNULL => 1}, 0); + $dbh->bz_add_column('bugs', 'everconfirmed', {TYPE => 'BOOLEAN', NOTNULL => 1}, + 1); - # 2005-03-28 - bug 238800 - index flags.type_id for editflagtypes.cgi - $dbh->bz_add_index('flags', 'flags_type_id_idx', [qw(type_id)]); + _populate_milestones_table(); - # For a short time, the flags_type_id_idx was misnamed in upgraded installs. - $dbh->bz_drop_index('flags', 'type_id'); + # 2000-03-22 Changed the default value for target_milestone to be "---" + # (which is still not quite correct, but much better than what it was + # doing), and made the size of the value field in the milestones table match + # the size of the target_milestone field in the bugs table. + $dbh->bz_alter_column('bugs', 'target_milestone', + {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"}); + $dbh->bz_alter_column('milestones', 'value', + {TYPE => 'varchar(20)', NOTNULL => 1}); - # 2005-04-28 - LpSolit@gmail.com - Bug 7233: add an index to versions - $dbh->bz_alter_column('versions', 'value', - {TYPE => 'varchar(64)', NOTNULL => 1}); - _add_versions_product_id_index(); - - if (!exists $dbh->bz_column_info('milestones', 'sortkey')->{DEFAULT}) { - $dbh->bz_alter_column('milestones', 'sortkey', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); - } + _add_products_defaultmilestone(); - # 2005-06-14 - LpSolit@gmail.com - Bug 292544 - $dbh->bz_alter_column('bugs', 'creation_ts', {TYPE => 'DATETIME'}); + # 2000-03-24 Added unique indexes into the cc and keyword tables. This + # prevents certain database inconsistencies, and, moreover, is required for + # new generalized list code to work. + if ( !$dbh->bz_index_info('cc', 'cc_bug_id_idx') + || !$dbh->bz_index_info('cc', 'cc_bug_id_idx')->{TYPE}) + { + $dbh->bz_drop_index('cc', 'cc_bug_id_idx'); + $dbh->bz_add_index('cc', 'cc_bug_id_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(bug_id who)]}); + } + if ( !$dbh->bz_index_info('keywords', 'keywords_bug_id_idx') + || !$dbh->bz_index_info('keywords', 'keywords_bug_id_idx')->{TYPE}) + { + $dbh->bz_drop_index('keywords', 'keywords_bug_id_idx'); + $dbh->bz_add_index('keywords', 'keywords_bug_id_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(bug_id keywordid)]}); + } - _fix_whine_queries_title_and_op_sys_value(); - _fix_attachments_submitter_id_idx(); - _copy_attachments_thedata_to_attach_data(); - _fix_broken_all_closed_series(); - # 2005-08-14 bugreport@peshkin.net -- Bug 304583 - # Get rid of leftover DERIVED group permissions - use constant GRANT_DERIVED => 1; - $dbh->do("DELETE FROM user_group_map WHERE grant_type = " . GRANT_DERIVED); + _copy_from_comments_to_longdescs(); + _populate_duplicates_table(); - _rederive_regex_groups(); + if (!$dbh->bz_column_info('email_setting', 'user_id')) { + $dbh->bz_add_column('profiles', 'emailflags', {TYPE => 'MEDIUMTEXT'}); + } - # PUBLIC is a reserved word in Oracle. - $dbh->bz_rename_column('series', 'public', 'is_public'); + if (!$dbh->bz_index_info('email_rates', 'email_rates_message_ts_idx')) { + $dbh->bz_add_index('email_rates', 'email_rates_message_ts_idx', ['message_ts']); + } - # 2005-11-04 LpSolit@gmail.com - Bug 305927 - $dbh->bz_alter_column('groups', 'userregexp', - {TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"}); + $dbh->bz_add_column('groups', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - _clean_control_characters_from_short_desc(); + $dbh->bz_add_column('attachments', 'isobsolete', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - # 2005-12-07 altlst@sonic.net -- Bug 225221 - $dbh->bz_add_column('longdescs', 'comment_id', - {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + $dbh->bz_drop_column("profiles", "emailnotification"); + $dbh->bz_drop_column("profiles", "newemailtech"); + + # 2003-11-19; chicks@chicks.net; bug 225973: fix field size to accommodate + # wider algorithms such as Blowfish. Note that this needs to be run + # before recrypting passwords in the following block. + $dbh->bz_alter_column('profiles', 'cryptpassword', {TYPE => 'varchar(128)'}); + + _recrypt_plaintext_passwords(); + + # 2001-06-15 kiko@async.com.br - Change bug:version size to avoid + # truncates re http://bugzilla.mozilla.org/show_bug.cgi?id=9352 + $dbh->bz_alter_column('bugs', 'version', {TYPE => 'varchar(64)', NOTNULL => 1}); + + _update_bugs_activity_to_only_record_changes(); + + # bug 90933: Make disabledtext NOT NULL + if (!$dbh->bz_column_info('profiles', 'disabledtext')->{NOTNULL}) { + $dbh->bz_alter_column("profiles", "disabledtext", + {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); + } + + $dbh->bz_add_column("bugs", "reporter_accessible", + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + $dbh->bz_add_column("bugs", "cclist_accessible", + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + + $dbh->bz_add_column("bugs_activity", "attach_id", {TYPE => 'INT5'}); + + _delete_logincookies_cryptpassword_and_handle_invalid_cookies(); + + # qacontact/assignee should always be able to see bugs: bug 97471 + $dbh->bz_drop_column("bugs", "qacontact_accessible"); + $dbh->bz_drop_column("bugs", "assignee_accessible"); + + # 2002-02-20 jeff.hedlund@matrixsi.com - bug 24789 time tracking + $dbh->bz_add_column("longdescs", "work_time", + {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'}); + $dbh->bz_add_column("bugs", "estimated_time", + {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'}); + $dbh->bz_add_column("bugs", "remaining_time", + {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'}); + $dbh->bz_add_column("bugs", "deadline", {TYPE => 'DATETIME'}); + + _use_ip_instead_of_hostname_in_logincookies(); + + $dbh->bz_add_column('longdescs', 'isprivate', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + $dbh->bz_add_column('attachments', 'isprivate', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + + $dbh->bz_add_column("bugs", "alias", {TYPE => "varchar(20)"}); + $dbh->bz_add_index('bugs', 'bugs_alias_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(alias)]}); + + _move_quips_into_db(); + + $dbh->bz_drop_column("namedqueries", "watchfordiffs"); + + _use_ids_for_products_and_components(); + _convert_groups_system_from_groupset(); + _convert_attachment_statuses_to_flags(); + _remove_spaces_and_commas_from_flagtypes(); + _setup_usebuggroups_backward_compatibility(); + _remove_user_series_map(); + + # 2006-08-03 remi_zara@mac.com bug 346241, make series.creator nullable + # This must happen before calling _copy_old_charts_into_database(). + if ($dbh->bz_column_info('series', 'creator')->{NOTNULL}) { + $dbh->bz_alter_column('series', 'creator', {TYPE => 'INT3'}); + $dbh->do("UPDATE series SET creator = NULL WHERE creator = 0"); + } + + _copy_old_charts_into_database(); + + _add_user_group_map_grant_type(); + _add_group_group_map_grant_type(); + + $dbh->bz_add_column("profiles", "extern_id", {TYPE => 'varchar(64)'}); - _stop_storing_inactive_flags(); - _change_short_desc_from_mediumtext_to_varchar(); + $dbh->bz_add_column('flagtypes', 'grant_group_id', {TYPE => 'INT3'}); + $dbh->bz_add_column('flagtypes', 'request_group_id', {TYPE => 'INT3'}); - # 2006-07-01 wurblzap@gmail.com -- Bug 69000 - $dbh->bz_add_column('namedqueries', 'id', - {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - _move_namedqueries_linkinfooter_to_its_own_table(); + # mailto is no longer just userids + $dbh->bz_rename_column('whine_schedules', 'mailto_userid', 'mailto'); + $dbh->bz_add_column('whine_schedules', 'mailto_type', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'}); + + _add_longdescs_already_wrapped(); - _add_classifications_sortkey(); - _move_data_nomail_into_db(); + # Moved enum types to separate tables so we need change the old enum + # types to standard varchars in the bugs table. + $dbh->bz_alter_column('bugs', 'bug_status', + {TYPE => 'varchar(64)', NOTNULL => 1}); + + # 2005-03-23 Tomas.Kopal@altap.cz - add default value to resolution, + # bug 286695 + $dbh->bz_alter_column('bugs', 'resolution', + {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"}); + $dbh->bz_alter_column('bugs', 'priority', + {TYPE => 'varchar(64)', NOTNULL => 1}); + $dbh->bz_alter_column('bugs', 'bug_severity', + {TYPE => 'varchar(64)', NOTNULL => 1}); + $dbh->bz_alter_column('bugs', 'rep_platform', + {TYPE => 'varchar(64)', NOTNULL => 1}, ''); + $dbh->bz_alter_column('bugs', 'op_sys', {TYPE => 'varchar(64)', NOTNULL => 1}); + + # When migrating quips from the '$datadir/comments' file to the DB, + # the user ID should be NULL instead of 0 (which is an invalid user ID). + if ($dbh->bz_column_info('quips', 'userid')->{NOTNULL}) { + $dbh->bz_alter_column('quips', 'userid', {TYPE => 'INT3'}); + print "Changing owner to NULL for quips where the owner is", " unknown...\n"; + $dbh->do('UPDATE quips SET userid = NULL WHERE userid = 0'); + } + + _convert_attachments_filename_from_mediumtext(); + + $dbh->bz_add_column('quips', 'approved', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + + # 2002-12-20 Bug 180870 - remove manual shadowdb replication code + $dbh->bz_drop_table("shadowlog"); + + _rename_votes_count_and_force_group_refresh(); + + # 2004/02/15 - Summaries shouldn't be null - see bug 220232 + if (!exists $dbh->bz_column_info('bugs', 'short_desc')->{NOTNULL}) { + $dbh->bz_alter_column('bugs', 'short_desc', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); + } + + $dbh->bz_add_column('products', 'classification_id', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '1'}); + + _fix_group_with_empty_name(); + + $dbh->bz_add_index('bugs_activity', 'bugs_activity_who_idx', [qw(who)]); + + # Add defaults for some fields that should have them but didn't. + $dbh->bz_alter_column('bugs', 'status_whiteboard', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); + if ($dbh->bz_column_info('bugs', 'votes')) { + $dbh->bz_alter_column('bugs', 'votes', + {TYPE => 'INT3', NOTNULL => 1, DEFAULT => '0'}); + } + + $dbh->bz_alter_column('bugs', 'lastdiffed', {TYPE => 'DATETIME'}); + + # 2005-03-09 qa_contact should be NULL instead of 0, bug 285534 + if ($dbh->bz_column_info('bugs', 'qa_contact')->{NOTNULL}) { + $dbh->bz_alter_column('bugs', 'qa_contact', {TYPE => 'INT3'}); + $dbh->do("UPDATE bugs SET qa_contact = NULL WHERE qa_contact = 0"); + } + + # 2005-03-27 initialqacontact should be NULL instead of 0, bug 287483 + if ($dbh->bz_column_info('components', 'initialqacontact')->{NOTNULL}) { + $dbh->bz_alter_column('components', 'initialqacontact', {TYPE => 'INT3'}); + } + $dbh->do("UPDATE components SET initialqacontact = NULL " + . "WHERE initialqacontact = 0"); + + _migrate_email_prefs_to_new_table(); + _initialize_new_email_prefs(); + _change_all_mysql_booleans_to_tinyint(); + + # make classification_id field type be consistent with DB:Schema + $dbh->bz_alter_column('products', 'classification_id', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '1'}); + + # initialowner was accidentally NULL when we checked-in Schema, + # when it really should be NOT NULL. + $dbh->bz_alter_column('components', 'initialowner', + {TYPE => 'INT3', NOTNULL => 1}, 0); + + # 2005-03-28 - bug 238800 - index flags.type_id for editflagtypes.cgi + $dbh->bz_add_index('flags', 'flags_type_id_idx', [qw(type_id)]); + + # For a short time, the flags_type_id_idx was misnamed in upgraded installs. + $dbh->bz_drop_index('flags', 'type_id'); + + # 2005-04-28 - LpSolit@gmail.com - Bug 7233: add an index to versions + $dbh->bz_alter_column('versions', 'value', + {TYPE => 'varchar(64)', NOTNULL => 1}); + _add_versions_product_id_index(); + + if (!exists $dbh->bz_column_info('milestones', 'sortkey')->{DEFAULT}) { + $dbh->bz_alter_column('milestones', 'sortkey', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); + } + + # 2005-06-14 - LpSolit@gmail.com - Bug 292544 + $dbh->bz_alter_column('bugs', 'creation_ts', {TYPE => 'DATETIME'}); + + _fix_whine_queries_title_and_op_sys_value(); + _fix_attachments_submitter_id_idx(); + _copy_attachments_thedata_to_attach_data(); + _fix_broken_all_closed_series(); + + # 2005-08-14 bugreport@peshkin.net -- Bug 304583 + # Get rid of leftover DERIVED group permissions + use constant GRANT_DERIVED => 1; + $dbh->do("DELETE FROM user_group_map WHERE grant_type = " . GRANT_DERIVED); + + _rederive_regex_groups(); + + # PUBLIC is a reserved word in Oracle. + $dbh->bz_rename_column('series', 'public', 'is_public'); + + # 2005-11-04 LpSolit@gmail.com - Bug 305927 + $dbh->bz_alter_column('groups', 'userregexp', + {TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"}); + + _clean_control_characters_from_short_desc(); - # The products table lacked sensible defaults. - if ($dbh->bz_column_info('products', 'milestoneurl')) { - $dbh->bz_alter_column('products', 'milestoneurl', - {TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"}); + # 2005-12-07 altlst@sonic.net -- Bug 225221 + $dbh->bz_add_column('longdescs', 'comment_id', + {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + + _stop_storing_inactive_flags(); + _change_short_desc_from_mediumtext_to_varchar(); + + # 2006-07-01 wurblzap@gmail.com -- Bug 69000 + $dbh->bz_add_column('namedqueries', 'id', + {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + _move_namedqueries_linkinfooter_to_its_own_table(); + + _add_classifications_sortkey(); + _move_data_nomail_into_db(); + + # The products table lacked sensible defaults. + if ($dbh->bz_column_info('products', 'milestoneurl')) { + $dbh->bz_alter_column('products', 'milestoneurl', + {TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"}); + } + if ($dbh->bz_column_info('products', 'disallownew')) { + $dbh->bz_alter_column('products', 'disallownew', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0}); + + if ($dbh->bz_column_info('products', 'votesperuser')) { + $dbh->bz_alter_column('products', 'votesperuser', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); + $dbh->bz_alter_column('products', 'votestoconfirm', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); } - if ($dbh->bz_column_info('products', 'disallownew')){ - $dbh->bz_alter_column('products', 'disallownew', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0}); + } - if ($dbh->bz_column_info('products', 'votesperuser')) { - $dbh->bz_alter_column('products', 'votesperuser', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); - $dbh->bz_alter_column('products', 'votestoconfirm', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); - } - } + # 2006-08-04 LpSolit@gmail.com - Bug 305941 + $dbh->bz_drop_column('profiles', 'refreshed_when'); + $dbh->bz_drop_column('groups', 'last_changed'); - # 2006-08-04 LpSolit@gmail.com - Bug 305941 - $dbh->bz_drop_column('profiles', 'refreshed_when'); - $dbh->bz_drop_column('groups', 'last_changed'); + # 2006-08-06 LpSolit@gmail.com - Bug 347521 + $dbh->bz_alter_column('flagtypes', 'id', + {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - # 2006-08-06 LpSolit@gmail.com - Bug 347521 - $dbh->bz_alter_column('flagtypes', 'id', - {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + $dbh->bz_alter_column('keyworddefs', 'id', + {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - $dbh->bz_alter_column('keyworddefs', 'id', - {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + # 2006-08-19 LpSolit@gmail.com - Bug 87795 + $dbh->bz_alter_column('tokens', 'userid', {TYPE => 'INT3'}); - # 2006-08-19 LpSolit@gmail.com - Bug 87795 - $dbh->bz_alter_column('tokens', 'userid', {TYPE => 'INT3'}); + $dbh->bz_drop_index('bugs', 'bugs_short_desc_idx'); - $dbh->bz_drop_index('bugs', 'bugs_short_desc_idx'); + # The profiles table was missing some defaults. + $dbh->bz_alter_column('profiles', 'disabledtext', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); + $dbh->bz_alter_column('profiles', 'realname', + {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}); - # The profiles table was missing some defaults. - $dbh->bz_alter_column('profiles', 'disabledtext', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); - $dbh->bz_alter_column('profiles', 'realname', - {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}); + _update_longdescs_who_index(); - _update_longdescs_who_index(); + $dbh->bz_add_column('setting', 'subclass', {TYPE => 'varchar(32)'}); - $dbh->bz_add_column('setting', 'subclass', {TYPE => 'varchar(32)'}); + $dbh->bz_alter_column('longdescs', 'thetext', + {TYPE => 'LONGTEXT', NOTNULL => 1}, ''); - $dbh->bz_alter_column('longdescs', 'thetext', - {TYPE => 'LONGTEXT', NOTNULL => 1}, ''); + # 2006-10-20 LpSolit@gmail.com - Bug 189627 + $dbh->bz_add_column('group_control_map', 'editcomponents', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + $dbh->bz_add_column('group_control_map', 'editbugs', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + $dbh->bz_add_column('group_control_map', 'canconfirm', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - # 2006-10-20 LpSolit@gmail.com - Bug 189627 - $dbh->bz_add_column('group_control_map', 'editcomponents', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - $dbh->bz_add_column('group_control_map', 'editbugs', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - $dbh->bz_add_column('group_control_map', 'canconfirm', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + # 2006-11-07 LpSolit@gmail.com - Bug 353656 + $dbh->bz_add_column('longdescs', 'type', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'}); + $dbh->bz_add_column('longdescs', 'extra_data', {TYPE => 'varchar(255)'}); - # 2006-11-07 LpSolit@gmail.com - Bug 353656 - $dbh->bz_add_column('longdescs', 'type', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'}); - $dbh->bz_add_column('longdescs', 'extra_data', {TYPE => 'varchar(255)'}); + $dbh->bz_add_column('versions', 'id', + {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + $dbh->bz_add_column('milestones', 'id', + {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - $dbh->bz_add_column('versions', 'id', - {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - $dbh->bz_add_column('milestones', 'id', - {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + _fix_uppercase_custom_field_names(); + _fix_uppercase_index_names(); - _fix_uppercase_custom_field_names(); - _fix_uppercase_index_names(); + # 2007-05-17 LpSolit@gmail.com - Bug 344965 + _initialize_workflow_for_upgrade($old_params); - # 2007-05-17 LpSolit@gmail.com - Bug 344965 - _initialize_workflow_for_upgrade($old_params); + # 2007-08-08 LpSolit@gmail.com - Bug 332149 + $dbh->bz_add_column('groups', 'icon_url', {TYPE => 'TINYTEXT'}); - # 2007-08-08 LpSolit@gmail.com - Bug 332149 - $dbh->bz_add_column('groups', 'icon_url', {TYPE => 'TINYTEXT'}); + # 2007-08-21 wurblzap@gmail.com - Bug 365378 + _make_lang_setting_dynamic(); - # 2007-08-21 wurblzap@gmail.com - Bug 365378 - _make_lang_setting_dynamic(); + # 2007-11-29 xiaoou.wu@oracle.com - Bug 153129 + _change_text_types(); - # 2007-11-29 xiaoou.wu@oracle.com - Bug 153129 - _change_text_types(); + # 2007-09-09 LpSolit@gmail.com - Bug 99215 + _fix_attachment_modification_date(); - # 2007-09-09 LpSolit@gmail.com - Bug 99215 - _fix_attachment_modification_date(); + $dbh->bz_drop_index('longdescs', 'longdescs_thetext_idx'); + _populate_bugs_fulltext(); - $dbh->bz_drop_index('longdescs', 'longdescs_thetext_idx'); - _populate_bugs_fulltext(); + # 2008-01-18 xiaoou.wu@oracle.com - Bug 414292 + $dbh->bz_alter_column('series', 'query', {TYPE => 'MEDIUMTEXT', NOTNULL => 1}); - # 2008-01-18 xiaoou.wu@oracle.com - Bug 414292 - $dbh->bz_alter_column('series', 'query', - { TYPE => 'MEDIUMTEXT', NOTNULL => 1 }); + # Add FK to multi select field tables + _add_foreign_keys_to_multiselects(); - # Add FK to multi select field tables - _add_foreign_keys_to_multiselects(); + # 2008-07-28 tfu@redhat.com - Bug 431669 + $dbh->bz_alter_column('group_control_map', 'product_id', + {TYPE => 'INT2', NOTNULL => 1}); - # 2008-07-28 tfu@redhat.com - Bug 431669 - $dbh->bz_alter_column('group_control_map', 'product_id', - { TYPE => 'INT2', NOTNULL => 1 }); + # 2008-09-07 LpSolit@gmail.com - Bug 452893 + _fix_illegal_flag_modification_dates(); - # 2008-09-07 LpSolit@gmail.com - Bug 452893 - _fix_illegal_flag_modification_dates(); + _add_visiblity_value_to_value_tables(); - _add_visiblity_value_to_value_tables(); + # 2009-03-02 arbingersys@gmail.com - Bug 423613 + _add_extern_id_index(); - # 2009-03-02 arbingersys@gmail.com - Bug 423613 - _add_extern_id_index(); + # 2009-03-31 LpSolit@gmail.com - Bug 478972 + $dbh->bz_alter_column('group_control_map', 'entry', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + $dbh->bz_alter_column('group_control_map', 'canedit', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - # 2009-03-31 LpSolit@gmail.com - Bug 478972 - $dbh->bz_alter_column('group_control_map', 'entry', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - $dbh->bz_alter_column('group_control_map', 'canedit', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + # 2009-01-16 oreomike@gmail.com - Bug 302420 + $dbh->bz_add_column('whine_events', 'mailifnobugs', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - # 2009-01-16 oreomike@gmail.com - Bug 302420 - $dbh->bz_add_column('whine_events', 'mailifnobugs', - { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + _convert_disallownew_to_isactive(); - _convert_disallownew_to_isactive(); + $dbh->bz_alter_column('bugs_activity', 'added', {TYPE => 'varchar(255)'}); + $dbh->bz_add_index('bugs_activity', 'bugs_activity_added_idx', ['added']); - $dbh->bz_alter_column('bugs_activity', 'added', - { TYPE => 'varchar(255)' }); - $dbh->bz_add_index('bugs_activity', 'bugs_activity_added_idx', ['added']); + # 2009-09-28 LpSolit@gmail.com - Bug 519032 + $dbh->bz_drop_column('series', 'last_viewed'); - # 2009-09-28 LpSolit@gmail.com - Bug 519032 - $dbh->bz_drop_column('series', 'last_viewed'); + # 2009-09-28 LpSolit@gmail.com - Bug 399073 + _fix_logincookies_ipaddr(); - # 2009-09-28 LpSolit@gmail.com - Bug 399073 - _fix_logincookies_ipaddr(); + # 2009-11-01 LpSolit@gmail.com - Bug 525025 + _fix_invalid_custom_field_names(); - # 2009-11-01 LpSolit@gmail.com - Bug 525025 - _fix_invalid_custom_field_names(); + _set_attachment_comment_types(); - _set_attachment_comment_types(); + $dbh->bz_drop_column('products', 'milestoneurl'); - $dbh->bz_drop_column('products', 'milestoneurl'); + _add_allows_unconfirmed_to_product_table(); + _convert_flagtypes_fks_to_set_null(); + _fix_decimal_types(); + _fix_series_creator_fk(); - _add_allows_unconfirmed_to_product_table(); - _convert_flagtypes_fks_to_set_null(); - _fix_decimal_types(); - _fix_series_creator_fk(); + # 2009-11-14 dkl@redhat.com - Bug 310450 + $dbh->bz_add_column('bugs_activity', 'comment_id', {TYPE => 'INT4'}); - # 2009-11-14 dkl@redhat.com - Bug 310450 - $dbh->bz_add_column('bugs_activity', 'comment_id', {TYPE => 'INT4'}); + # 2010-04-07 LpSolit@gmail.com - Bug 69621 + $dbh->bz_drop_column('bugs', 'keywords'); - # 2010-04-07 LpSolit@gmail.com - Bug 69621 - $dbh->bz_drop_column('bugs', 'keywords'); + # 2010-05-07 ewong@pw-wspx.org - Bug 463945 + $dbh->bz_alter_column('group_control_map', 'membercontrol', + {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA}); + $dbh->bz_alter_column('group_control_map', 'othercontrol', + {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA}); - # 2010-05-07 ewong@pw-wspx.org - Bug 463945 - $dbh->bz_alter_column('group_control_map', 'membercontrol', - {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA}); - $dbh->bz_alter_column('group_control_map', 'othercontrol', - {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA}); + # Add NOT NULL to some columns that need it, and DEFAULT to + # attachments.ispatch. + $dbh->bz_alter_column('attachments', 'ispatch', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + $dbh->bz_alter_column('keyworddefs', 'description', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); + $dbh->bz_alter_column('products', 'description', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); - # Add NOT NULL to some columns that need it, and DEFAULT to - # attachments.ispatch. - $dbh->bz_alter_column('attachments', 'ispatch', - { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - $dbh->bz_alter_column('keyworddefs', 'description', - { TYPE => 'MEDIUMTEXT', NOTNULL => 1 }, ''); - $dbh->bz_alter_column('products', 'description', - { TYPE => 'MEDIUMTEXT', NOTNULL => 1 }, ''); + # Change the default of allows_unconfirmed to TRUE as part + # of the new workflow. + $dbh->bz_alter_column('products', 'allows_unconfirmed', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - # Change the default of allows_unconfirmed to TRUE as part - # of the new workflow. - $dbh->bz_alter_column('products', 'allows_unconfirmed', - { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE' }); + # 2010-07-18 LpSolit@gmail.com - Bug 119703 + _remove_attachment_isurl(); - # 2010-07-18 LpSolit@gmail.com - Bug 119703 - _remove_attachment_isurl(); + # 2009-05-07 ghendricks@novell.com - Bug 77193 + _add_isactive_to_product_fields(); - # 2009-05-07 ghendricks@novell.com - Bug 77193 - _add_isactive_to_product_fields(); + # 2010-10-09 LpSolit@gmail.com - Bug 505165 + $dbh->bz_alter_column('flags', 'setter_id', {TYPE => 'INT3', NOTNULL => 1}); - # 2010-10-09 LpSolit@gmail.com - Bug 505165 - $dbh->bz_alter_column('flags', 'setter_id', {TYPE => 'INT3', NOTNULL => 1}); + # 2010-10-09 LpSolit@gmail.com - Bug 451735 + _fix_series_indexes(); - # 2010-10-09 LpSolit@gmail.com - Bug 451735 - _fix_series_indexes(); + $dbh->bz_add_column('bug_see_also', 'id', + {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - $dbh->bz_add_column('bug_see_also', 'id', - {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + _rename_tags_to_tag(); - _rename_tags_to_tag(); + # 2011-01-29 LpSolit@gmail.com - Bug 616185 + _migrate_user_tags(); - # 2011-01-29 LpSolit@gmail.com - Bug 616185 - _migrate_user_tags(); + _populate_bug_see_also_class(); - _populate_bug_see_also_class(); + # 2011-06-15 dkl@mozilla.com - Bug 658929 + _migrate_disabledtext_boolean(); - # 2011-06-15 dkl@mozilla.com - Bug 658929 - _migrate_disabledtext_boolean(); + # 2011-10-11 miketosh - Bug 690173 + _on_delete_set_null_for_audit_log_userid(); - # 2011-10-11 miketosh - Bug 690173 - _on_delete_set_null_for_audit_log_userid(); + # 2011-11-01 glob@mozilla.com - Bug 240437 + $dbh->bz_add_column('profiles', 'last_seen_date', {TYPE => 'DATETIME'}); - # 2011-11-01 glob@mozilla.com - Bug 240437 - $dbh->bz_add_column('profiles', 'last_seen_date', {TYPE => 'DATETIME'}); + # 2011-11-28 dkl@mozilla.com - Bug 685611 + _fix_notnull_defaults(); - # 2011-11-28 dkl@mozilla.com - Bug 685611 - _fix_notnull_defaults(); - - # 2012-02-15 LpSolit@gmail.com - Bug 722113 - if ($dbh->bz_index_info('profile_search', 'profile_search_user_id')) { - $dbh->bz_drop_index('profile_search', 'profile_search_user_id'); - $dbh->bz_add_index('profile_search', 'profile_search_user_id_idx', [qw(user_id)]); - } + # 2012-02-15 LpSolit@gmail.com - Bug 722113 + if ($dbh->bz_index_info('profile_search', 'profile_search_user_id')) { + $dbh->bz_drop_index('profile_search', 'profile_search_user_id'); + $dbh->bz_add_index('profile_search', 'profile_search_user_id_idx', + [qw(user_id)]); + } - # 2012-06-06 dkl@mozilla.com - Bug 762288 - $dbh->bz_alter_column('bugs_activity', 'removed', - { TYPE => 'varchar(255)' }); - $dbh->bz_add_index('bugs_activity', 'bugs_activity_removed_idx', ['removed']); + # 2012-06-06 dkl@mozilla.com - Bug 762288 + $dbh->bz_alter_column('bugs_activity', 'removed', {TYPE => 'varchar(255)'}); + $dbh->bz_add_index('bugs_activity', 'bugs_activity_removed_idx', ['removed']); - # 2012-06-13 dkl@mozilla.com - Bug 764457 - $dbh->bz_add_column('bugs_activity', 'id', - {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + # 2012-06-13 dkl@mozilla.com - Bug 764457 + $dbh->bz_add_column('bugs_activity', 'id', + {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - # 2012-06-13 dkl@mozilla.com - Bug 764466 - $dbh->bz_add_column('profiles_activity', 'id', - {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + # 2012-06-13 dkl@mozilla.com - Bug 764466 + $dbh->bz_add_column('profiles_activity', 'id', + {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - # 2012-07-24 dkl@mozilla.com - Bug 776972 - # BMO - we change this to BIGSERIAL further down - #$dbh->bz_alter_column('bugs_activity', 'id', - # {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + # 2012-07-24 dkl@mozilla.com - Bug 776972 + # BMO - we change this to BIGSERIAL further down + #$dbh->bz_alter_column('bugs_activity', 'id', + # {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - # 2012-07-24 dkl@mozilla.com - Bug 776982 - _fix_longdescs_primary_key(); + # 2012-07-24 dkl@mozilla.com - Bug 776982 + _fix_longdescs_primary_key(); - # 2012-08-02 dkl@mozilla.com - Bug 756953 - _fix_dependencies_dupes(); + # 2012-08-02 dkl@mozilla.com - Bug 756953 + _fix_dependencies_dupes(); - # 2013-02-04 dkl@mozilla.com - Bug 824346 - _fix_flagclusions_indexes(); + # 2013-02-04 dkl@mozilla.com - Bug 824346 + _fix_flagclusions_indexes(); - # 2012-04-15 Frank@Frank-Becker.de - Bug 740536 - $dbh->bz_add_index('audit_log', 'audit_log_class_idx', ['class', 'at_time']); + # 2012-04-15 Frank@Frank-Becker.de - Bug 740536 + $dbh->bz_add_index('audit_log', 'audit_log_class_idx', ['class', 'at_time']); - # 2013-08-16 glob@mozilla.com - Bug 905925 - $dbh->bz_add_index('attachments', 'attachments_ispatch_idx', ['ispatch']); + # 2013-08-16 glob@mozilla.com - Bug 905925 + $dbh->bz_add_index('attachments', 'attachments_ispatch_idx', ['ispatch']); - # 2014-06-09 dylan@mozilla.com - Bug 1022923 - $dbh->bz_add_index('bug_user_last_visit', - 'bug_user_last_visit_last_visit_ts_idx', - ['last_visit_ts']); + # 2014-06-09 dylan@mozilla.com - Bug 1022923 + $dbh->bz_add_index('bug_user_last_visit', + 'bug_user_last_visit_last_visit_ts_idx', + ['last_visit_ts']); - # 2014-07-14 sgreen@redhat.com - Bug 726696 - $dbh->bz_alter_column('tokens', 'tokentype', - {TYPE => 'varchar(16)', NOTNULL => 1}); + # 2014-07-14 sgreen@redhat.com - Bug 726696 + $dbh->bz_alter_column('tokens', 'tokentype', + {TYPE => 'varchar(16)', NOTNULL => 1}); - # 2014-07-27 LpSolit@gmail.com - Bug 1044561 - _fix_user_api_keys_indexes(); + # 2014-07-27 LpSolit@gmail.com - Bug 1044561 + _fix_user_api_keys_indexes(); - # 2018-06-14 dylan@mozilla.com - Bug 1468818 - $dbh->bz_add_column('longdescs', 'is_markdown', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + # 2018-06-14 dylan@mozilla.com - Bug 1468818 + $dbh->bz_add_column('longdescs', 'is_markdown', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - # 2014-10-?? dkl@mozilla.com - Bug 1062940 - $dbh->bz_alter_column('bugs', 'alias', { TYPE => 'varchar(40)' }); + # 2014-10-?? dkl@mozilla.com - Bug 1062940 + $dbh->bz_alter_column('bugs', 'alias', {TYPE => 'varchar(40)'}); - # 2015-05-13 dylan@mozilla.com - Bug 1160430 - $dbh->bz_add_column('keyworddefs', 'is_active', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + # 2015-05-13 dylan@mozilla.com - Bug 1160430 + $dbh->bz_add_column('keyworddefs', 'is_active', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - $dbh->bz_add_column('user_api_keys', 'app_id', - {TYPE => 'varchar(64)'}); - $dbh->bz_add_index('user_api_keys', 'user_api_keys_user_id_app_id_idx', - [qw(user_id app_id)]); + $dbh->bz_add_column('user_api_keys', 'app_id', {TYPE => 'varchar(64)'}); + $dbh->bz_add_index('user_api_keys', 'user_api_keys_user_id_app_id_idx', + [qw(user_id app_id)]); - _add_attach_size(); + _add_attach_size(); - _fix_disable_mail(); + _fix_disable_mail(); - # 2015-07-25 dylan@mozilla.com - Bug 1179856 - $dbh->bz_alter_column('tokens', 'token', - {TYPE => 'varchar(22)', NOTNULL => 1, PRIMARYKEY => 1}); + # 2015-07-25 dylan@mozilla.com - Bug 1179856 + $dbh->bz_alter_column('tokens', 'token', + {TYPE => 'varchar(22)', NOTNULL => 1, PRIMARYKEY => 1}); - # 2015-08-20 dylan@mozilla.com - Bug 1196092 - $dbh->bz_alter_column('logincookies', 'cookie', - {TYPE => 'varchar(22)', NOTNULL => 1}); - $dbh->bz_add_index('logincookies', 'logincookies_cookie_idx', - {TYPE => 'UNIQUE', FIELDS => ['cookie']}); - $dbh->bz_add_column('logincookies', 'id', - {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + # 2015-08-20 dylan@mozilla.com - Bug 1196092 + $dbh->bz_alter_column('logincookies', 'cookie', + {TYPE => 'varchar(22)', NOTNULL => 1}); + $dbh->bz_add_index('logincookies', 'logincookies_cookie_idx', + {TYPE => 'UNIQUE', FIELDS => ['cookie']}); + $dbh->bz_add_column('logincookies', 'id', + {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - $dbh->bz_add_column('user_api_keys', 'last_used_ip', - {TYPE => 'varchar(40)'}); + $dbh->bz_add_column('user_api_keys', 'last_used_ip', {TYPE => 'varchar(40)'}); - _add_restrict_ipaddr(); + _add_restrict_ipaddr(); - $dbh->bz_add_column('profiles', 'password_change_required', - { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE' }); - $dbh->bz_add_column('profiles', 'password_change_reason', - { TYPE => 'varchar(64)' }); + $dbh->bz_add_column('profiles', 'password_change_required', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + $dbh->bz_add_column('profiles', 'password_change_reason', + {TYPE => 'varchar(64)'}); - $dbh->bz_add_column('profiles', 'mfa', { TYPE => 'varchar(8)', , DEFAULT => "''" }); + $dbh->bz_add_column('profiles', 'mfa', + {TYPE => 'varchar(8)',, DEFAULT => "''"}); - $dbh->bz_add_column('profiles', 'mfa_required_date', { TYPE => 'DATETIME' }); - _migrate_group_owners(); + $dbh->bz_add_column('profiles', 'mfa_required_date', {TYPE => 'DATETIME'}); + _migrate_group_owners(); - $dbh->bz_add_column('groups', 'idle_member_removal', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'}); + $dbh->bz_add_column('groups', 'idle_member_removal', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'}); - _migrate_preference_categories(); + _migrate_preference_categories(); - # 2016-09-01 dkl@mozilla.com - Bug 1268317 - $dbh->bz_add_column('components', 'triage_owner_id', - {TYPE => 'INT3'}); + # 2016-09-01 dkl@mozilla.com - Bug 1268317 + $dbh->bz_add_column('components', 'triage_owner_id', {TYPE => 'INT3'}); - $dbh->bz_add_column('profiles', 'nickname', - {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}); - $dbh->bz_add_index('profiles', 'profiles_nickname_idx', [qw(nickname)]); + $dbh->bz_add_column('profiles', 'nickname', + {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}); + $dbh->bz_add_index('profiles', 'profiles_nickname_idx', [qw(nickname)]); - $dbh->bz_add_index('profiles', 'profiles_realname_ft_idx', - {TYPE => 'FULLTEXT', FIELDS => ['realname']}); - _migrate_nicknames(); + $dbh->bz_add_index('profiles', 'profiles_realname_ft_idx', + {TYPE => 'FULLTEXT', FIELDS => ['realname']}); + _migrate_nicknames(); - ################################################################ - # New --TABLE-- changes should go *** A B O V E *** this point # - ################################################################ + ################################################################ + # New --TABLE-- changes should go *** A B O V E *** this point # + ################################################################ - Bugzilla::Hook::process('install_update_db'); + Bugzilla::Hook::process('install_update_db'); - # We do this here because otherwise the foreign key from - # products.classification_id to classifications.id will fail - # (because products.classification_id defaults to "1", so on upgraded - # installations it's already been set before the first Classification - # exists). - Bugzilla::Install::create_default_classification(); + # We do this here because otherwise the foreign key from + # products.classification_id to classifications.id will fail + # (because products.classification_id defaults to "1", so on upgraded + # installations it's already been set before the first Classification + # exists). + Bugzilla::Install::create_default_classification(); - $dbh->bz_setup_foreign_keys(); + $dbh->bz_setup_foreign_keys(); } # Subroutines should be ordered in the order that they are called. # Thus, newer subroutines should be at the bottom. sub _update_pre_checksetup_bugzillas { - my $dbh = Bugzilla->dbh; - # really old fields that were added before checksetup.pl existed - # but aren't in very old bugzilla's (like 2.1) - # Steve Stock (sstock@iconnect-inc.com) - - $dbh->bz_add_column('bugs', 'target_milestone', - {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"}); - $dbh->bz_add_column('bugs', 'qa_contact', {TYPE => 'INT3'}); - $dbh->bz_add_column('bugs', 'status_whiteboard', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); - if (!$dbh->bz_column_info('products', 'isactive')){ - $dbh->bz_add_column('products', 'disallownew', - {TYPE => 'BOOLEAN', NOTNULL => 1}, 0); - } - - $dbh->bz_add_column('components', 'initialqacontact', - {TYPE => 'TINYTEXT'}); - $dbh->bz_add_column('components', 'description', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); + my $dbh = Bugzilla->dbh; + + # really old fields that were added before checksetup.pl existed + # but aren't in very old bugzilla's (like 2.1) + # Steve Stock (sstock@iconnect-inc.com) + + $dbh->bz_add_column('bugs', 'target_milestone', + {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"}); + $dbh->bz_add_column('bugs', 'qa_contact', {TYPE => 'INT3'}); + $dbh->bz_add_column('bugs', 'status_whiteboard', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); + if (!$dbh->bz_column_info('products', 'isactive')) { + $dbh->bz_add_column('products', 'disallownew', + {TYPE => 'BOOLEAN', NOTNULL => 1}, 0); + } + + $dbh->bz_add_column('components', 'initialqacontact', {TYPE => 'TINYTEXT'}); + $dbh->bz_add_column('components', 'description', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); } sub _add_bug_vote_cache { - my $dbh = Bugzilla->dbh; - # 1999-10-11 Restructured voting database to add a cached value in each - # bug recording how many total votes that bug has. While I'm at it, - # I removed the unused "area" field from the bugs database. It is - # distressing to realize that the bugs table has reached the maximum - # number of indices allowed by MySQL (16), which may make future - # enhancements awkward. - # (P.S. All is not lost; it appears that the latest betas of MySQL - # support a new table format which will allow 32 indices.) - - if ($dbh->bz_column_info('bugs', 'area')) { - $dbh->bz_drop_column('bugs', 'area'); - $dbh->bz_add_column('bugs', 'votes', {TYPE => 'INT3', NOTNULL => 1, - DEFAULT => 0}); - $dbh->bz_add_index('bugs', 'bugs_votes_idx', [qw(votes)]); - $dbh->bz_add_column('products', 'votesperuser', - {TYPE => 'INT2', NOTNULL => 1}, 0); - } + my $dbh = Bugzilla->dbh; + + # 1999-10-11 Restructured voting database to add a cached value in each + # bug recording how many total votes that bug has. While I'm at it, + # I removed the unused "area" field from the bugs database. It is + # distressing to realize that the bugs table has reached the maximum + # number of indices allowed by MySQL (16), which may make future + # enhancements awkward. + # (P.S. All is not lost; it appears that the latest betas of MySQL + # support a new table format which will allow 32 indices.) + + if ($dbh->bz_column_info('bugs', 'area')) { + $dbh->bz_drop_column('bugs', 'area'); + $dbh->bz_add_column('bugs', 'votes', + {TYPE => 'INT3', NOTNULL => 1, DEFAULT => 0}); + $dbh->bz_add_index('bugs', 'bugs_votes_idx', [qw(votes)]); + $dbh->bz_add_column('products', 'votesperuser', {TYPE => 'INT2', NOTNULL => 1}, + 0); + } } sub _update_product_name_definition { - my $dbh = Bugzilla->dbh; - # The product name used to be very different in various tables. - # - # It was varchar(16) in bugs - # tinytext in components - # tinytext in products - # tinytext in versions - # - # tinytext is equivalent to varchar(255), which is quite huge, so I change - # them all to varchar(64). - - # Only do this if these fields still exist - they're removed in - # a later change - if ($dbh->bz_column_info('products', 'product')) { - $dbh->bz_alter_column('bugs', 'product', - {TYPE => 'varchar(64)', NOTNULL => 1}); - $dbh->bz_alter_column('components', 'program', {TYPE => 'varchar(64)'}); - $dbh->bz_alter_column('products', 'product', {TYPE => 'varchar(64)'}); - $dbh->bz_alter_column('versions', 'program', - {TYPE => 'varchar(64)', NOTNULL => 1}); - } + my $dbh = Bugzilla->dbh; + + # The product name used to be very different in various tables. + # + # It was varchar(16) in bugs + # tinytext in components + # tinytext in products + # tinytext in versions + # + # tinytext is equivalent to varchar(255), which is quite huge, so I change + # them all to varchar(64). + + # Only do this if these fields still exist - they're removed in + # a later change + if ($dbh->bz_column_info('products', 'product')) { + $dbh->bz_alter_column('bugs', 'product', {TYPE => 'varchar(64)', NOTNULL => 1}); + $dbh->bz_alter_column('components', 'program', {TYPE => 'varchar(64)'}); + $dbh->bz_alter_column('products', 'product', {TYPE => 'varchar(64)'}); + $dbh->bz_alter_column('versions', 'program', + {TYPE => 'varchar(64)', NOTNULL => 1}); + } } # A helper for the function below. sub _write_one_longdesc { - my ($id, $who, $when, $buffer) = (@_); - my $dbh = Bugzilla->dbh; - $buffer = trim($buffer); - return if !$buffer; - $dbh->do("INSERT INTO longdescs (bug_id, who, bug_when, thetext) + my ($id, $who, $when, $buffer) = (@_); + my $dbh = Bugzilla->dbh; + $buffer = trim($buffer); + return if !$buffer; + $dbh->do( + "INSERT INTO longdescs (bug_id, who, bug_when, thetext) VALUES (?,?,?,?)", undef, $id, $who, - time2str("%Y/%m/%d %H:%M:%S", $when), $buffer); + time2str("%Y/%m/%d %H:%M:%S", $when), $buffer + ); } sub _populate_longdescs { - my $dbh = Bugzilla->dbh; - # 2000-01-20 Added a new "longdescs" table, which is supposed to have - # all the long descriptions in it, replacing the old long_desc field - # in the bugs table. The below hideous code populates this new table - # with things from the old field, with ugly parsing and heuristics. - - if ($dbh->bz_column_info('bugs', 'long_desc')) { - my ($total) = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs"); - - print "Populating new long_desc table. This is slow. There are", - " $total\nbugs to process; a line of dots will be printed", - " for each 50.\n\n"; - local $| = 1; - - # On MySQL, longdescs doesn't benefit from transactions, but this - # doesn't hurt. - $dbh->bz_start_transaction(); - - $dbh->do('DELETE FROM longdescs'); - - my $sth = $dbh->prepare("SELECT bug_id, creation_ts, reporter, - long_desc FROM bugs ORDER BY bug_id"); - $sth->execute(); - my $count = 0; - while (my ($id, $createtime, $reporterid, $desc) = - $sth->fetchrow_array()) + my $dbh = Bugzilla->dbh; + + # 2000-01-20 Added a new "longdescs" table, which is supposed to have + # all the long descriptions in it, replacing the old long_desc field + # in the bugs table. The below hideous code populates this new table + # with things from the old field, with ugly parsing and heuristics. + + if ($dbh->bz_column_info('bugs', 'long_desc')) { + my ($total) = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs"); + + print "Populating new long_desc table. This is slow. There are", + " $total\nbugs to process; a line of dots will be printed", + " for each 50.\n\n"; + local $| = 1; + + # On MySQL, longdescs doesn't benefit from transactions, but this + # doesn't hurt. + $dbh->bz_start_transaction(); + + $dbh->do('DELETE FROM longdescs'); + + my $sth = $dbh->prepare( + "SELECT bug_id, creation_ts, reporter, + long_desc FROM bugs ORDER BY bug_id" + ); + $sth->execute(); + my $count = 0; + while (my ($id, $createtime, $reporterid, $desc) = $sth->fetchrow_array()) { + $count++; + indicate_progress({total => $total, current => $count}); + $desc =~ s/\r//g; + my $who = $reporterid; + my $when = str2time($createtime); + my $buffer = ""; + foreach my $line (split(/\n/, $desc)) { + $line =~ s/\s+$//g; # Trim trailing whitespace. + if ($line =~ /^------- Additional Comments From ([^\s]+)\s+(\d.+\d)\s+-------$/) { - $count++; - indicate_progress({ total => $total, current => $count }); - $desc =~ s/\r//g; - my $who = $reporterid; - my $when = str2time($createtime); - my $buffer = ""; - foreach my $line (split(/\n/, $desc)) { - $line =~ s/\s+$//g; # Trim trailing whitespace. - if ($line =~ /^------- Additional Comments From ([^\s]+)\s+(\d.+\d)\s+-------$/) - { - my $name = $1; - my $date = str2time($2); - # Oy, what a hack. The creation time is accurate to the - # second. But the long text only contains things accurate - # to the And so, if someone makes a comment within a - # minute of the original bug creation, then the comment can - # come *before* the bug creation. So, we add 59 seconds to - # the time of all comments, so that they are always - # considered to have happened at the *end* of the given - # minute, not the beginning. - $date += 59; - if ($date >= $when) { - _write_one_longdesc($id, $who, $when, $buffer); - $buffer = ""; - $when = $date; - my $s2 = $dbh->prepare("SELECT userid FROM profiles " . - "WHERE login_name = ?"); - $s2->execute($name); - ($who) = ($s2->fetchrow_array()); - - if (!$who) { - # This username doesn't exist. Maybe someone - # renamed him or something. Invent a new profile - # entry disabled, just to represent him. - $dbh->do("INSERT INTO profiles (login_name, + my $name = $1; + my $date = str2time($2); + + # Oy, what a hack. The creation time is accurate to the + # second. But the long text only contains things accurate + # to the And so, if someone makes a comment within a + # minute of the original bug creation, then the comment can + # come *before* the bug creation. So, we add 59 seconds to + # the time of all comments, so that they are always + # considered to have happened at the *end* of the given + # minute, not the beginning. + $date += 59; + if ($date >= $when) { + _write_one_longdesc($id, $who, $when, $buffer); + $buffer = ""; + $when = $date; + my $s2 = $dbh->prepare("SELECT userid FROM profiles " . "WHERE login_name = ?"); + $s2->execute($name); + ($who) = ($s2->fetchrow_array()); + + if (!$who) { + + # This username doesn't exist. Maybe someone + # renamed him or something. Invent a new profile + # entry disabled, just to represent him. + $dbh->do( + "INSERT INTO profiles (login_name, cryptpassword, disabledtext) VALUES (?,?,?)", undef, $name, '*', - "Account created only to maintain" - . " database integrity"); - $who = $dbh->bz_last_key('profiles', 'userid'); - } - next; - } - } - $buffer .= $line . "\n"; + "Account created only to maintain" . " database integrity" + ); + $who = $dbh->bz_last_key('profiles', 'userid'); } - _write_one_longdesc($id, $who, $when, $buffer); - } # while loop + next; + } + } + $buffer .= $line . "\n"; + } + _write_one_longdesc($id, $who, $when, $buffer); + } # while loop - print "\n\n"; - $dbh->bz_drop_column('bugs', 'long_desc'); - $dbh->bz_commit_transaction(); - } # main if + print "\n\n"; + $dbh->bz_drop_column('bugs', 'long_desc'); + $dbh->bz_commit_transaction(); + } # main if } sub _update_bugs_activity_field_to_fieldid { - my $dbh = Bugzilla->dbh; + my $dbh = Bugzilla->dbh; - # 2000-01-18 Added a new table fielddefs that records information about the - # different fields we keep an activity log on. The bugs_activity table - # now has a pointer into that table instead of recording the name directly. - if ($dbh->bz_column_info('bugs_activity', 'field')) { - $dbh->bz_add_column('bugs_activity', 'fieldid', - {TYPE => 'INT3', NOTNULL => 1}, 0); + # 2000-01-18 Added a new table fielddefs that records information about the + # different fields we keep an activity log on. The bugs_activity table + # now has a pointer into that table instead of recording the name directly. + if ($dbh->bz_column_info('bugs_activity', 'field')) { + $dbh->bz_add_column('bugs_activity', 'fieldid', {TYPE => 'INT3', NOTNULL => 1}, + 0); - $dbh->bz_add_index('bugs_activity', 'bugs_activity_fieldid_idx', - [qw(fieldid)]); - print "Populating new bugs_activity.fieldid field...\n"; + $dbh->bz_add_index('bugs_activity', 'bugs_activity_fieldid_idx', [qw(fieldid)]); + print "Populating new bugs_activity.fieldid field...\n"; - $dbh->bz_start_transaction(); + $dbh->bz_start_transaction(); - my $ids = $dbh->selectall_arrayref( - 'SELECT DISTINCT fielddefs.id, bugs_activity.field + my $ids = $dbh->selectall_arrayref( + 'SELECT DISTINCT fielddefs.id, bugs_activity.field FROM bugs_activity LEFT JOIN fielddefs - ON bugs_activity.field = fielddefs.name', {Slice=>{}}); - - foreach my $item (@$ids) { - my $id = $item->{id}; - my $field = $item->{field}; - # If the id is NULL - if (!$id) { - $dbh->do("INSERT INTO fielddefs (name, description) VALUES " . - "(?, ?)", undef, $field, $field); - $id = $dbh->bz_last_key('fielddefs', 'id'); - } - $dbh->do("UPDATE bugs_activity SET fieldid = ? WHERE field = ?", - undef, $id, $field); - } - $dbh->bz_commit_transaction(); + ON bugs_activity.field = fielddefs.name', {Slice => {}} + ); - $dbh->bz_drop_column('bugs_activity', 'field'); + foreach my $item (@$ids) { + my $id = $item->{id}; + my $field = $item->{field}; + + # If the id is NULL + if (!$id) { + $dbh->do("INSERT INTO fielddefs (name, description) VALUES " . "(?, ?)", + undef, $field, $field); + $id = $dbh->bz_last_key('fielddefs', 'id'); + } + $dbh->do("UPDATE bugs_activity SET fieldid = ? WHERE field = ?", + undef, $id, $field); } + $dbh->bz_commit_transaction(); + + $dbh->bz_drop_column('bugs_activity', 'field'); + } } sub _add_unique_login_name_index_to_profiles { - my $dbh = Bugzilla->dbh; - - # 2000-01-22 The "login_name" field in the "profiles" table was not - # declared to be unique. Sure enough, somehow, I got 22 duplicated entries - # in my database. This code detects that, cleans up the duplicates, and - # then tweaks the table to declare the field to be unique. What a pain. - if (!$dbh->bz_index_info('profiles', 'profiles_login_name_idx') - || !$dbh->bz_index_info('profiles', 'profiles_login_name_idx')->{TYPE}) - { - print "Searching for duplicate entries in the profiles table...\n"; - while (1) { - # This code is weird in that it loops around and keeps doing this - # select again. That's because I'm paranoid about deleting entries - # out from under us in the profiles table. Things get weird if - # there are *three* or more entries for the same user... - my $sth = $dbh->prepare("SELECT p1.userid, p2.userid, p1.login_name + my $dbh = Bugzilla->dbh; + + # 2000-01-22 The "login_name" field in the "profiles" table was not + # declared to be unique. Sure enough, somehow, I got 22 duplicated entries + # in my database. This code detects that, cleans up the duplicates, and + # then tweaks the table to declare the field to be unique. What a pain. + if ( !$dbh->bz_index_info('profiles', 'profiles_login_name_idx') + || !$dbh->bz_index_info('profiles', 'profiles_login_name_idx')->{TYPE}) + { + print "Searching for duplicate entries in the profiles table...\n"; + while (1) { + + # This code is weird in that it loops around and keeps doing this + # select again. That's because I'm paranoid about deleting entries + # out from under us in the profiles table. Things get weird if + # there are *three* or more entries for the same user... + my $sth = $dbh->prepare( + "SELECT p1.userid, p2.userid, p1.login_name FROM profiles AS p1, profiles AS p2 WHERE p1.userid < p2.userid AND p1.login_name = p2.login_name - ORDER BY p1.login_name"); - $sth->execute(); - my ($u1, $u2, $n) = ($sth->fetchrow_array); - last if !$u1; - - print "Both $u1 & $u2 are ids for $n! Merging $u2 into $u1...\n"; - foreach my $i (["bugs", "reporter"], - ["bugs", "assigned_to"], - ["bugs", "qa_contact"], - ["attachments", "submitter_id"], - ["bugs_activity", "who"], - ["cc", "who"], - ["votes", "who"], - ["longdescs", "who"]) { - my ($table, $field) = (@$i); - if ($dbh->bz_table_info($table)) { - print " Updating $table.$field...\n"; - $dbh->do("UPDATE $table SET $field = $u1 " . - "WHERE $field = $u2"); - } - } - $dbh->do("DELETE FROM profiles WHERE userid = $u2"); + ORDER BY p1.login_name" + ); + $sth->execute(); + my ($u1, $u2, $n) = ($sth->fetchrow_array); + last if !$u1; + + print "Both $u1 & $u2 are ids for $n! Merging $u2 into $u1...\n"; + foreach my $i ( + ["bugs", "reporter"], + ["bugs", "assigned_to"], + ["bugs", "qa_contact"], + ["attachments", "submitter_id"], + ["bugs_activity", "who"], + ["cc", "who"], + ["votes", "who"], + ["longdescs", "who"] + ) + { + my ($table, $field) = (@$i); + if ($dbh->bz_table_info($table)) { + print " Updating $table.$field...\n"; + $dbh->do("UPDATE $table SET $field = $u1 " . "WHERE $field = $u2"); } - print "OK, changing index type to prevent duplicates in the", - " future...\n"; - - $dbh->bz_drop_index('profiles', 'profiles_login_name_idx'); - $dbh->bz_add_index('profiles', 'profiles_login_name_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(login_name)]}); + } + $dbh->do("DELETE FROM profiles WHERE userid = $u2"); } + print "OK, changing index type to prevent duplicates in the", " future...\n"; + + $dbh->bz_drop_index('profiles', 'profiles_login_name_idx'); + $dbh->bz_add_index('profiles', 'profiles_login_name_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(login_name)]}); + } } sub _update_component_user_fields_to_ids { - my $dbh = Bugzilla->dbh; - - # components.initialowner - my $comp_init_owner = $dbh->bz_column_info('components', 'initialowner'); - if ($comp_init_owner && $comp_init_owner->{TYPE} eq 'TINYTEXT') { - my $sth = $dbh->prepare("SELECT program, value, initialowner - FROM components"); - $sth->execute(); - while (my ($program, $value, $initialowner) = $sth->fetchrow_array()) { - my ($id) = $dbh->selectrow_array( - "SELECT userid FROM profiles WHERE login_name = ?", - undef, $initialowner); - - unless (defined $id) { - print "Warning: You have an invalid default assignee", - " '$initialowner'\n in component '$value' of program", - " '$program'!\n"; - $id = 0; - } + my $dbh = Bugzilla->dbh; - $dbh->do("UPDATE components SET initialowner = ? - WHERE program = ? AND value = ?", undef, - $id, $program, $value); - } - $dbh->bz_alter_column('components','initialowner',{TYPE => 'INT3'}); + # components.initialowner + my $comp_init_owner = $dbh->bz_column_info('components', 'initialowner'); + if ($comp_init_owner && $comp_init_owner->{TYPE} eq 'TINYTEXT') { + my $sth = $dbh->prepare( + "SELECT program, value, initialowner + FROM components" + ); + $sth->execute(); + while (my ($program, $value, $initialowner) = $sth->fetchrow_array()) { + my ($id) + = $dbh->selectrow_array("SELECT userid FROM profiles WHERE login_name = ?", + undef, $initialowner); + + unless (defined $id) { + print "Warning: You have an invalid default assignee", + " '$initialowner'\n in component '$value' of program", " '$program'!\n"; + $id = 0; + } + + $dbh->do( + "UPDATE components SET initialowner = ? + WHERE program = ? AND value = ?", undef, $id, $program, $value + ); } + $dbh->bz_alter_column('components', 'initialowner', {TYPE => 'INT3'}); + } - # components.initialqacontact - my $comp_init_qa = $dbh->bz_column_info('components', 'initialqacontact'); - if ($comp_init_qa && $comp_init_qa->{TYPE} eq 'TINYTEXT') { - my $sth = $dbh->prepare("SELECT program, value, initialqacontact - FROM components"); - $sth->execute(); - while (my ($program, $value, $initialqacontact) = - $sth->fetchrow_array()) - { - my ($id) = $dbh->selectrow_array( - "SELECT userid FROM profiles WHERE login_name = ?", - undef, $initialqacontact); - - unless (defined $id) { - if ($initialqacontact) { - print "Warning: You have an invalid default QA contact", - " $initialqacontact' in program '$program',", - " component '$value'!\n"; - } - $id = 0; - } - - $dbh->do("UPDATE components SET initialqacontact = ? - WHERE program = ? AND value = ?", undef, - $id, $program, $value); + # components.initialqacontact + my $comp_init_qa = $dbh->bz_column_info('components', 'initialqacontact'); + if ($comp_init_qa && $comp_init_qa->{TYPE} eq 'TINYTEXT') { + my $sth = $dbh->prepare( + "SELECT program, value, initialqacontact + FROM components" + ); + $sth->execute(); + while (my ($program, $value, $initialqacontact) = $sth->fetchrow_array()) { + my ($id) + = $dbh->selectrow_array("SELECT userid FROM profiles WHERE login_name = ?", + undef, $initialqacontact); + + unless (defined $id) { + if ($initialqacontact) { + print "Warning: You have an invalid default QA contact", + " $initialqacontact' in program '$program',", " component '$value'!\n"; } + $id = 0; + } - $dbh->bz_alter_column('components','initialqacontact',{TYPE => 'INT3'}); + $dbh->do( + "UPDATE components SET initialqacontact = ? + WHERE program = ? AND value = ?", undef, $id, $program, $value + ); } + + $dbh->bz_alter_column('components', 'initialqacontact', {TYPE => 'INT3'}); + } } sub _populate_milestones_table { - my $dbh = Bugzilla->dbh; - # 2000-03-21 Adding a table for target milestones to - # database - matthew@zeroknowledge.com - # If the milestones table is empty, and we're still back in a Bugzilla - # that has a bugs.product field, that means that we just created - # the milestones table and it needs to be populated. - my $milestones_exist = $dbh->selectrow_array( - "SELECT DISTINCT 1 FROM milestones"); - if (!$milestones_exist && $dbh->bz_column_info('bugs', 'product')) { - print "Replacing blank milestones...\n"; - - $dbh->do("UPDATE bugs + my $dbh = Bugzilla->dbh; + + # 2000-03-21 Adding a table for target milestones to + # database - matthew@zeroknowledge.com + # If the milestones table is empty, and we're still back in a Bugzilla + # that has a bugs.product field, that means that we just created + # the milestones table and it needs to be populated. + my $milestones_exist + = $dbh->selectrow_array("SELECT DISTINCT 1 FROM milestones"); + if (!$milestones_exist && $dbh->bz_column_info('bugs', 'product')) { + print "Replacing blank milestones...\n"; + + $dbh->do( + "UPDATE bugs SET target_milestone = '---' - WHERE target_milestone = ' '"); - - # If we are upgrading from 2.8 or earlier, we will have *created* - # the milestones table with a product_id field, but Bugzilla expects - # it to have a "product" field. So we change the field backward so - # other code can run. The change will be reversed later in checksetup. - if ($dbh->bz_column_info('milestones', 'product_id')) { - # Dropping the column leaves us with a milestones_product_id_idx - # index that is only on the "value" column. We need to drop the - # whole index so that it can be correctly re-created later. - $dbh->bz_drop_index('milestones', 'milestones_product_id_idx'); - $dbh->bz_drop_column('milestones', 'product_id'); - $dbh->bz_add_column('milestones', 'product', - {TYPE => 'varchar(64)', NOTNULL => 1}, ''); - } + WHERE target_milestone = ' '" + ); + + # If we are upgrading from 2.8 or earlier, we will have *created* + # the milestones table with a product_id field, but Bugzilla expects + # it to have a "product" field. So we change the field backward so + # other code can run. The change will be reversed later in checksetup. + if ($dbh->bz_column_info('milestones', 'product_id')) { + + # Dropping the column leaves us with a milestones_product_id_idx + # index that is only on the "value" column. We need to drop the + # whole index so that it can be correctly re-created later. + $dbh->bz_drop_index('milestones', 'milestones_product_id_idx'); + $dbh->bz_drop_column('milestones', 'product_id'); + $dbh->bz_add_column('milestones', 'product', + {TYPE => 'varchar(64)', NOTNULL => 1}, ''); + } - # Populate the milestone table with all existing values in the database - my $sth = $dbh->prepare("SELECT DISTINCT target_milestone, product - FROM bugs"); - $sth->execute(); + # Populate the milestone table with all existing values in the database + my $sth = $dbh->prepare( + "SELECT DISTINCT target_milestone, product + FROM bugs" + ); + $sth->execute(); - print "Populating milestones table...\n"; + print "Populating milestones table...\n"; - while (my ($value, $product) = $sth->fetchrow_array()) { - # check if the value already exists - my $sortkey = substr($value, 1); - if ($sortkey !~ /^\d+$/) { - $sortkey = 0; - } else { - $sortkey *= 10; - } - my $ms_exists = $dbh->selectrow_array( - "SELECT value FROM milestones - WHERE value = ? AND product = ?", undef, $value, $product); + while (my ($value, $product) = $sth->fetchrow_array()) { - if (!$ms_exists) { - $dbh->do("INSERT INTO milestones(value, product, sortkey) - VALUES (?,?,?)", undef, $value, $product, $sortkey); - } - } + # check if the value already exists + my $sortkey = substr($value, 1); + if ($sortkey !~ /^\d+$/) { + $sortkey = 0; + } + else { + $sortkey *= 10; + } + my $ms_exists = $dbh->selectrow_array( + "SELECT value FROM milestones + WHERE value = ? AND product = ?", undef, $value, $product + ); + + if (!$ms_exists) { + $dbh->do( + "INSERT INTO milestones(value, product, sortkey) + VALUES (?,?,?)", undef, $value, $product, $sortkey + ); + } } + } } sub _add_products_defaultmilestone { - my $dbh = Bugzilla->dbh; - - # 2000-03-23 Added a defaultmilestone field to the products table, so that - # we know which milestone to initially assign bugs to. - if (!$dbh->bz_column_info('products', 'defaultmilestone')) { - $dbh->bz_add_column('products', 'defaultmilestone', - {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"}); - my $sth = $dbh->prepare( - "SELECT product, defaultmilestone FROM products"); - $sth->execute(); - while (my ($product, $default_ms) = $sth->fetchrow_array()) { - my $exists = $dbh->selectrow_array( - "SELECT value FROM milestones - WHERE value = ? AND product = ?", - undef, $default_ms, $product); - if (!$exists) { - $dbh->do("INSERT INTO milestones(value, product) " . - "VALUES (?, ?)", undef, $default_ms, $product); - } - } + my $dbh = Bugzilla->dbh; + + # 2000-03-23 Added a defaultmilestone field to the products table, so that + # we know which milestone to initially assign bugs to. + if (!$dbh->bz_column_info('products', 'defaultmilestone')) { + $dbh->bz_add_column('products', 'defaultmilestone', + {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"}); + my $sth = $dbh->prepare("SELECT product, defaultmilestone FROM products"); + $sth->execute(); + while (my ($product, $default_ms) = $sth->fetchrow_array()) { + my $exists = $dbh->selectrow_array( + "SELECT value FROM milestones + WHERE value = ? AND product = ?", undef, $default_ms, $product + ); + if (!$exists) { + $dbh->do("INSERT INTO milestones(value, product) " . "VALUES (?, ?)", + undef, $default_ms, $product); + } } + } } sub _copy_from_comments_to_longdescs { - my $dbh = Bugzilla->dbh; - # 2000-11-27 For Bugzilla 2.5 and later. Copy data from 'comments' to - # 'longdescs' - the new name of the comments table. - if ($dbh->bz_table_info('comments')) { - print "Copying data from 'comments' to 'longdescs'...\n"; - my $quoted_when = $dbh->quote_identifier('when'); - $dbh->do("INSERT INTO longdescs (bug_when, bug_id, who, thetext) + my $dbh = Bugzilla->dbh; + + # 2000-11-27 For Bugzilla 2.5 and later. Copy data from 'comments' to + # 'longdescs' - the new name of the comments table. + if ($dbh->bz_table_info('comments')) { + print "Copying data from 'comments' to 'longdescs'...\n"; + my $quoted_when = $dbh->quote_identifier('when'); + $dbh->do( + "INSERT INTO longdescs (bug_when, bug_id, who, thetext) SELECT $quoted_when, bug_id, who, comment - FROM comments"); - $dbh->bz_drop_table("comments"); - } + FROM comments" + ); + $dbh->bz_drop_table("comments"); + } } sub _populate_duplicates_table { - my $dbh = Bugzilla->dbh; - # 2000-07-15 Added duplicates table so Bugzilla tracks duplicates in a - # better way than it used to. This code searches the comments to populate - # the table initially. It's executed if the table is empty; if it's - # empty because there are no dupes (as opposed to having just created - # the table) it won't have any effect anyway, so it doesn't matter. - my ($dups_exist) = $dbh->selectrow_array( - "SELECT DISTINCT 1 FROM duplicates"); - # We also check against a schema change that happened later. - if (!$dups_exist && !$dbh->bz_column_info('groups', 'isactive')) { - # populate table - print "Populating duplicates table from comments...\n"; - - my $sth = $dbh->prepare( - "SELECT longdescs.bug_id, thetext + my $dbh = Bugzilla->dbh; + + # 2000-07-15 Added duplicates table so Bugzilla tracks duplicates in a + # better way than it used to. This code searches the comments to populate + # the table initially. It's executed if the table is empty; if it's + # empty because there are no dupes (as opposed to having just created + # the table) it won't have any effect anyway, so it doesn't matter. + my ($dups_exist) = $dbh->selectrow_array("SELECT DISTINCT 1 FROM duplicates"); + + # We also check against a schema change that happened later. + if (!$dups_exist && !$dbh->bz_column_info('groups', 'isactive')) { + + # populate table + print "Populating duplicates table from comments...\n"; + + my $sth = $dbh->prepare( + "SELECT longdescs.bug_id, thetext FROM longdescs LEFT JOIN bugs ON longdescs.bug_id = bugs.bug_id - WHERE (" . $dbh->sql_regexp("thetext", - "'[.*.]{3} This bug has been marked as a duplicate" - . " of [[:digit:]]+ [.*.]{3}'") - . ") + WHERE (" + . $dbh->sql_regexp("thetext", + "'[.*.]{3} This bug has been marked as a duplicate" + . " of [[:digit:]]+ [.*.]{3}'") + . ") AND resolution = 'DUPLICATE' - ORDER BY longdescs.bug_when"); - $sth->execute(); - - my (%dupes, $key); - # Because of the way hashes work, this loop removes all but the - # last dupe resolution found for a given bug. - while (my ($dupe, $dupe_of) = $sth->fetchrow_array()) { - $dupes{$dupe} = $dupe_of; - } + ORDER BY longdescs.bug_when" + ); + $sth->execute(); - foreach $key (keys(%dupes)){ - $dupes{$key} =~ /^.*\*\*\* This bug has been marked as a duplicate of (\d+) \*\*\*$/ms; - $dupes{$key} = $1; - $dbh->do("INSERT INTO duplicates VALUES(?, ?)", undef, - $dupes{$key}, $key); - # BugItsADupeOf Dupe - } + my (%dupes, $key); + + # Because of the way hashes work, this loop removes all but the + # last dupe resolution found for a given bug. + while (my ($dupe, $dupe_of) = $sth->fetchrow_array()) { + $dupes{$dupe} = $dupe_of; } + + foreach $key (keys(%dupes)) { + $dupes{$key} + =~ /^.*\*\*\* This bug has been marked as a duplicate of (\d+) \*\*\*$/ms; + $dupes{$key} = $1; + $dbh->do("INSERT INTO duplicates VALUES(?, ?)", undef, $dupes{$key}, $key); + + # BugItsADupeOf Dupe + } + } } sub _recrypt_plaintext_passwords { - my $dbh = Bugzilla->dbh; - # 2001-06-12; myk@mozilla.org; bugs 74032, 77473: - # Recrypt passwords using Perl &crypt instead of the mysql equivalent - # and delete plaintext passwords from the database. - if ($dbh->bz_column_info('profiles', 'password')) { + my $dbh = Bugzilla->dbh; - print <bz_column_info('profiles', 'password')) { + + print <selectrow_array('SELECT COUNT(*) FROM profiles'); - my $sth = $dbh->prepare("SELECT userid, password FROM profiles"); - $sth->execute(); - - my $i = 1; + # Re-crypt everyone's password. + my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM profiles'); + my $sth = $dbh->prepare("SELECT userid, password FROM profiles"); + $sth->execute(); - print "Fixing passwords...\n"; - while (my ($userid, $password) = $sth->fetchrow_array()) { - my $cryptpassword = $dbh->quote(bz_crypt($password)); - $dbh->do("UPDATE profiles " . - "SET cryptpassword = $cryptpassword " . - "WHERE userid = $userid"); - indicate_progress({ total => $total, current => $i, every => 10 }); - } - print "\n"; + my $i = 1; - # Drop the plaintext password field. - $dbh->bz_drop_column('profiles', 'password'); + print "Fixing passwords...\n"; + while (my ($userid, $password) = $sth->fetchrow_array()) { + my $cryptpassword = $dbh->quote(bz_crypt($password)); + $dbh->do("UPDATE profiles " + . "SET cryptpassword = $cryptpassword " + . "WHERE userid = $userid"); + indicate_progress({total => $total, current => $i, every => 10}); } + print "\n"; + + # Drop the plaintext password field. + $dbh->bz_drop_column('profiles', 'password'); + } } sub _update_bugs_activity_to_only_record_changes { - my $dbh = Bugzilla->dbh; - # 2001-07-20 jake@bugzilla.org - Change bugs_activity to only record changes - # http://bugzilla.mozilla.org/show_bug.cgi?id=55161 - if ($dbh->bz_column_info('bugs_activity', 'oldvalue')) { - $dbh->bz_add_column("bugs_activity", "removed", {TYPE => "TINYTEXT"}); - $dbh->bz_add_column("bugs_activity", "added", {TYPE => "TINYTEXT"}); - - # Need to get field id's for the fields that have multiple values - my @multi; - foreach my $f ("cc", "dependson", "blocked", "keywords") { - my $sth = $dbh->prepare("SELECT id " . - "FROM fielddefs " . - "WHERE name = '$f'"); - $sth->execute(); - my ($fid) = $sth->fetchrow_array(); - push (@multi, $fid); + my $dbh = Bugzilla->dbh; + + # 2001-07-20 jake@bugzilla.org - Change bugs_activity to only record changes + # http://bugzilla.mozilla.org/show_bug.cgi?id=55161 + if ($dbh->bz_column_info('bugs_activity', 'oldvalue')) { + $dbh->bz_add_column("bugs_activity", "removed", {TYPE => "TINYTEXT"}); + $dbh->bz_add_column("bugs_activity", "added", {TYPE => "TINYTEXT"}); + + # Need to get field id's for the fields that have multiple values + my @multi; + foreach my $f ("cc", "dependson", "blocked", "keywords") { + my $sth = $dbh->prepare("SELECT id " . "FROM fielddefs " . "WHERE name = '$f'"); + $sth->execute(); + my ($fid) = $sth->fetchrow_array(); + push(@multi, $fid); + } + + # Now we need to process the bugs_activity table and reformat the data + print "Fixing activity log...\n"; + my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM bugs_activity'); + my $sth = $dbh->prepare( + "SELECT bug_id, who, bug_when, fieldid, + oldvalue, newvalue FROM bugs_activity" + ); + $sth->execute; + my $i = 0; + while (my ($bug_id, $who, $bug_when, $fieldid, $oldvalue, $newvalue) + = $sth->fetchrow_array()) + { + $i++; + indicate_progress({total => $total, current => $i, every => 10}); + + # Make sure (old|new)value isn't null (to suppress warnings) + $oldvalue ||= ""; + $newvalue ||= ""; + my ($added, $removed) = ""; + if (grep ($_ eq $fieldid, @multi)) { + $oldvalue =~ s/[\s,]+/ /g; + $newvalue =~ s/[\s,]+/ /g; + my @old = split(" ", $oldvalue); + my @new = split(" ", $newvalue); + my (@add, @remove) = (); + + # Find values that were "added" + foreach my $value (@new) { + if (!grep ($_ eq $value, @old)) { + push(@add, $value); + } } - # Now we need to process the bugs_activity table and reformat the data - print "Fixing activity log...\n"; - my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM bugs_activity'); - my $sth = $dbh->prepare("SELECT bug_id, who, bug_when, fieldid, - oldvalue, newvalue FROM bugs_activity"); - $sth->execute; - my $i = 0; - while (my ($bug_id, $who, $bug_when, $fieldid, $oldvalue, $newvalue) - = $sth->fetchrow_array()) - { - $i++; - indicate_progress({ total => $total, current => $i, every => 10 }); - # Make sure (old|new)value isn't null (to suppress warnings) - $oldvalue ||= ""; - $newvalue ||= ""; - my ($added, $removed) = ""; - if (grep ($_ eq $fieldid, @multi)) { - $oldvalue =~ s/[\s,]+/ /g; - $newvalue =~ s/[\s,]+/ /g; - my @old = split(" ", $oldvalue); - my @new = split(" ", $newvalue); - my (@add, @remove) = (); - # Find values that were "added" - foreach my $value(@new) { - if (! grep ($_ eq $value, @old)) { - push (@add, $value); - } - } - # Find values that were removed - foreach my $value(@old) { - if (! grep ($_ eq $value, @new)) { - push (@remove, $value); - } - } - $added = join (", ", @add); - $removed = join (", ", @remove); - # If we can't determine what changed, put a ? in both fields - unless ($added || $removed) { - $added = "?"; - $removed = "?"; - } - # If the original field (old|new)value was full, then this - # could be incomplete data. - if (length($oldvalue) == 255 || length($newvalue) == 255) { - $added = "? $added"; - $removed = "? $removed"; - } - } else { - $removed = $oldvalue; - $added = $newvalue; - } - $added = $dbh->quote($added); - $removed = $dbh->quote($removed); - $dbh->do("UPDATE bugs_activity + # Find values that were removed + foreach my $value (@old) { + if (!grep ($_ eq $value, @new)) { + push(@remove, $value); + } + } + $added = join(", ", @add); + $removed = join(", ", @remove); + + # If we can't determine what changed, put a ? in both fields + unless ($added || $removed) { + $added = "?"; + $removed = "?"; + } + + # If the original field (old|new)value was full, then this + # could be incomplete data. + if (length($oldvalue) == 255 || length($newvalue) == 255) { + $added = "? $added"; + $removed = "? $removed"; + } + } + else { + $removed = $oldvalue; + $added = $newvalue; + } + $added = $dbh->quote($added); + $removed = $dbh->quote($removed); + $dbh->do( + "UPDATE bugs_activity SET removed = $removed, added = $added WHERE bug_id = $bug_id AND who = $who AND bug_when = '$bug_when' - AND fieldid = $fieldid"); - } - print "\n"; - $dbh->bz_drop_column("bugs_activity", "oldvalue"); - $dbh->bz_drop_column("bugs_activity", "newvalue"); + AND fieldid = $fieldid" + ); } + print "\n"; + $dbh->bz_drop_column("bugs_activity", "oldvalue"); + $dbh->bz_drop_column("bugs_activity", "newvalue"); + } } sub _delete_logincookies_cryptpassword_and_handle_invalid_cookies { - my $dbh = Bugzilla->dbh; - # 2002-02-04 bbaetz@student.usyd.edu.au bug 95732 - # Remove logincookies.cryptpassword, and delete entries which become - # invalid - if ($dbh->bz_column_info("logincookies", "cryptpassword")) { - # We need to delete any cookies which are invalid before dropping the - # column - print "Removing invalid login cookies...\n"; - - # mysql doesn't support DELETE with multi-table queries, so we have - # to iterate - my $sth = $dbh->prepare("SELECT cookie FROM logincookies, profiles " . - "WHERE logincookies.cryptpassword != " . - "profiles.cryptpassword AND " . - "logincookies.userid = profiles.userid"); - $sth->execute(); - while (my ($cookie) = $sth->fetchrow_array()) { - $dbh->do("DELETE FROM logincookies WHERE cookie = $cookie"); - } - - $dbh->bz_drop_column("logincookies", "cryptpassword"); + my $dbh = Bugzilla->dbh; + + # 2002-02-04 bbaetz@student.usyd.edu.au bug 95732 + # Remove logincookies.cryptpassword, and delete entries which become + # invalid + if ($dbh->bz_column_info("logincookies", "cryptpassword")) { + + # We need to delete any cookies which are invalid before dropping the + # column + print "Removing invalid login cookies...\n"; + + # mysql doesn't support DELETE with multi-table queries, so we have + # to iterate + my $sth + = $dbh->prepare("SELECT cookie FROM logincookies, profiles " + . "WHERE logincookies.cryptpassword != " + . "profiles.cryptpassword AND " + . "logincookies.userid = profiles.userid"); + $sth->execute(); + while (my ($cookie) = $sth->fetchrow_array()) { + $dbh->do("DELETE FROM logincookies WHERE cookie = $cookie"); } + + $dbh->bz_drop_column("logincookies", "cryptpassword"); + } } sub _use_ip_instead_of_hostname_in_logincookies { - my $dbh = Bugzilla->dbh; - - # 2002-03-15 bbaetz@student.usyd.edu.au - bug 129466 - # 2002-05-13 preed@sigkill.com - bug 129446 patch backported to the - # BUGZILLA-2_14_1-BRANCH as a security blocker for the 2.14.2 release - # - # Use the ip, not the hostname, in the logincookies table - if ($dbh->bz_column_info("logincookies", "hostname")) { - print "Clearing the logincookies table...\n"; - # We've changed what we match against, so all entries are now invalid - $dbh->do("DELETE FROM logincookies"); - - # Now update the logincookies schema - $dbh->bz_drop_column("logincookies", "hostname"); - $dbh->bz_add_column("logincookies", "ipaddr", - {TYPE => 'varchar(40)'}); - } + my $dbh = Bugzilla->dbh; + + # 2002-03-15 bbaetz@student.usyd.edu.au - bug 129466 + # 2002-05-13 preed@sigkill.com - bug 129446 patch backported to the + # BUGZILLA-2_14_1-BRANCH as a security blocker for the 2.14.2 release + # + # Use the ip, not the hostname, in the logincookies table + if ($dbh->bz_column_info("logincookies", "hostname")) { + print "Clearing the logincookies table...\n"; + + # We've changed what we match against, so all entries are now invalid + $dbh->do("DELETE FROM logincookies"); + + # Now update the logincookies schema + $dbh->bz_drop_column("logincookies", "hostname"); + $dbh->bz_add_column("logincookies", "ipaddr", {TYPE => 'varchar(40)'}); + } } sub _move_quips_into_db { - my $dbh = Bugzilla->dbh; - my $datadir = bz_locations->{'datadir'}; - # 2002-07-15 davef@tetsubo.com - bug 67950 - # Move quips to the db. - if (-e "$datadir/comments") { - print "Populating quips table from $datadir/comments...\n"; - my $comments = new IO::File("$datadir/comments", 'r') - || die "$datadir/comments: $!"; - $comments->untaint; - while (<$comments>) { - chomp; - $dbh->do("INSERT INTO quips (quip) VALUES (?)", undef, $_); - } - - print "\n", install_string('update_quips', { data => $datadir }), "\n"; - $comments->close; - rename("$datadir/comments", "$datadir/comments.bak") - || warn "Failed to rename: $!"; + my $dbh = Bugzilla->dbh; + my $datadir = bz_locations->{'datadir'}; + + # 2002-07-15 davef@tetsubo.com - bug 67950 + # Move quips to the db. + if (-e "$datadir/comments") { + print "Populating quips table from $datadir/comments...\n"; + my $comments = new IO::File("$datadir/comments", 'r') + || die "$datadir/comments: $!"; + $comments->untaint; + while (<$comments>) { + chomp; + $dbh->do("INSERT INTO quips (quip) VALUES (?)", undef, $_); } + + print "\n", install_string('update_quips', {data => $datadir}), "\n"; + $comments->close; + rename("$datadir/comments", "$datadir/comments.bak") + || warn "Failed to rename: $!"; + } } sub _use_ids_for_products_and_components { - my $dbh = Bugzilla->dbh; - # 2002-08-12 jake@bugzilla.org/bbaetz@student.usyd.edu.au - bug 43600 - # Use integer IDs for products and components. - if ($dbh->bz_column_info("products", "product")) { - print "Updating database to use product IDs.\n"; - - # First, we need to remove possible NULL entries - # NULLs may exist, but won't have been used, since all the uses of them - # are in NOT NULL fields in other tables - $dbh->do("DELETE FROM products WHERE product IS NULL"); - $dbh->do("DELETE FROM components WHERE value IS NULL"); - - $dbh->bz_add_column("products", "id", - {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - $dbh->bz_add_column("components", "product_id", - {TYPE => 'INT2', NOTNULL => 1}, 0); - $dbh->bz_add_column("versions", "product_id", - {TYPE => 'INT2', NOTNULL => 1}, 0); - $dbh->bz_add_column("milestones", "product_id", - {TYPE => 'INT2', NOTNULL => 1}, 0); - $dbh->bz_add_column("bugs", "product_id", - {TYPE => 'INT2', NOTNULL => 1}, 0); - - # The attachstatusdefs table was added in version 2.15, but - # removed again in early 2.17. If it exists now, we still need - # to perform this change with product_id because the code later on - # which converts the attachment statuses to flags depends on it. - # But we need to avoid this if the user is upgrading from 2.14 - # or earlier (because it won't be there to convert). - if ($dbh->bz_table_info("attachstatusdefs")) { - $dbh->bz_add_column("attachstatusdefs", "product_id", - {TYPE => 'INT2', NOTNULL => 1}, 0); - } - - my %products; - my $sth = $dbh->prepare("SELECT id, product FROM products"); - $sth->execute; - while (my ($product_id, $product) = $sth->fetchrow_array()) { - if (exists $products{$product}) { - print "Ignoring duplicate product $product\n"; - $dbh->do("DELETE FROM products WHERE id = $product_id"); - next; - } - $products{$product} = 1; - $dbh->do("UPDATE components SET product_id = $product_id " . - "WHERE program = " . $dbh->quote($product)); - $dbh->do("UPDATE versions SET product_id = $product_id " . - "WHERE program = " . $dbh->quote($product)); - $dbh->do("UPDATE milestones SET product_id = $product_id " . - "WHERE product = " . $dbh->quote($product)); - $dbh->do("UPDATE bugs SET product_id = $product_id " . - "WHERE product = " . $dbh->quote($product)); - $dbh->do("UPDATE attachstatusdefs SET product_id = $product_id " . - "WHERE product = " . $dbh->quote($product)) - if $dbh->bz_table_info("attachstatusdefs"); - } - - print "Updating the database to use component IDs.\n"; + my $dbh = Bugzilla->dbh; + + # 2002-08-12 jake@bugzilla.org/bbaetz@student.usyd.edu.au - bug 43600 + # Use integer IDs for products and components. + if ($dbh->bz_column_info("products", "product")) { + print "Updating database to use product IDs.\n"; + + # First, we need to remove possible NULL entries + # NULLs may exist, but won't have been used, since all the uses of them + # are in NOT NULL fields in other tables + $dbh->do("DELETE FROM products WHERE product IS NULL"); + $dbh->do("DELETE FROM components WHERE value IS NULL"); + + $dbh->bz_add_column("products", "id", + {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + $dbh->bz_add_column("components", "product_id", {TYPE => 'INT2', NOTNULL => 1}, + 0); + $dbh->bz_add_column("versions", "product_id", {TYPE => 'INT2', NOTNULL => 1}, + 0); + $dbh->bz_add_column("milestones", "product_id", {TYPE => 'INT2', NOTNULL => 1}, + 0); + $dbh->bz_add_column("bugs", "product_id", {TYPE => 'INT2', NOTNULL => 1}, 0); + + # The attachstatusdefs table was added in version 2.15, but + # removed again in early 2.17. If it exists now, we still need + # to perform this change with product_id because the code later on + # which converts the attachment statuses to flags depends on it. + # But we need to avoid this if the user is upgrading from 2.14 + # or earlier (because it won't be there to convert). + if ($dbh->bz_table_info("attachstatusdefs")) { + $dbh->bz_add_column("attachstatusdefs", "product_id", + {TYPE => 'INT2', NOTNULL => 1}, 0); + } - $dbh->bz_add_column("components", "id", - {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - $dbh->bz_add_column("bugs", "component_id", - {TYPE => 'INT2', NOTNULL => 1}, 0); + my %products; + my $sth = $dbh->prepare("SELECT id, product FROM products"); + $sth->execute; + while (my ($product_id, $product) = $sth->fetchrow_array()) { + if (exists $products{$product}) { + print "Ignoring duplicate product $product\n"; + $dbh->do("DELETE FROM products WHERE id = $product_id"); + next; + } + $products{$product} = 1; + $dbh->do("UPDATE components SET product_id = $product_id " + . "WHERE program = " + . $dbh->quote($product)); + $dbh->do("UPDATE versions SET product_id = $product_id " + . "WHERE program = " + . $dbh->quote($product)); + $dbh->do("UPDATE milestones SET product_id = $product_id " + . "WHERE product = " + . $dbh->quote($product)); + $dbh->do("UPDATE bugs SET product_id = $product_id " + . "WHERE product = " + . $dbh->quote($product)); + $dbh->do("UPDATE attachstatusdefs SET product_id = $product_id " + . "WHERE product = " + . $dbh->quote($product)) + if $dbh->bz_table_info("attachstatusdefs"); + } - my %components; - $sth = $dbh->prepare("SELECT id, value, product_id FROM components"); - $sth->execute; - while (my ($component_id, $component, $product_id) - = $sth->fetchrow_array()) - { - if (exists $components{$component}) { - if (exists $components{$component}{$product_id}) { - print "Ignoring duplicate component $component for", - " product $product_id\n"; - $dbh->do("DELETE FROM components WHERE id = $component_id"); - next; - } - } else { - $components{$component} = {}; - } - $components{$component}{$product_id} = 1; - $dbh->do("UPDATE bugs SET component_id = $component_id " . - "WHERE component = " . $dbh->quote($component) . - " AND product_id = $product_id"); + print "Updating the database to use component IDs.\n"; + + $dbh->bz_add_column("components", "id", + {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + $dbh->bz_add_column("bugs", "component_id", {TYPE => 'INT2', NOTNULL => 1}, 0); + + my %components; + $sth = $dbh->prepare("SELECT id, value, product_id FROM components"); + $sth->execute; + while (my ($component_id, $component, $product_id) = $sth->fetchrow_array()) { + if (exists $components{$component}) { + if (exists $components{$component}{$product_id}) { + print "Ignoring duplicate component $component for", " product $product_id\n"; + $dbh->do("DELETE FROM components WHERE id = $component_id"); + next; } - print "Fixing Indexes and Uniqueness.\n"; - $dbh->bz_drop_index('milestones', 'milestones_product_idx'); - - $dbh->bz_add_index('milestones', 'milestones_product_id_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(product_id value)]}); - - $dbh->bz_drop_index('bugs', 'bugs_product_idx'); - $dbh->bz_add_index('bugs', 'bugs_product_id_idx', [qw(product_id)]); - $dbh->bz_drop_index('bugs', 'bugs_component_idx'); - $dbh->bz_add_index('bugs', 'bugs_component_id_idx', [qw(component_id)]); - - print "Removing, renaming, and retyping old product and", - " component fields.\n"; - $dbh->bz_drop_column("components", "program"); - $dbh->bz_drop_column("versions", "program"); - $dbh->bz_drop_column("milestones", "product"); - $dbh->bz_drop_column("bugs", "product"); - $dbh->bz_drop_column("bugs", "component"); - $dbh->bz_drop_column("attachstatusdefs", "product") - if $dbh->bz_table_info("attachstatusdefs"); - $dbh->bz_rename_column("products", "product", "name"); - $dbh->bz_alter_column("products", "name", - {TYPE => 'varchar(64)', NOTNULL => 1}); - $dbh->bz_rename_column("components", "value", "name"); - $dbh->bz_alter_column("components", "name", - {TYPE => 'varchar(64)', NOTNULL => 1}); - - print "Adding indexes for products and components tables.\n"; - $dbh->bz_add_index('products', 'products_name_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(name)]}); - $dbh->bz_add_index('components', 'components_product_id_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(product_id name)]}); - $dbh->bz_add_index('components', 'components_name_idx', [qw(name)]); + } + else { + $components{$component} = {}; + } + $components{$component}{$product_id} = 1; + $dbh->do("UPDATE bugs SET component_id = $component_id " + . "WHERE component = " + . $dbh->quote($component) + . " AND product_id = $product_id"); } + print "Fixing Indexes and Uniqueness.\n"; + $dbh->bz_drop_index('milestones', 'milestones_product_idx'); + + $dbh->bz_add_index('milestones', 'milestones_product_id_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(product_id value)]}); + + $dbh->bz_drop_index('bugs', 'bugs_product_idx'); + $dbh->bz_add_index('bugs', 'bugs_product_id_idx', [qw(product_id)]); + $dbh->bz_drop_index('bugs', 'bugs_component_idx'); + $dbh->bz_add_index('bugs', 'bugs_component_id_idx', [qw(component_id)]); + + print "Removing, renaming, and retyping old product and", + " component fields.\n"; + $dbh->bz_drop_column("components", "program"); + $dbh->bz_drop_column("versions", "program"); + $dbh->bz_drop_column("milestones", "product"); + $dbh->bz_drop_column("bugs", "product"); + $dbh->bz_drop_column("bugs", "component"); + $dbh->bz_drop_column("attachstatusdefs", "product") + if $dbh->bz_table_info("attachstatusdefs"); + $dbh->bz_rename_column("products", "product", "name"); + $dbh->bz_alter_column("products", "name", + {TYPE => 'varchar(64)', NOTNULL => 1}); + $dbh->bz_rename_column("components", "value", "name"); + $dbh->bz_alter_column("components", "name", + {TYPE => 'varchar(64)', NOTNULL => 1}); + + print "Adding indexes for products and components tables.\n"; + $dbh->bz_add_index('products', 'products_name_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(name)]}); + $dbh->bz_add_index('components', 'components_product_id_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(product_id name)]}); + $dbh->bz_add_index('components', 'components_name_idx', [qw(name)]); + } } # Helper for the below function. @@ -1562,1211 +1609,1341 @@ sub _use_ids_for_products_and_components { # group names, _list_bits is used to fill in a list of references # to groupset bits for groups that no longer exist. sub _list_bits { - my ($num) = @_; - my $dbh = Bugzilla->dbh; - my @res; - my $curr = 1; - while (1) { - # Convert a big integer to a list of bits - my $sth = $dbh->prepare("SELECT ($num & ~$curr) > 0, + my ($num) = @_; + my $dbh = Bugzilla->dbh; + my @res; + my $curr = 1; + while (1) { + + # Convert a big integer to a list of bits + my $sth = $dbh->prepare( + "SELECT ($num & ~$curr) > 0, ($num & $curr), ($num & ~$curr), - $curr << 1"); - $sth->execute; - my ($more, $thisbit, $remain, $nval) = $sth->fetchrow_array; - push @res,"UNKNOWN<$curr>" if ($thisbit); - $curr = $nval; - $num = $remain; - last if !$more; - } - return @res; + $curr << 1" + ); + $sth->execute; + my ($more, $thisbit, $remain, $nval) = $sth->fetchrow_array; + push @res, "UNKNOWN<$curr>" if ($thisbit); + $curr = $nval; + $num = $remain; + last if !$more; + } + return @res; } sub _convert_groups_system_from_groupset { - my $dbh = Bugzilla->dbh; - # 2002-09-22 - bugreport@peshkin.net - bug 157756 - # - # If the whole groups system is new, but the installation isn't, - # convert all the old groupset groups, etc... - # - # This requires: - # 1) define groups ids in group table - # 2) populate user_group_map with grants from old groupsets - # and blessgroupsets - # 3) populate bug_group_map with data converted from old bug groupsets - # 4) convert activity logs to use group names instead of numbers - # 5) identify the admin from the old all-ones groupset - - # The groups system needs to be converted if groupset exists - if ($dbh->bz_column_info("profiles", "groupset")) { - # Some mysql versions will promote any unique key to primary key - # so all unique keys are removed first and then added back in - $dbh->bz_drop_index('groups', 'groups_bit_idx'); - $dbh->bz_drop_index('groups', 'groups_name_idx'); - my @primary_key = $dbh->primary_key(undef, undef, 'groups'); - if (@primary_key) { - $dbh->do("ALTER TABLE groups DROP PRIMARY KEY"); - } + my $dbh = Bugzilla->dbh; + + # 2002-09-22 - bugreport@peshkin.net - bug 157756 + # + # If the whole groups system is new, but the installation isn't, + # convert all the old groupset groups, etc... + # + # This requires: + # 1) define groups ids in group table + # 2) populate user_group_map with grants from old groupsets + # and blessgroupsets + # 3) populate bug_group_map with data converted from old bug groupsets + # 4) convert activity logs to use group names instead of numbers + # 5) identify the admin from the old all-ones groupset + + # The groups system needs to be converted if groupset exists + if ($dbh->bz_column_info("profiles", "groupset")) { + + # Some mysql versions will promote any unique key to primary key + # so all unique keys are removed first and then added back in + $dbh->bz_drop_index('groups', 'groups_bit_idx'); + $dbh->bz_drop_index('groups', 'groups_name_idx'); + my @primary_key = $dbh->primary_key(undef, undef, 'groups'); + if (@primary_key) { + $dbh->do("ALTER TABLE groups DROP PRIMARY KEY"); + } - $dbh->bz_add_column('groups', 'id', - {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - - $dbh->bz_add_index('groups', 'groups_name_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(name)]}); - - # Convert all existing groupset records to map entries before removing - # groupset fields or removing "bit" from groups. - my $sth = $dbh->prepare("SELECT bit, id FROM groups WHERE bit > 0"); - $sth->execute(); - while (my ($bit, $gid) = $sth->fetchrow_array) { - # Create user_group_map membership grants for old groupsets. - # Get each user with the old groupset bit set - my $sth2 = $dbh->prepare("SELECT userid FROM profiles - WHERE (groupset & $bit) != 0"); - $sth2->execute(); - while (my ($uid) = $sth2->fetchrow_array) { - # Check to see if the user is already a member of the group - # and, if not, insert a new record. - my $query = "SELECT user_id FROM user_group_map + $dbh->bz_add_column('groups', 'id', + {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + + $dbh->bz_add_index('groups', 'groups_name_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(name)]}); + + # Convert all existing groupset records to map entries before removing + # groupset fields or removing "bit" from groups. + my $sth = $dbh->prepare("SELECT bit, id FROM groups WHERE bit > 0"); + $sth->execute(); + while (my ($bit, $gid) = $sth->fetchrow_array) { + + # Create user_group_map membership grants for old groupsets. + # Get each user with the old groupset bit set + my $sth2 = $dbh->prepare( + "SELECT userid FROM profiles + WHERE (groupset & $bit) != 0" + ); + $sth2->execute(); + while (my ($uid) = $sth2->fetchrow_array) { + + # Check to see if the user is already a member of the group + # and, if not, insert a new record. + my $query = "SELECT user_id FROM user_group_map WHERE group_id = $gid AND user_id = $uid AND isbless = 0"; - my $sth3 = $dbh->prepare($query); - $sth3->execute(); - if ( !$sth3->fetchrow_array() ) { - $dbh->do("INSERT INTO user_group_map + my $sth3 = $dbh->prepare($query); + $sth3->execute(); + if (!$sth3->fetchrow_array()) { + $dbh->do( + "INSERT INTO user_group_map (user_id, group_id, isbless, grant_type) - VALUES ($uid, $gid, 0, " . GRANT_DIRECT . ")"); - } - } - # Create user can bless group grants for old groupsets, but only - # if we're upgrading from a Bugzilla that had blessing. - if($dbh->bz_column_info('profiles', 'blessgroupset')) { - # Get each user with the old blessgroupset bit set - $sth2 = $dbh->prepare("SELECT userid FROM profiles - WHERE (blessgroupset & $bit) != 0"); - $sth2->execute(); - while (my ($uid) = $sth2->fetchrow_array) { - $dbh->do("INSERT INTO user_group_map + VALUES ($uid, $gid, 0, " . GRANT_DIRECT . ")" + ); + } + } + + # Create user can bless group grants for old groupsets, but only + # if we're upgrading from a Bugzilla that had blessing. + if ($dbh->bz_column_info('profiles', 'blessgroupset')) { + + # Get each user with the old blessgroupset bit set + $sth2 = $dbh->prepare( + "SELECT userid FROM profiles + WHERE (blessgroupset & $bit) != 0" + ); + $sth2->execute(); + while (my ($uid) = $sth2->fetchrow_array) { + $dbh->do( + "INSERT INTO user_group_map (user_id, group_id, isbless, grant_type) - VALUES ($uid, $gid, 1, " . GRANT_DIRECT . ")"); - } - } - # Create bug_group_map records for old groupsets. - # Get each bug with the old group bit set. - $sth2 = $dbh->prepare("SELECT bug_id FROM bugs - WHERE (groupset & $bit) != 0"); - $sth2->execute(); - while (my ($bug_id) = $sth2->fetchrow_array) { - # Insert the bug, group pair into the bug_group_map. - $dbh->do("INSERT INTO bug_group_map (bug_id, group_id) - VALUES ($bug_id, $gid)"); - } + VALUES ($uid, $gid, 1, " . GRANT_DIRECT . ")" + ); } - # Replace old activity log groupset records with lists of names - # of groups. - $sth = $dbh->prepare("SELECT id FROM fielddefs - WHERE name = " . $dbh->quote('bug_group')); - $sth->execute(); - my ($bgfid) = $sth->fetchrow_array; - # Get the field id for the old groupset field - $sth = $dbh->prepare("SELECT id FROM fielddefs - WHERE name = " . $dbh->quote('groupset')); - $sth->execute(); - my ($gsid) = $sth->fetchrow_array; - # Get all bugs_activity records from groupset changes - if ($gsid) { - $sth = $dbh->prepare("SELECT bug_id, bug_when, who, added, removed - FROM bugs_activity WHERE fieldid = $gsid"); - $sth->execute(); - while (my ($bug_id, $bug_when, $who, $added, $removed) = - $sth->fetchrow_array) - { - $added ||= 0; - $removed ||= 0; - # Get names of groups added. - my $sth2 = $dbh->prepare("SELECT name FROM groups - WHERE (bit & $added) != 0 - AND (bit & $removed) = 0"); - $sth2->execute(); - my @logadd; - while (my ($n) = $sth2->fetchrow_array) { - push @logadd, $n; - } - # Get names of groups removed. - $sth2 = $dbh->prepare("SELECT name FROM groups - WHERE (bit & $removed) != 0 - AND (bit & $added) = 0"); - $sth2->execute(); - my @logrem; - while (my ($n) = $sth2->fetchrow_array) { - push @logrem, $n; - } - # Get list of group bits added that correspond to - # missing groups. - $sth2 = $dbh->prepare("SELECT ($added & ~BIT_OR(bit)) - FROM groups"); - $sth2->execute(); - my ($miss) = $sth2->fetchrow_array; - if ($miss) { - push @logadd, _list_bits($miss); - print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id", - " CONTAINS DELETED GROUPS\n"; - } - # Get list of group bits deleted that correspond to - # missing groups. - $sth2 = $dbh->prepare("SELECT ($removed & ~BIT_OR(bit)) - FROM groups"); - $sth2->execute(); - ($miss) = $sth2->fetchrow_array; - if ($miss) { - push @logrem, _list_bits($miss); - print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id", - " CONTAINS DELETED GROUPS\n"; - } - my $logr = ""; - my $loga = ""; - $logr = join(", ", @logrem) . '?' if @logrem; - $loga = join(", ", @logadd) . '?' if @logadd; - # Replace to old activity record with the converted data. - $dbh->do("UPDATE bugs_activity SET fieldid = $bgfid, added = " . - $dbh->quote($loga) . ", removed = " . - $dbh->quote($logr) . - " WHERE bug_id = $bug_id AND bug_when = " . - $dbh->quote($bug_when) . - " AND who = $who AND fieldid = $gsid"); - } - # Replace groupset changes with group name changes in - # profiles_activity. Get profiles_activity records for groupset. - $sth = $dbh->prepare( - "SELECT userid, profiles_when, who, newvalue, oldvalue " . - "FROM profiles_activity " . - "WHERE fieldid = $gsid"); - $sth->execute(); - while (my ($uid, $uwhen, $uwho, $added, $removed) = - $sth->fetchrow_array) - { - $added ||= 0; - $removed ||= 0; - # Get names of groups added. - my $sth2 = $dbh->prepare("SELECT name FROM groups + } + + # Create bug_group_map records for old groupsets. + # Get each bug with the old group bit set. + $sth2 = $dbh->prepare( + "SELECT bug_id FROM bugs + WHERE (groupset & $bit) != 0" + ); + $sth2->execute(); + while (my ($bug_id) = $sth2->fetchrow_array) { + + # Insert the bug, group pair into the bug_group_map. + $dbh->do( + "INSERT INTO bug_group_map (bug_id, group_id) + VALUES ($bug_id, $gid)" + ); + } + } + + # Replace old activity log groupset records with lists of names + # of groups. + $sth = $dbh->prepare( + "SELECT id FROM fielddefs + WHERE name = " . $dbh->quote('bug_group') + ); + $sth->execute(); + my ($bgfid) = $sth->fetchrow_array; + + # Get the field id for the old groupset field + $sth = $dbh->prepare( + "SELECT id FROM fielddefs + WHERE name = " . $dbh->quote('groupset') + ); + $sth->execute(); + my ($gsid) = $sth->fetchrow_array; + + # Get all bugs_activity records from groupset changes + if ($gsid) { + $sth = $dbh->prepare( + "SELECT bug_id, bug_when, who, added, removed + FROM bugs_activity WHERE fieldid = $gsid" + ); + $sth->execute(); + while (my ($bug_id, $bug_when, $who, $added, $removed) = $sth->fetchrow_array) { + $added ||= 0; + $removed ||= 0; + + # Get names of groups added. + my $sth2 = $dbh->prepare( + "SELECT name FROM groups WHERE (bit & $added) != 0 - AND (bit & $removed) = 0"); - $sth2->execute(); - my @logadd; - while (my ($n) = $sth2->fetchrow_array) { - push @logadd, $n; - } - # Get names of groups removed. - $sth2 = $dbh->prepare("SELECT name FROM groups + AND (bit & $removed) = 0" + ); + $sth2->execute(); + my @logadd; + while (my ($n) = $sth2->fetchrow_array) { + push @logadd, $n; + } + + # Get names of groups removed. + $sth2 = $dbh->prepare( + "SELECT name FROM groups WHERE (bit & $removed) != 0 - AND (bit & $added) = 0"); - $sth2->execute(); - my @logrem; - while (my ($n) = $sth2->fetchrow_array) { - push @logrem, $n; - } - my $ladd = ""; - my $lrem = ""; - $ladd = join(", ", @logadd) . '?' if @logadd; - $lrem = join(", ", @logrem) . '?' if @logrem; - # Replace profiles_activity record for groupset change - # with group list. - $dbh->do("UPDATE profiles_activity " . - "SET fieldid = $bgfid, newvalue = " . - $dbh->quote($ladd) . ", oldvalue = " . - $dbh->quote($lrem) . - " WHERE userid = $uid AND profiles_when = " . - $dbh->quote($uwhen) . - " AND who = $uwho AND fieldid = $gsid"); - } + AND (bit & $added) = 0" + ); + $sth2->execute(); + my @logrem; + while (my ($n) = $sth2->fetchrow_array) { + push @logrem, $n; } - # Identify admin group. - my ($admin_gid) = $dbh->selectrow_array( - "SELECT id FROM groups WHERE name = 'admin'"); - if (!$admin_gid) { - $dbh->do(q{INSERT INTO groups (name, description) - VALUES ('admin', 'Administrators')}); - $admin_gid = $dbh->bz_last_key('groups', 'id'); + # Get list of group bits added that correspond to + # missing groups. + $sth2 = $dbh->prepare( + "SELECT ($added & ~BIT_OR(bit)) + FROM groups" + ); + $sth2->execute(); + my ($miss) = $sth2->fetchrow_array; + if ($miss) { + push @logadd, _list_bits($miss); + print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id", + " CONTAINS DELETED GROUPS\n"; } - # Find current admins - my @admins; - # Don't lose admins from DBs where Bug 157704 applies - $sth = $dbh->prepare( - "SELECT userid, (groupset & 65536), login_name " . - "FROM profiles " . - "WHERE (groupset | 65536) = 9223372036854775807"); - $sth->execute(); - while ( my ($userid, $iscomplete, $login_name) - = $sth->fetchrow_array() ) - { - # existing administrators are made members of group "admin" - print "\nWARNING - $login_name IS AN ADMIN IN SPITE OF BUG", - " 157704\n\n" if (!$iscomplete); - push(@admins, $userid) unless grep($_ eq $userid, @admins); + + # Get list of group bits deleted that correspond to + # missing groups. + $sth2 = $dbh->prepare( + "SELECT ($removed & ~BIT_OR(bit)) + FROM groups" + ); + $sth2->execute(); + ($miss) = $sth2->fetchrow_array; + if ($miss) { + push @logrem, _list_bits($miss); + print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id", + " CONTAINS DELETED GROUPS\n"; } - # Now make all those users admins directly. They were already - # added to every other group, above, because of their groupset. - foreach my $admin_id (@admins) { - $dbh->do("INSERT INTO user_group_map - (user_id, group_id, isbless, grant_type) - VALUES (?, ?, ?, ?)", - undef, $admin_id, $admin_gid, $_, GRANT_DIRECT) - foreach (0, 1); + my $logr = ""; + my $loga = ""; + $logr = join(", ", @logrem) . '?' if @logrem; + $loga = join(", ", @logadd) . '?' if @logadd; + + # Replace to old activity record with the converted data. + $dbh->do("UPDATE bugs_activity SET fieldid = $bgfid, added = " + . $dbh->quote($loga) + . ", removed = " + . $dbh->quote($logr) + . " WHERE bug_id = $bug_id AND bug_when = " + . $dbh->quote($bug_when) + . " AND who = $who AND fieldid = $gsid"); + } + + # Replace groupset changes with group name changes in + # profiles_activity. Get profiles_activity records for groupset. + $sth + = $dbh->prepare("SELECT userid, profiles_when, who, newvalue, oldvalue " + . "FROM profiles_activity " + . "WHERE fieldid = $gsid"); + $sth->execute(); + while (my ($uid, $uwhen, $uwho, $added, $removed) = $sth->fetchrow_array) { + $added ||= 0; + $removed ||= 0; + + # Get names of groups added. + my $sth2 = $dbh->prepare( + "SELECT name FROM groups + WHERE (bit & $added) != 0 + AND (bit & $removed) = 0" + ); + $sth2->execute(); + my @logadd; + while (my ($n) = $sth2->fetchrow_array) { + push @logadd, $n; + } + + # Get names of groups removed. + $sth2 = $dbh->prepare( + "SELECT name FROM groups + WHERE (bit & $removed) != 0 + AND (bit & $added) = 0" + ); + $sth2->execute(); + my @logrem; + while (my ($n) = $sth2->fetchrow_array) { + push @logrem, $n; } + my $ladd = ""; + my $lrem = ""; + $ladd = join(", ", @logadd) . '?' if @logadd; + $lrem = join(", ", @logrem) . '?' if @logrem; + + # Replace profiles_activity record for groupset change + # with group list. + $dbh->do("UPDATE profiles_activity " + . "SET fieldid = $bgfid, newvalue = " + . $dbh->quote($ladd) + . ", oldvalue = " + . $dbh->quote($lrem) + . " WHERE userid = $uid AND profiles_when = " + . $dbh->quote($uwhen) + . " AND who = $uwho AND fieldid = $gsid"); + } + } - $dbh->bz_drop_column('profiles','groupset'); - $dbh->bz_drop_column('profiles','blessgroupset'); - $dbh->bz_drop_column('bugs','groupset'); - $dbh->bz_drop_column('groups','bit'); - $dbh->do("DELETE FROM fielddefs WHERE name = " - . $dbh->quote('groupset')); + # Identify admin group. + my ($admin_gid) + = $dbh->selectrow_array("SELECT id FROM groups WHERE name = 'admin'"); + if (!$admin_gid) { + $dbh->do( + q{INSERT INTO groups (name, description) + VALUES ('admin', 'Administrators')} + ); + $admin_gid = $dbh->bz_last_key('groups', 'id'); } + + # Find current admins + my @admins; + + # Don't lose admins from DBs where Bug 157704 applies + $sth + = $dbh->prepare("SELECT userid, (groupset & 65536), login_name " + . "FROM profiles " + . "WHERE (groupset | 65536) = 9223372036854775807"); + $sth->execute(); + while (my ($userid, $iscomplete, $login_name) = $sth->fetchrow_array()) { + + # existing administrators are made members of group "admin" + print "\nWARNING - $login_name IS AN ADMIN IN SPITE OF BUG", " 157704\n\n" + if (!$iscomplete); + push(@admins, $userid) unless grep($_ eq $userid, @admins); + } + + # Now make all those users admins directly. They were already + # added to every other group, above, because of their groupset. + foreach my $admin_id (@admins) { + $dbh->do( + "INSERT INTO user_group_map + (user_id, group_id, isbless, grant_type) + VALUES (?, ?, ?, ?)", undef, $admin_id, $admin_gid, $_, + GRANT_DIRECT + ) foreach (0, 1); + } + + $dbh->bz_drop_column('profiles', 'groupset'); + $dbh->bz_drop_column('profiles', 'blessgroupset'); + $dbh->bz_drop_column('bugs', 'groupset'); + $dbh->bz_drop_column('groups', 'bit'); + $dbh->do("DELETE FROM fielddefs WHERE name = " . $dbh->quote('groupset')); + } } sub _convert_attachment_statuses_to_flags { - my $dbh = Bugzilla->dbh; + my $dbh = Bugzilla->dbh; + + # September 2002 myk@mozilla.org bug 98801 + # Convert the attachment statuses tables into flags tables. + if ( $dbh->bz_table_info("attachstatuses") + && $dbh->bz_table_info("attachstatusdefs")) + { + print "Converting attachment statuses to flags...\n"; + + # Get IDs for the old attachment status and new flag fields. + my ($old_field_id) + = $dbh->selectrow_array( + "SELECT id FROM fielddefs WHERE name='attachstatusdefs.name'") + || 0; + my ($new_field_id) + = $dbh->selectrow_array( + "SELECT id FROM fielddefs WHERE name = 'flagtypes.name'"); + + # Convert attachment status definitions to flag types. If more than one + # status has the same name and description, it is merged into a single + # status with multiple inclusion records. - # September 2002 myk@mozilla.org bug 98801 - # Convert the attachment statuses tables into flags tables. - if ($dbh->bz_table_info("attachstatuses") - && $dbh->bz_table_info("attachstatusdefs")) - { - print "Converting attachment statuses to flags...\n"; - - # Get IDs for the old attachment status and new flag fields. - my ($old_field_id) = $dbh->selectrow_array( - "SELECT id FROM fielddefs WHERE name='attachstatusdefs.name'") - || 0; - my ($new_field_id) = $dbh->selectrow_array( - "SELECT id FROM fielddefs WHERE name = 'flagtypes.name'"); - - # Convert attachment status definitions to flag types. If more than one - # status has the same name and description, it is merged into a single - # status with multiple inclusion records. - - my $sth = $dbh->prepare( - "SELECT id, name, description, sortkey, product_id - FROM attachstatusdefs"); - - # status definition IDs indexed by name/description - my $def_ids = {}; - - # merged IDs and the IDs they were merged into. The key is the old ID, - # the value is the new one. This allows us to give statuses the right - # ID when we convert them over to flags. This map includes IDs that - # weren't merged (in this case the old and new IDs are the same), since - # it makes the code simpler. - my $def_id_map = {}; - - $sth->execute(); - while (my ($id, $name, $desc, $sortkey, $prod_id) = - $sth->fetchrow_array()) - { - my $key = $name . $desc; - if (!$def_ids->{$key}) { - $def_ids->{$key} = $id; - my $quoted_name = $dbh->quote($name); - my $quoted_desc = $dbh->quote($desc); - $dbh->do("INSERT INTO flagtypes (id, name, description, + my $sth = $dbh->prepare( + "SELECT id, name, description, sortkey, product_id + FROM attachstatusdefs" + ); + + # status definition IDs indexed by name/description + my $def_ids = {}; + + # merged IDs and the IDs they were merged into. The key is the old ID, + # the value is the new one. This allows us to give statuses the right + # ID when we convert them over to flags. This map includes IDs that + # weren't merged (in this case the old and new IDs are the same), since + # it makes the code simpler. + my $def_id_map = {}; + + $sth->execute(); + while (my ($id, $name, $desc, $sortkey, $prod_id) = $sth->fetchrow_array()) { + my $key = $name . $desc; + if (!$def_ids->{$key}) { + $def_ids->{$key} = $id; + my $quoted_name = $dbh->quote($name); + my $quoted_desc = $dbh->quote($desc); + $dbh->do( + "INSERT INTO flagtypes (id, name, description, sortkey, target_type) VALUES ($id, $quoted_name, $quoted_desc, - $sortkey,'a')"); - } - $def_id_map->{$id} = $def_ids->{$key}; - $dbh->do("INSERT INTO flaginclusions (type_id, product_id) - VALUES ($def_id_map->{$id}, $prod_id)"); - } + $sortkey,'a')" + ); + } + $def_id_map->{$id} = $def_ids->{$key}; + $dbh->do( + "INSERT INTO flaginclusions (type_id, product_id) + VALUES ($def_id_map->{$id}, $prod_id)" + ); + } - # Note: even though we've converted status definitions, we still - # can't drop the table because we need it to convert the statuses - # themselves. - - # Convert attachment statuses to flags. To do this we select - # the statuses from the status table and then, for each one, - # figure out who set it and when they set it from the bugs - # activity table. - my $id = 0; - $sth = $dbh->prepare( - "SELECT attachstatuses.attach_id, attachstatusdefs.id, + # Note: even though we've converted status definitions, we still + # can't drop the table because we need it to convert the statuses + # themselves. + + # Convert attachment statuses to flags. To do this we select + # the statuses from the status table and then, for each one, + # figure out who set it and when they set it from the bugs + # activity table. + my $id = 0; + $sth = $dbh->prepare( + "SELECT attachstatuses.attach_id, attachstatusdefs.id, attachstatusdefs.name, attachments.bug_id FROM attachstatuses, attachstatusdefs, attachments WHERE attachstatuses.statusid = attachstatusdefs.id - AND attachstatuses.attach_id = attachments.attach_id"); + AND attachstatuses.attach_id = attachments.attach_id" + ); - # a query to determine when the attachment status was set and who set it - my $sth2 = $dbh->prepare("SELECT added, who, bug_when + # a query to determine when the attachment status was set and who set it + my $sth2 = $dbh->prepare( + "SELECT added, who, bug_when FROM bugs_activity WHERE bug_id = ? AND attach_id = ? AND fieldid = $old_field_id - ORDER BY bug_when DESC"); - - $sth->execute(); - while (my ($attach_id, $def_id, $status, $bug_id) = - $sth->fetchrow_array()) - { - ++$id; - - # Determine when the attachment status was set and who set it. - # We should always be able to find out this info from the bug - # activity, but we fall back to default values just in case. - $sth2->execute($bug_id, $attach_id); - my ($added, $who, $when); - while (($added, $who, $when) = $sth2->fetchrow_array()) { - last if $added =~ /(^|[, ]+)\Q$status\E([, ]+|$)/; - } - $who = $dbh->quote($who); # "NULL" by default if $who is undefined - $when = $when ? $dbh->quote($when) : "NOW()"; - + ORDER BY bug_when DESC" + ); - $dbh->do("INSERT INTO flags (id, type_id, status, bug_id, + $sth->execute(); + while (my ($attach_id, $def_id, $status, $bug_id) = $sth->fetchrow_array()) { + ++$id; + + # Determine when the attachment status was set and who set it. + # We should always be able to find out this info from the bug + # activity, but we fall back to default values just in case. + $sth2->execute($bug_id, $attach_id); + my ($added, $who, $when); + while (($added, $who, $when) = $sth2->fetchrow_array()) { + last if $added =~ /(^|[, ]+)\Q$status\E([, ]+|$)/; + } + $who = $dbh->quote($who); # "NULL" by default if $who is undefined + $when = $when ? $dbh->quote($when) : "NOW()"; + + + $dbh->do( + "INSERT INTO flags (id, type_id, status, bug_id, attach_id, creation_date, modification_date, requestee_id, setter_id) VALUES ($id, $def_id_map->{$def_id}, '+', $bug_id, - $attach_id, $when, $when, NULL, $who)"); - } + $attach_id, $when, $when, NULL, $who)" + ); + } - # Now that we've converted both tables we can drop them. - $dbh->bz_drop_table("attachstatuses"); - $dbh->bz_drop_table("attachstatusdefs"); + # Now that we've converted both tables we can drop them. + $dbh->bz_drop_table("attachstatuses"); + $dbh->bz_drop_table("attachstatusdefs"); - # Convert activity records for attachment statuses into records - # for flags. - $sth = $dbh->prepare("SELECT attach_id, who, bug_when, added, + # Convert activity records for attachment statuses into records + # for flags. + $sth = $dbh->prepare( + "SELECT attach_id, who, bug_when, added, removed FROM bugs_activity - WHERE fieldid = $old_field_id"); - $sth->execute(); - while (my ($attach_id, $who, $when, $old_added, $old_removed) = - $sth->fetchrow_array()) - { - my @additions = split(/[, ]+/, $old_added); - @additions = map("$_+", @additions); - my $new_added = $dbh->quote(join(", ", @additions)); - - my @removals = split(/[, ]+/, $old_removed); - @removals = map("$_+", @removals); - my $new_removed = $dbh->quote(join(", ", @removals)); - - $old_added = $dbh->quote($old_added); - $old_removed = $dbh->quote($old_removed); - $who = $dbh->quote($who); - $when = $dbh->quote($when); - - $dbh->do("UPDATE bugs_activity SET fieldid = $new_field_id, " . - "added = $new_added, removed = $new_removed " . - "WHERE attach_id = $attach_id AND who = $who " . - "AND bug_when = $when AND fieldid = $old_field_id " . - "AND added = $old_added AND removed = $old_removed"); - } + WHERE fieldid = $old_field_id" + ); + $sth->execute(); + while (my ($attach_id, $who, $when, $old_added, $old_removed) + = $sth->fetchrow_array()) + { + my @additions = split(/[, ]+/, $old_added); + @additions = map("$_+", @additions); + my $new_added = $dbh->quote(join(", ", @additions)); + + my @removals = split(/[, ]+/, $old_removed); + @removals = map("$_+", @removals); + my $new_removed = $dbh->quote(join(", ", @removals)); + + $old_added = $dbh->quote($old_added); + $old_removed = $dbh->quote($old_removed); + $who = $dbh->quote($who); + $when = $dbh->quote($when); + + $dbh->do("UPDATE bugs_activity SET fieldid = $new_field_id, " + . "added = $new_added, removed = $new_removed " + . "WHERE attach_id = $attach_id AND who = $who " + . "AND bug_when = $when AND fieldid = $old_field_id " + . "AND added = $old_added AND removed = $old_removed"); + } - # Remove the attachment status field from the field definitions. - $dbh->do("DELETE FROM fielddefs WHERE name='attachstatusdefs.name'"); + # Remove the attachment status field from the field definitions. + $dbh->do("DELETE FROM fielddefs WHERE name='attachstatusdefs.name'"); - print "done.\n"; - } + print "done.\n"; + } } sub _remove_spaces_and_commas_from_flagtypes { - my $dbh = Bugzilla->dbh; - # Get all names and IDs, to find broken ones and to - # check for collisions when renaming. - my $sth = $dbh->prepare("SELECT name, id FROM flagtypes"); - $sth->execute(); - - my %flagtypes; - my @badflagnames; - # find broken flagtype names, and populate a hash table - # to check for collisions. - while (my ($name, $id) = $sth->fetchrow_array()) { - $flagtypes{$name} = $id; - if ($name =~ /[ ,]/) { - push(@badflagnames, $name); - } + my $dbh = Bugzilla->dbh; + + # Get all names and IDs, to find broken ones and to + # check for collisions when renaming. + my $sth = $dbh->prepare("SELECT name, id FROM flagtypes"); + $sth->execute(); + + my %flagtypes; + my @badflagnames; + + # find broken flagtype names, and populate a hash table + # to check for collisions. + while (my ($name, $id) = $sth->fetchrow_array()) { + $flagtypes{$name} = $id; + if ($name =~ /[ ,]/) { + push(@badflagnames, $name); } - if (@badflagnames) { - print "Removing spaces and commas from flag names...\n"; - my ($flagname, $tryflagname); - my $sth = $dbh->prepare("UPDATE flagtypes SET name = ? WHERE id = ?"); - foreach $flagname (@badflagnames) { - print " Bad flag type name \"$flagname\" ...\n"; - # find a new name for this flagtype. - ($tryflagname = $flagname) =~ tr/ ,/__/; - # avoid collisions with existing flagtype names. - while (defined($flagtypes{$tryflagname})) { - print " ... can't rename as \"$tryflagname\" ...\n"; - $tryflagname .= "'"; - if (length($tryflagname) > 50) { - my $lastchanceflagname = (substr $tryflagname, 0, 47) . '...'; - if (defined($flagtypes{$lastchanceflagname})) { - print " ... last attempt as \"$lastchanceflagname\" still failed.'\n"; - die install_string('update_flags_bad_name', - { flag => $flagname }), "\n"; - } - $tryflagname = $lastchanceflagname; - } - } - $sth->execute($tryflagname, $flagtypes{$flagname}); - print " renamed flag type \"$flagname\" as \"$tryflagname\"\n"; - $flagtypes{$tryflagname} = $flagtypes{$flagname}; - delete $flagtypes{$flagname}; + } + if (@badflagnames) { + print "Removing spaces and commas from flag names...\n"; + my ($flagname, $tryflagname); + my $sth = $dbh->prepare("UPDATE flagtypes SET name = ? WHERE id = ?"); + foreach $flagname (@badflagnames) { + print " Bad flag type name \"$flagname\" ...\n"; + + # find a new name for this flagtype. + ($tryflagname = $flagname) =~ tr/ ,/__/; + + # avoid collisions with existing flagtype names. + while (defined($flagtypes{$tryflagname})) { + print " ... can't rename as \"$tryflagname\" ...\n"; + $tryflagname .= "'"; + if (length($tryflagname) > 50) { + my $lastchanceflagname = (substr $tryflagname, 0, 47) . '...'; + if (defined($flagtypes{$lastchanceflagname})) { + print " ... last attempt as \"$lastchanceflagname\" still failed.'\n"; + die install_string('update_flags_bad_name', {flag => $flagname}), "\n"; + } + $tryflagname = $lastchanceflagname; } - print "... done.\n"; + } + $sth->execute($tryflagname, $flagtypes{$flagname}); + print " renamed flag type \"$flagname\" as \"$tryflagname\"\n"; + $flagtypes{$tryflagname} = $flagtypes{$flagname}; + delete $flagtypes{$flagname}; } + print "... done.\n"; + } } sub _setup_usebuggroups_backward_compatibility { - my $dbh = Bugzilla->dbh; - - # Don't run this on newer Bugzillas. This is a reliable test because - # the longdescs table existed in 2.16 (which had usebuggroups) - # but not in 2.18, and this code happens between 2.16 and 2.18. - return if $dbh->bz_column_info('longdescs', 'already_wrapped'); - - # 2002-11-24 - bugreport@peshkin.net - bug 147275 - # - # If group_control_map is empty, backward-compatibility - # usebuggroups-equivalent records should be created. - my ($maps_exist) = $dbh->selectrow_array( - "SELECT DISTINCT 1 FROM group_control_map"); - if (!$maps_exist) { - print "Converting old usebuggroups controls...\n"; - # Initially populate group_control_map. - # First, get all the existing products and their groups. - my $sth = $dbh->prepare("SELECT groups.id, products.id, groups.name, + my $dbh = Bugzilla->dbh; + + # Don't run this on newer Bugzillas. This is a reliable test because + # the longdescs table existed in 2.16 (which had usebuggroups) + # but not in 2.18, and this code happens between 2.16 and 2.18. + return if $dbh->bz_column_info('longdescs', 'already_wrapped'); + + # 2002-11-24 - bugreport@peshkin.net - bug 147275 + # + # If group_control_map is empty, backward-compatibility + # usebuggroups-equivalent records should be created. + my ($maps_exist) + = $dbh->selectrow_array("SELECT DISTINCT 1 FROM group_control_map"); + if (!$maps_exist) { + print "Converting old usebuggroups controls...\n"; + + # Initially populate group_control_map. + # First, get all the existing products and their groups. + my $sth = $dbh->prepare( + "SELECT groups.id, products.id, groups.name, products.name FROM groups, products - WHERE isbuggroup != 0"); - $sth->execute(); - while (my ($groupid, $productid, $groupname, $productname) - = $sth->fetchrow_array()) - { - if ($groupname eq $productname) { - # Product and group have same name. - $dbh->do("INSERT INTO group_control_map " . - "(group_id, product_id, membercontrol, othercontrol) " . - "VALUES (?, ?, ?, ?)", undef, - ($groupid, $productid, CONTROLMAPDEFAULT, CONTROLMAPNA)); - } else { - # See if this group is a product group at all. - my $sth2 = $dbh->prepare("SELECT id FROM products - WHERE name = " .$dbh->quote($groupname)); - $sth2->execute(); - my ($id) = $sth2->fetchrow_array(); - if (!$id) { - # If there is no product with the same name as this - # group, then it is permitted for all products. - $dbh->do("INSERT INTO group_control_map " . - "(group_id, product_id, membercontrol, othercontrol) " . - "VALUES (?, ?, ?, ?)", undef, - ($groupid, $productid, CONTROLMAPSHOWN, CONTROLMAPNA)); - } - } + WHERE isbuggroup != 0" + ); + $sth->execute(); + while (my ($groupid, $productid, $groupname, $productname) + = $sth->fetchrow_array()) + { + if ($groupname eq $productname) { + + # Product and group have same name. + $dbh->do( + "INSERT INTO group_control_map " + . "(group_id, product_id, membercontrol, othercontrol) " + . "VALUES (?, ?, ?, ?)", + undef, + ($groupid, $productid, CONTROLMAPDEFAULT, CONTROLMAPNA) + ); + } + else { + # See if this group is a product group at all. + my $sth2 = $dbh->prepare( + "SELECT id FROM products + WHERE name = " . $dbh->quote($groupname) + ); + $sth2->execute(); + my ($id) = $sth2->fetchrow_array(); + if (!$id) { + + # If there is no product with the same name as this + # group, then it is permitted for all products. + $dbh->do( + "INSERT INTO group_control_map " + . "(group_id, product_id, membercontrol, othercontrol) " + . "VALUES (?, ?, ?, ?)", + undef, + ($groupid, $productid, CONTROLMAPSHOWN, CONTROLMAPNA) + ); } + } } + } } sub _remove_user_series_map { - my $dbh = Bugzilla->dbh; - # 2004-07-17 GRM - Remove "subscriptions" concept from charting, and add - # group-based security instead. - if ($dbh->bz_table_info("user_series_map")) { - # Oracle doesn't like "date" as a column name, and apparently some DBs - # don't like 'value' either. We use the changes to subscriptions as - # something to hang these renamings off. - $dbh->bz_rename_column('series_data', 'date', 'series_date'); - $dbh->bz_rename_column('series_data', 'value', 'series_value'); - - # series_categories.category_id produces a too-long column name for the - # auto-incrementing sequence (Oracle again). - $dbh->bz_rename_column('series_categories', 'category_id', 'id'); - - $dbh->bz_add_column("series", "public", - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - - # Migrate public-ness across from user_series_map to new field - my $sth = $dbh->prepare("SELECT series_id from user_series_map " . - "WHERE user_id = 0"); - $sth->execute(); - while (my ($public_series_id) = $sth->fetchrow_array()) { - $dbh->do("UPDATE series SET public = 1 " . - "WHERE series_id = $public_series_id"); - } + my $dbh = Bugzilla->dbh; + + # 2004-07-17 GRM - Remove "subscriptions" concept from charting, and add + # group-based security instead. + if ($dbh->bz_table_info("user_series_map")) { + + # Oracle doesn't like "date" as a column name, and apparently some DBs + # don't like 'value' either. We use the changes to subscriptions as + # something to hang these renamings off. + $dbh->bz_rename_column('series_data', 'date', 'series_date'); + $dbh->bz_rename_column('series_data', 'value', 'series_value'); - $dbh->bz_drop_table("user_series_map"); + # series_categories.category_id produces a too-long column name for the + # auto-incrementing sequence (Oracle again). + $dbh->bz_rename_column('series_categories', 'category_id', 'id'); + + $dbh->bz_add_column("series", "public", + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + + # Migrate public-ness across from user_series_map to new field + my $sth = $dbh->prepare( + "SELECT series_id from user_series_map " . "WHERE user_id = 0"); + $sth->execute(); + while (my ($public_series_id) = $sth->fetchrow_array()) { + $dbh->do( + "UPDATE series SET public = 1 " . "WHERE series_id = $public_series_id"); } + + $dbh->bz_drop_table("user_series_map"); + } } sub _copy_old_charts_into_database { - my $dbh = Bugzilla->dbh; - my $datadir = bz_locations()->{'datadir'}; - # 2003-06-26 Copy the old charting data into the database, and create the - # queries that will keep it all running. When the old charting system goes - # away, if this code ever runs, it'll just find no files and do nothing. - my $series_exists = $dbh->selectrow_array("SELECT 1 FROM series " . - $dbh->sql_limit(1)); - if (!$series_exists && -d "$datadir/mining" && -e "$datadir/mining/-All-") { - print "Migrating old chart data into database...\n"; - - # We prepare the handle to insert the series data - my $seriesdatasth = $dbh->prepare( - "INSERT INTO series_data (series_id, series_date, series_value) - VALUES (?, ?, ?)"); - - my $deletesth = $dbh->prepare( - "DELETE FROM series_data WHERE series_id = ? AND series_date = ?"); - - my $groupmapsth = $dbh->prepare( - "INSERT INTO category_group_map (category_id, group_id) - VALUES (?, ?)"); - - # Fields in the data file (matches the current collectstats.pl) - my @statuses = - qw(NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED VERIFIED CLOSED); - my @resolutions = - qw(FIXED INVALID WONTFIX LATER REMIND DUPLICATE WORKSFORME MOVED); - my @fields = (@statuses, @resolutions); - - # We have a localization problem here. Where do we get these values? - my $all_name = "-All-"; - my $open_name = "All Open"; - - $dbh->bz_start_transaction(); - my $products = $dbh->selectall_arrayref("SELECT name FROM products"); - - foreach my $product ((map { $_->[0] } @$products), "-All-") { - print "$product:\n"; - # First, create the series - my %queries; - my %seriesids; - - my $query_prod = ""; - if ($product ne "-All-") { - $query_prod = "product=" . html_quote($product) . "&"; - } + my $dbh = Bugzilla->dbh; + my $datadir = bz_locations()->{'datadir'}; + + # 2003-06-26 Copy the old charting data into the database, and create the + # queries that will keep it all running. When the old charting system goes + # away, if this code ever runs, it'll just find no files and do nothing. + my $series_exists + = $dbh->selectrow_array("SELECT 1 FROM series " . $dbh->sql_limit(1)); + if (!$series_exists && -d "$datadir/mining" && -e "$datadir/mining/-All-") { + print "Migrating old chart data into database...\n"; + + # We prepare the handle to insert the series data + my $seriesdatasth = $dbh->prepare( + "INSERT INTO series_data (series_id, series_date, series_value) + VALUES (?, ?, ?)" + ); - # The query for statuses is different to that for resolutions. - $queries{$_} = ($query_prod . "bug_status=$_") foreach (@statuses); - $queries{$_} = ($query_prod . "resolution=$_") - foreach (@resolutions); - - foreach my $field (@fields) { - # Create a Series for each field in this product. - my $series = new Bugzilla::Series(undef, $product, $all_name, - $field, undef, 1, - $queries{$field}, 1); - $series->writeToDatabase(); - $seriesids{$field} = $series->{'series_id'}; - } + my $deletesth = $dbh->prepare( + "DELETE FROM series_data WHERE series_id = ? AND series_date = ?"); - # We also add a new query for "Open", so that migrated products get - # the same set as new products (see editproducts.cgi.) - my @openedstatuses = ("UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED"); - my $query = join("&", map { "bug_status=$_" } @openedstatuses); - my $series = new Bugzilla::Series(undef, $product, $all_name, - $open_name, undef, 1, - $query_prod . $query, 1); - $series->writeToDatabase(); - $seriesids{$open_name} = $series->{'series_id'}; - - # Now, we attempt to read in historical data, if any - # Convert the name in the same way that collectstats.pl does - my $product_file = $product; - $product_file =~ s/\//-/gs; - $product_file = "$datadir/mining/$product_file"; - - # There are many reasons that this might fail (e.g. no stats - # for this product), so we don't worry if it does. - my $in = new IO::File($product_file) or next; - - # The data files should be in a standard format, even for old - # Bugzillas, because of the conversion code further up this file. - my %data; - my $last_date = ""; - - my @lines = <$in>; - while (my $line = shift @lines) { - if ($line =~ /^(\d+\|.*)/) { - my @numbers = split(/\||\r/, $1); - - # Only take the first line for each date; it was possible to - # run collectstats.pl more than once in a day. - next if $numbers[0] eq $last_date; - - for my $i (0 .. $#fields) { - # $numbers[0] is the date - $data{$fields[$i]}{$numbers[0]} = $numbers[$i + 1]; - - # Keep a total of the number of open bugs for this day - if (grep { $_ eq $fields[$i] } @openedstatuses) { - $data{$open_name}{$numbers[0]} += $numbers[$i + 1]; - } - } - - $last_date = $numbers[0]; - } - } + my $groupmapsth = $dbh->prepare( + "INSERT INTO category_group_map (category_id, group_id) + VALUES (?, ?)" + ); - $in->close; - - my $total_items = (scalar(@fields) + 1) - * scalar(keys %{ $data{'NEW'} }); - my $count = 0; - foreach my $field (@fields, $open_name) { - # Insert values into series_data: series_id, date, value - my %fielddata = %{$data{$field}}; - foreach my $date (keys %fielddata) { - # We need to delete in case the text file had duplicate - # entries in it. - $deletesth->execute($seriesids{$field}, $date); - - # We prepared this above - $seriesdatasth->execute($seriesids{$field}, - $date, $fielddata{$date} || 0); - indicate_progress({ total => $total_items, - current => ++$count, every => 100 }); - } - } + # Fields in the data file (matches the current collectstats.pl) + my @statuses = qw(NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED VERIFIED CLOSED); + my @resolutions + = qw(FIXED INVALID WONTFIX LATER REMIND DUPLICATE WORKSFORME MOVED); + my @fields = (@statuses, @resolutions); + + # We have a localization problem here. Where do we get these values? + my $all_name = "-All-"; + my $open_name = "All Open"; - # Create the groupsets for the category - my $category_id = - $dbh->selectrow_array("SELECT id FROM series_categories " . - "WHERE name = " . $dbh->quote($product)); - my $product_id = - $dbh->selectrow_array("SELECT id FROM products " . - "WHERE name = " . $dbh->quote($product)); - - if (defined($category_id) && defined($product_id)) { - - # Get all the mandatory groups for this product - my $group_ids = - $dbh->selectcol_arrayref("SELECT group_id " . - "FROM group_control_map " . - "WHERE product_id = $product_id " . - "AND (membercontrol = " . CONTROLMAPMANDATORY . - " OR othercontrol = " . CONTROLMAPMANDATORY . ")"); - - foreach my $group_id (@$group_ids) { - $groupmapsth->execute($category_id, $group_id); - } + $dbh->bz_start_transaction(); + my $products = $dbh->selectall_arrayref("SELECT name FROM products"); + + foreach my $product ((map { $_->[0] } @$products), "-All-") { + print "$product:\n"; + + # First, create the series + my %queries; + my %seriesids; + + my $query_prod = ""; + if ($product ne "-All-") { + $query_prod = "product=" . html_quote($product) . "&"; + } + + # The query for statuses is different to that for resolutions. + $queries{$_} = ($query_prod . "bug_status=$_") foreach (@statuses); + $queries{$_} = ($query_prod . "resolution=$_") foreach (@resolutions); + + foreach my $field (@fields) { + + # Create a Series for each field in this product. + my $series = new Bugzilla::Series(undef, $product, $all_name, $field, undef, 1, + $queries{$field}, 1); + $series->writeToDatabase(); + $seriesids{$field} = $series->{'series_id'}; + } + + # We also add a new query for "Open", so that migrated products get + # the same set as new products (see editproducts.cgi.) + my @openedstatuses = ("UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED"); + my $query = join("&", map {"bug_status=$_"} @openedstatuses); + my $series + = new Bugzilla::Series(undef, $product, $all_name, $open_name, undef, 1, + $query_prod . $query, 1); + $series->writeToDatabase(); + $seriesids{$open_name} = $series->{'series_id'}; + + # Now, we attempt to read in historical data, if any + # Convert the name in the same way that collectstats.pl does + my $product_file = $product; + $product_file =~ s/\//-/gs; + $product_file = "$datadir/mining/$product_file"; + + # There are many reasons that this might fail (e.g. no stats + # for this product), so we don't worry if it does. + my $in = new IO::File($product_file) or next; + + # The data files should be in a standard format, even for old + # Bugzillas, because of the conversion code further up this file. + my %data; + my $last_date = ""; + + my @lines = <$in>; + while (my $line = shift @lines) { + if ($line =~ /^(\d+\|.*)/) { + my @numbers = split(/\||\r/, $1); + + # Only take the first line for each date; it was possible to + # run collectstats.pl more than once in a day. + next if $numbers[0] eq $last_date; + + for my $i (0 .. $#fields) { + + # $numbers[0] is the date + $data{$fields[$i]}{$numbers[0]} = $numbers[$i + 1]; + + # Keep a total of the number of open bugs for this day + if (grep { $_ eq $fields[$i] } @openedstatuses) { + $data{$open_name}{$numbers[0]} += $numbers[$i + 1]; } + } + + $last_date = $numbers[0]; } + } + + $in->close; + + my $total_items = (scalar(@fields) + 1) * scalar(keys %{$data{'NEW'}}); + my $count = 0; + foreach my $field (@fields, $open_name) { - $dbh->bz_commit_transaction(); + # Insert values into series_data: series_id, date, value + my %fielddata = %{$data{$field}}; + foreach my $date (keys %fielddata) { + + # We need to delete in case the text file had duplicate + # entries in it. + $deletesth->execute($seriesids{$field}, $date); + + # We prepared this above + $seriesdatasth->execute($seriesids{$field}, $date, $fielddata{$date} || 0); + indicate_progress({total => $total_items, current => ++$count, every => 100}); + } + } + + # Create the groupsets for the category + my $category_id = $dbh->selectrow_array( + "SELECT id FROM series_categories " . "WHERE name = " . $dbh->quote($product)); + my $product_id = $dbh->selectrow_array( + "SELECT id FROM products " . "WHERE name = " . $dbh->quote($product)); + + if (defined($category_id) && defined($product_id)) { + + # Get all the mandatory groups for this product + my $group_ids + = $dbh->selectcol_arrayref("SELECT group_id " + . "FROM group_control_map " + . "WHERE product_id = $product_id " + . "AND (membercontrol = " + . CONTROLMAPMANDATORY + . " OR othercontrol = " + . CONTROLMAPMANDATORY + . ")"); + + foreach my $group_id (@$group_ids) { + $groupmapsth->execute($category_id, $group_id); + } + } } + + $dbh->bz_commit_transaction(); + } } sub _add_user_group_map_grant_type { - my $dbh = Bugzilla->dbh; - # 2004-04-12 - Keep regexp-based group permissions up-to-date - Bug 240325 - if ($dbh->bz_column_info("user_group_map", "isderived")) { - $dbh->bz_add_column('user_group_map', 'grant_type', - {TYPE => 'INT1', NOTNULL => 1, DEFAULT => '0'}); - $dbh->do("DELETE FROM user_group_map WHERE isderived != 0"); - $dbh->do("UPDATE user_group_map SET grant_type = " . GRANT_DIRECT); - $dbh->bz_drop_column("user_group_map", "isderived"); - - $dbh->bz_drop_index('user_group_map', 'user_group_map_user_id_idx'); - $dbh->bz_add_index('user_group_map', 'user_group_map_user_id_idx', - {TYPE => 'UNIQUE', - FIELDS => [qw(user_id group_id grant_type isbless)]}); - } + my $dbh = Bugzilla->dbh; + + # 2004-04-12 - Keep regexp-based group permissions up-to-date - Bug 240325 + if ($dbh->bz_column_info("user_group_map", "isderived")) { + $dbh->bz_add_column('user_group_map', 'grant_type', + {TYPE => 'INT1', NOTNULL => 1, DEFAULT => '0'}); + $dbh->do("DELETE FROM user_group_map WHERE isderived != 0"); + $dbh->do("UPDATE user_group_map SET grant_type = " . GRANT_DIRECT); + $dbh->bz_drop_column("user_group_map", "isderived"); + + $dbh->bz_drop_index('user_group_map', 'user_group_map_user_id_idx'); + $dbh->bz_add_index('user_group_map', 'user_group_map_user_id_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(user_id group_id grant_type isbless)]}); + } } sub _add_group_group_map_grant_type { - my $dbh = Bugzilla->dbh; - # 2004-07-16 - Make it possible to have group-group relationships other than - # membership and bless. - if ($dbh->bz_column_info("group_group_map", "isbless")) { - $dbh->bz_add_column('group_group_map', 'grant_type', - {TYPE => 'INT1', NOTNULL => 1, DEFAULT => '0'}); - $dbh->do("UPDATE group_group_map SET grant_type = " . - "IF(isbless, " . GROUP_BLESS . ", " . - GROUP_MEMBERSHIP . ")"); - $dbh->bz_drop_index('group_group_map', 'group_group_map_member_id_idx'); - $dbh->bz_drop_column("group_group_map", "isbless"); - $dbh->bz_add_index('group_group_map', 'group_group_map_member_id_idx', - {TYPE => 'UNIQUE', - FIELDS => [qw(member_id grantor_id grant_type)]}); - } + my $dbh = Bugzilla->dbh; + + # 2004-07-16 - Make it possible to have group-group relationships other than + # membership and bless. + if ($dbh->bz_column_info("group_group_map", "isbless")) { + $dbh->bz_add_column('group_group_map', 'grant_type', + {TYPE => 'INT1', NOTNULL => 1, DEFAULT => '0'}); + $dbh->do("UPDATE group_group_map SET grant_type = " + . "IF(isbless, " + . GROUP_BLESS . ", " + . GROUP_MEMBERSHIP + . ")"); + $dbh->bz_drop_index('group_group_map', 'group_group_map_member_id_idx'); + $dbh->bz_drop_column("group_group_map", "isbless"); + $dbh->bz_add_index( + 'group_group_map', + 'group_group_map_member_id_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(member_id grantor_id grant_type)]} + ); + } } sub _add_longdescs_already_wrapped { - my $dbh = Bugzilla->dbh; - # 2005-01-29 - mkanat@bugzilla.org - if (!$dbh->bz_column_info('longdescs', 'already_wrapped')) { - # Old, pre-wrapped comments should not be auto-wrapped - $dbh->bz_add_column('longdescs', 'already_wrapped', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}, 1); - # If an old comment doesn't have a newline in the first 81 characters, - # (or doesn't contain a newline at all) and it contains a space, - # then it's probably a mis-wrapped comment and we should wrap it - # at display-time. - print "Fixing old, mis-wrapped comments...\n"; - $dbh->do(q{UPDATE longdescs SET already_wrapped = 0 + my $dbh = Bugzilla->dbh; + + # 2005-01-29 - mkanat@bugzilla.org + if (!$dbh->bz_column_info('longdescs', 'already_wrapped')) { + + # Old, pre-wrapped comments should not be auto-wrapped + $dbh->bz_add_column('longdescs', 'already_wrapped', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}, 1); + + # If an old comment doesn't have a newline in the first 81 characters, + # (or doesn't contain a newline at all) and it contains a space, + # then it's probably a mis-wrapped comment and we should wrap it + # at display-time. + print "Fixing old, mis-wrapped comments...\n"; + $dbh->do( + q{UPDATE longdescs SET already_wrapped = 0 WHERE (} . $dbh->sql_position(q{'\n'}, 'thetext') . q{ > 81 OR } . $dbh->sql_position(q{'\n'}, 'thetext') . q{ = 0) - AND SUBSTRING(thetext FROM 1 FOR 80) LIKE '% %'}); - } + AND SUBSTRING(thetext FROM 1 FOR 80) LIKE '% %'} + ); + } } sub _convert_attachments_filename_from_mediumtext { - my $dbh = Bugzilla->dbh; - # 2002 November, myk@mozilla.org, bug 178841: - # - # Convert the "attachments.filename" column from a ridiculously large - # "mediumtext" to a much more sensible "varchar(100)". Also takes - # the opportunity to remove paths from existing filenames, since they - # shouldn't be there for security. Buggy browsers include them, - # and attachment.cgi now takes them out, but old ones need converting. - my $ref = $dbh->bz_column_info("attachments", "filename"); - if ($ref->{TYPE} ne 'varchar(100)') { - print "Removing paths from filenames in attachments table..."; - - my $sth = $dbh->prepare("SELECT attach_id, filename FROM attachments " . - "WHERE " . $dbh->sql_position(q{'/'}, 'filename') . " > 0 OR " . - $dbh->sql_position(q{'\\\\'}, 'filename') . " > 0"); - $sth->execute; - - while (my ($attach_id, $filename) = $sth->fetchrow_array) { - $filename =~ s/^.*[\/\\]//; - my $quoted_filename = $dbh->quote($filename); - $dbh->do("UPDATE attachments SET filename = $quoted_filename " . - "WHERE attach_id = $attach_id"); - } + my $dbh = Bugzilla->dbh; + + # 2002 November, myk@mozilla.org, bug 178841: + # + # Convert the "attachments.filename" column from a ridiculously large + # "mediumtext" to a much more sensible "varchar(100)". Also takes + # the opportunity to remove paths from existing filenames, since they + # shouldn't be there for security. Buggy browsers include them, + # and attachment.cgi now takes them out, but old ones need converting. + my $ref = $dbh->bz_column_info("attachments", "filename"); + if ($ref->{TYPE} ne 'varchar(100)') { + print "Removing paths from filenames in attachments table..."; + + my $sth + = $dbh->prepare("SELECT attach_id, filename FROM attachments " + . "WHERE " + . $dbh->sql_position(q{'/'}, 'filename') + . " > 0 OR " + . $dbh->sql_position(q{'\\\\'}, 'filename') + . " > 0"); + $sth->execute; + + while (my ($attach_id, $filename) = $sth->fetchrow_array) { + $filename =~ s/^.*[\/\\]//; + my $quoted_filename = $dbh->quote($filename); + $dbh->do("UPDATE attachments SET filename = $quoted_filename " + . "WHERE attach_id = $attach_id"); + } - print "Done.\n"; + print "Done.\n"; - $dbh->bz_alter_column("attachments", "filename", - {TYPE => 'varchar(100)', NOTNULL => 1}); - } + $dbh->bz_alter_column("attachments", "filename", + {TYPE => 'varchar(100)', NOTNULL => 1}); + } } sub _rename_votes_count_and_force_group_refresh { - my $dbh = Bugzilla->dbh; - # 2003-04-27 - bugzilla@chimpychompy.org (GavinS) - # - # Bug 180086 (http://bugzilla.mozilla.org/show_bug.cgi?id=180086) - # - # Renaming the 'count' column in the votes table because Sybase doesn't - # like it - return if !$dbh->bz_table_info('votes'); - return if $dbh->bz_column_info('votes', 'count'); - $dbh->bz_rename_column('votes', 'count', 'vote_count'); + my $dbh = Bugzilla->dbh; + + # 2003-04-27 - bugzilla@chimpychompy.org (GavinS) + # + # Bug 180086 (http://bugzilla.mozilla.org/show_bug.cgi?id=180086) + # + # Renaming the 'count' column in the votes table because Sybase doesn't + # like it + return if !$dbh->bz_table_info('votes'); + return if $dbh->bz_column_info('votes', 'count'); + $dbh->bz_rename_column('votes', 'count', 'vote_count'); } sub _fix_group_with_empty_name { - my $dbh = Bugzilla->dbh; - # 2005-01-12 Nick Barnes bug 278010 - # Rename any group which has an empty name. - # Note that there can be at most one such group (because of - # the SQL index on the name column). - my ($emptygroupid) = $dbh->selectrow_array( - "SELECT id FROM groups where name = ''"); - if ($emptygroupid) { - # There is a group with an empty name; find a name to rename it - # as. Must avoid collisions with existing names. Start with - # group_$gid and add _ if necessary. - my $trycount = 0; - my $trygroupname; - my $sth = $dbh->prepare("SELECT 1 FROM groups where name = ?"); - my $name_exists = 1; - - while ($name_exists) { - $trygroupname = "group_$emptygroupid"; - if ($trycount > 0) { - $trygroupname .= "_$trycount"; - } - $name_exists = $dbh->selectrow_array($sth, undef, $trygroupname); - $trycount++; - } - $dbh->do("UPDATE groups SET name = ? WHERE id = ?", - undef, $trygroupname, $emptygroupid); - print "Group $emptygroupid had an empty name; renamed as", - " '$trygroupname'.\n"; + my $dbh = Bugzilla->dbh; + + # 2005-01-12 Nick Barnes bug 278010 + # Rename any group which has an empty name. + # Note that there can be at most one such group (because of + # the SQL index on the name column). + my ($emptygroupid) + = $dbh->selectrow_array("SELECT id FROM groups where name = ''"); + if ($emptygroupid) { + + # There is a group with an empty name; find a name to rename it + # as. Must avoid collisions with existing names. Start with + # group_$gid and add _ if necessary. + my $trycount = 0; + my $trygroupname; + my $sth = $dbh->prepare("SELECT 1 FROM groups where name = ?"); + my $name_exists = 1; + + while ($name_exists) { + $trygroupname = "group_$emptygroupid"; + if ($trycount > 0) { + $trygroupname .= "_$trycount"; + } + $name_exists = $dbh->selectrow_array($sth, undef, $trygroupname); + $trycount++; } + $dbh->do("UPDATE groups SET name = ? WHERE id = ?", + undef, $trygroupname, $emptygroupid); + print "Group $emptygroupid had an empty name; renamed as", + " '$trygroupname'.\n"; + } } # A helper for the emailprefs subs below sub _clone_email_event { - my ($source, $target) = @_; - my $dbh = Bugzilla->dbh; + my ($source, $target) = @_; + my $dbh = Bugzilla->dbh; - $dbh->do("INSERT INTO email_setting (user_id, relationship, event) + $dbh->do( + "INSERT INTO email_setting (user_id, relationship, event) SELECT user_id, relationship, $target FROM email_setting - WHERE event = $source"); + WHERE event = $source" + ); } sub _migrate_email_prefs_to_new_table { - my $dbh = Bugzilla->dbh; - # 2005-03-29 - gerv@gerv.net - bug 73665. - # Migrate email preferences to new email prefs table. - if ($dbh->bz_column_info("profiles", "emailflags")) { - print "Migrating email preferences to new table...\n"; - - # These are the "roles" and "reasons" from the original code, mapped to - # the new terminology of relationships and events. - my %relationships = ("Owner" => REL_ASSIGNEE, - "Reporter" => REL_REPORTER, - "QAcontact" => REL_QA, - "CClist" => REL_CC, - # REL_VOTER was "4" before it was moved to an - # extension. - "Voter" => 4); - - my %events = ("Removeme" => EVT_ADDED_REMOVED, - "Comments" => EVT_COMMENT, - "Attachments" => EVT_ATTACHMENT, - "Status" => EVT_PROJ_MANAGEMENT, - "Resolved" => EVT_OPENED_CLOSED, - "Keywords" => EVT_KEYWORD, - "CC" => EVT_CC, - "Other" => EVT_OTHER, - "Unconfirmed" => EVT_UNCONFIRMED); - - # Request preferences - my %requestprefs = ("FlagRequestee" => EVT_FLAG_REQUESTED, - "FlagRequester" => EVT_REQUESTED_FLAG); - - # We run the below code in a transaction to speed things up. - $dbh->bz_start_transaction(); - - # Select all emailflags flag strings - my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM profiles'); - my $sth = $dbh->prepare("SELECT userid, emailflags FROM profiles"); - $sth->execute(); - my $i = 0; - - while (my ($userid, $flagstring) = $sth->fetchrow_array()) { - $i++; - indicate_progress({ total => $total, current => $i, every => 10 }); - # If the user has never logged in since emailprefs arrived, and the - # temporary code to give them a default string never ran, then - # $flagstring will be null. In this case, they just get all mail. - $flagstring ||= ""; - - # The 255 param is here, because without a third param, split will - # trim any trailing null fields, which causes Perl to eject lots of - # warnings. Any suitably large number would do. - my %emailflags = split(/~/, $flagstring, 255); - - my $sth2 = $dbh->prepare("INSERT into email_setting " . - "(user_id, relationship, event) VALUES (" . - "$userid, ?, ?)"); - foreach my $relationship (keys %relationships) { - foreach my $event (keys %events) { - my $key = "email$relationship$event"; - if (!exists($emailflags{$key}) - || $emailflags{$key} eq 'on') - { - $sth2->execute($relationships{$relationship}, - $events{$event}); - } - } - } - # Note that in the old system, the value of "excludeself" is - # assumed to be off if the preference does not exist in the - # user's list, unlike other preferences whose value is - # assumed to be on if they do not exist. - # - # This preference has changed from global to per-relationship. - if (!exists($emailflags{'ExcludeSelf'}) - || $emailflags{'ExcludeSelf'} ne 'on') - { - foreach my $relationship (keys %relationships) { - $dbh->do("INSERT into email_setting " . - "(user_id, relationship, event) VALUES (" . - $userid . ", " . - $relationships{$relationship}. ", " . - EVT_CHANGED_BY_ME . ")"); - } - } + my $dbh = Bugzilla->dbh; + + # 2005-03-29 - gerv@gerv.net - bug 73665. + # Migrate email preferences to new email prefs table. + if ($dbh->bz_column_info("profiles", "emailflags")) { + print "Migrating email preferences to new table...\n"; + + # These are the "roles" and "reasons" from the original code, mapped to + # the new terminology of relationships and events. + my %relationships = ( + "Owner" => REL_ASSIGNEE, + "Reporter" => REL_REPORTER, + "QAcontact" => REL_QA, + "CClist" => REL_CC, + + # REL_VOTER was "4" before it was moved to an + # extension. + "Voter" => 4 + ); - foreach my $key (keys %requestprefs) { - if (!exists($emailflags{$key}) || $emailflags{$key} eq 'on') { - $dbh->do("INSERT into email_setting " . - "(user_id, relationship, event) VALUES (" . - $userid . ", " . REL_ANY . ", " . - $requestprefs{$key} . ")"); - } - } - } - print "\n"; + my %events = ( + "Removeme" => EVT_ADDED_REMOVED, + "Comments" => EVT_COMMENT, + "Attachments" => EVT_ATTACHMENT, + "Status" => EVT_PROJ_MANAGEMENT, + "Resolved" => EVT_OPENED_CLOSED, + "Keywords" => EVT_KEYWORD, + "CC" => EVT_CC, + "Other" => EVT_OTHER, + "Unconfirmed" => EVT_UNCONFIRMED + ); - # EVT_ATTACHMENT_DATA should initially have identical settings to - # EVT_ATTACHMENT. - _clone_email_event(EVT_ATTACHMENT, EVT_ATTACHMENT_DATA); + # Request preferences + my %requestprefs = ( + "FlagRequestee" => EVT_FLAG_REQUESTED, + "FlagRequester" => EVT_REQUESTED_FLAG + ); + + # We run the below code in a transaction to speed things up. + $dbh->bz_start_transaction(); - $dbh->bz_commit_transaction(); - $dbh->bz_drop_column("profiles", "emailflags"); + # Select all emailflags flag strings + my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM profiles'); + my $sth = $dbh->prepare("SELECT userid, emailflags FROM profiles"); + $sth->execute(); + my $i = 0; + + while (my ($userid, $flagstring) = $sth->fetchrow_array()) { + $i++; + indicate_progress({total => $total, current => $i, every => 10}); + + # If the user has never logged in since emailprefs arrived, and the + # temporary code to give them a default string never ran, then + # $flagstring will be null. In this case, they just get all mail. + $flagstring ||= ""; + + # The 255 param is here, because without a third param, split will + # trim any trailing null fields, which causes Perl to eject lots of + # warnings. Any suitably large number would do. + my %emailflags = split(/~/, $flagstring, 255); + + my $sth2 + = $dbh->prepare("INSERT into email_setting " + . "(user_id, relationship, event) VALUES (" + . "$userid, ?, ?)"); + foreach my $relationship (keys %relationships) { + foreach my $event (keys %events) { + my $key = "email$relationship$event"; + if (!exists($emailflags{$key}) || $emailflags{$key} eq 'on') { + $sth2->execute($relationships{$relationship}, $events{$event}); + } + } + } + + # Note that in the old system, the value of "excludeself" is + # assumed to be off if the preference does not exist in the + # user's list, unlike other preferences whose value is + # assumed to be on if they do not exist. + # + # This preference has changed from global to per-relationship. + if (!exists($emailflags{'ExcludeSelf'}) || $emailflags{'ExcludeSelf'} ne 'on') { + foreach my $relationship (keys %relationships) { + $dbh->do("INSERT into email_setting " + . "(user_id, relationship, event) VALUES (" + . $userid . ", " + . $relationships{$relationship} . ", " + . EVT_CHANGED_BY_ME + . ")"); + } + } + + foreach my $key (keys %requestprefs) { + if (!exists($emailflags{$key}) || $emailflags{$key} eq 'on') { + $dbh->do("INSERT into email_setting " + . "(user_id, relationship, event) VALUES (" + . $userid . ", " + . REL_ANY . ", " + . $requestprefs{$key} + . ")"); + } + } } + print "\n"; + + # EVT_ATTACHMENT_DATA should initially have identical settings to + # EVT_ATTACHMENT. + _clone_email_event(EVT_ATTACHMENT, EVT_ATTACHMENT_DATA); + + $dbh->bz_commit_transaction(); + $dbh->bz_drop_column("profiles", "emailflags"); + } } sub _initialize_new_email_prefs { - my $dbh = Bugzilla->dbh; - # Check for any "new" email settings that wouldn't have been ported over - # during the block above. Since these settings would have otherwise - # fallen under EVT_OTHER, we'll just clone those settings. That way if - # folks have already disabled all of that mail, there won't be any change. - my %events = ( - "Dependency Tree Changes" => EVT_DEPEND_BLOCK, - "Product/Component Changes" => EVT_COMPONENT, - ); - - foreach my $desc (keys %events) { - my $event = $events{$desc}; - my $have_events = $dbh->selectrow_array( - "SELECT 1 FROM email_setting WHERE event = $event " - . $dbh->sql_limit(1)); - - if (!$have_events) { - # No settings in the table yet, so we assume that this is the - # first time it's being set. - print "Initializing \"$desc\" email_setting ...\n"; - _clone_email_event(EVT_OTHER, $event); - } + my $dbh = Bugzilla->dbh; + + # Check for any "new" email settings that wouldn't have been ported over + # during the block above. Since these settings would have otherwise + # fallen under EVT_OTHER, we'll just clone those settings. That way if + # folks have already disabled all of that mail, there won't be any change. + my %events = ( + "Dependency Tree Changes" => EVT_DEPEND_BLOCK, + "Product/Component Changes" => EVT_COMPONENT, + ); + + foreach my $desc (keys %events) { + my $event = $events{$desc}; + my $have_events = $dbh->selectrow_array( + "SELECT 1 FROM email_setting WHERE event = $event " . $dbh->sql_limit(1)); + + if (!$have_events) { + + # No settings in the table yet, so we assume that this is the + # first time it's being set. + print "Initializing \"$desc\" email_setting ...\n"; + _clone_email_event(EVT_OTHER, $event); } + } } sub _change_all_mysql_booleans_to_tinyint { - my $dbh = Bugzilla->dbh; - # 2005-03-27: Standardize all boolean fields to plain "tinyint" - if ( $dbh->isa('Bugzilla::DB::Mysql') ) { - # This is a change to make things consistent with Schema, so we use - # direct-database access methods. - my $quip_info_sth = $dbh->column_info(undef, undef, 'quips', '%'); - my $quips_cols = $quip_info_sth->fetchall_hashref("COLUMN_NAME"); - my $approved_col = $quips_cols->{'approved'}; - if ( $approved_col->{TYPE_NAME} eq 'TINYINT' - and $approved_col->{COLUMN_SIZE} == 1 ) - { - # series.public could have been renamed to series.is_public, - # and so wouldn't need to be fixed manually. - if ($dbh->bz_column_info('series', 'public')) { - $dbh->bz_alter_column_raw('series', 'public', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '0'}); - } - $dbh->bz_alter_column_raw('bug_status', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - $dbh->bz_alter_column_raw('rep_platform', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - $dbh->bz_alter_column_raw('resolution', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - $dbh->bz_alter_column_raw('op_sys', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - $dbh->bz_alter_column_raw('bug_severity', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - $dbh->bz_alter_column_raw('priority', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - $dbh->bz_alter_column_raw('quips', 'approved', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - } - } + my $dbh = Bugzilla->dbh; + + # 2005-03-27: Standardize all boolean fields to plain "tinyint" + if ($dbh->isa('Bugzilla::DB::Mysql')) { + + # This is a change to make things consistent with Schema, so we use + # direct-database access methods. + my $quip_info_sth = $dbh->column_info(undef, undef, 'quips', '%'); + my $quips_cols = $quip_info_sth->fetchall_hashref("COLUMN_NAME"); + my $approved_col = $quips_cols->{'approved'}; + if ( $approved_col->{TYPE_NAME} eq 'TINYINT' + and $approved_col->{COLUMN_SIZE} == 1) + { + # series.public could have been renamed to series.is_public, + # and so wouldn't need to be fixed manually. + if ($dbh->bz_column_info('series', 'public')) { + $dbh->bz_alter_column_raw('series', 'public', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '0'}); + } + $dbh->bz_alter_column_raw('bug_status', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + $dbh->bz_alter_column_raw('rep_platform', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + $dbh->bz_alter_column_raw('resolution', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + $dbh->bz_alter_column_raw('op_sys', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + $dbh->bz_alter_column_raw('bug_severity', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + $dbh->bz_alter_column_raw('priority', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + $dbh->bz_alter_column_raw('quips', 'approved', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + } + } } # A helper for the below function. sub _de_dup_version { - my ($product_id, $version) = @_; - my $dbh = Bugzilla->dbh; - print "Fixing duplicate version $version in product_id $product_id...\n"; - $dbh->do('DELETE FROM versions WHERE product_id = ? AND value = ?', - undef, $product_id, $version); - $dbh->do('INSERT INTO versions (product_id, value) VALUES (?,?)', - undef, $product_id, $version); + my ($product_id, $version) = @_; + my $dbh = Bugzilla->dbh; + print "Fixing duplicate version $version in product_id $product_id...\n"; + $dbh->do('DELETE FROM versions WHERE product_id = ? AND value = ?', + undef, $product_id, $version); + $dbh->do('INSERT INTO versions (product_id, value) VALUES (?,?)', + undef, $product_id, $version); } sub _add_versions_product_id_index { - my $dbh = Bugzilla->dbh; - if (!$dbh->bz_index_info('versions', 'versions_product_id_idx')) { - my $dup_versions = $dbh->selectall_arrayref( - 'SELECT product_id, value FROM versions - GROUP BY product_id, value HAVING COUNT(value) > 1', {Slice=>{}}); - foreach my $dup_version (@$dup_versions) { - _de_dup_version($dup_version->{product_id}, $dup_version->{value}); - } - - $dbh->bz_add_index('versions', 'versions_product_id_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(product_id value)]}); + my $dbh = Bugzilla->dbh; + if (!$dbh->bz_index_info('versions', 'versions_product_id_idx')) { + my $dup_versions = $dbh->selectall_arrayref( + 'SELECT product_id, value FROM versions + GROUP BY product_id, value HAVING COUNT(value) > 1', {Slice => {}} + ); + foreach my $dup_version (@$dup_versions) { + _de_dup_version($dup_version->{product_id}, $dup_version->{value}); } + + $dbh->bz_add_index('versions', 'versions_product_id_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(product_id value)]}); + } } sub _fix_whine_queries_title_and_op_sys_value { - my $dbh = Bugzilla->dbh; - if (!exists $dbh->bz_column_info('whine_queries', 'title')->{DEFAULT}) { - # The below change actually has nothing to do with the whine_queries - # change, it just has to be contained within a schema change so that - # it doesn't run every time we run checksetup. - - # Old Bugzillas have "other" as an OS choice, new ones have "Other" - # (capital O). - print "Setting any 'other' op_sys to 'Other'...\n"; - $dbh->do('UPDATE op_sys SET value = ? WHERE value = ?', - undef, "Other", "other"); - $dbh->do('UPDATE bugs SET op_sys = ? WHERE op_sys = ?', - undef, "Other", "other"); - if (Bugzilla->params->{'defaultopsys'} eq 'other') { - # We can't actually fix the param here, because WriteParams() will - # make $datadir/params unwriteable to the webservergroup. - # It's too much of an ugly hack to copy the permission-fixing code - # down to here. (It would create more potential future bugs than - # it would solve problems.) - print "WARNING: Your 'defaultopsys' param is set to 'other', but" - . " Bugzilla now\n" - . " uses 'Other' (capital O).\n"; - } - - # Add a DEFAULT to whine_queries stuff so that editwhines.cgi - # works on PostgreSQL. - $dbh->bz_alter_column('whine_queries', 'title', {TYPE => 'varchar(128)', - NOTNULL => 1, DEFAULT => "''"}); + my $dbh = Bugzilla->dbh; + if (!exists $dbh->bz_column_info('whine_queries', 'title')->{DEFAULT}) { + + # The below change actually has nothing to do with the whine_queries + # change, it just has to be contained within a schema change so that + # it doesn't run every time we run checksetup. + + # Old Bugzillas have "other" as an OS choice, new ones have "Other" + # (capital O). + print "Setting any 'other' op_sys to 'Other'...\n"; + $dbh->do('UPDATE op_sys SET value = ? WHERE value = ?', undef, "Other", + "other"); + $dbh->do('UPDATE bugs SET op_sys = ? WHERE op_sys = ?', undef, "Other", + "other"); + if (Bugzilla->params->{'defaultopsys'} eq 'other') { + + # We can't actually fix the param here, because WriteParams() will + # make $datadir/params unwriteable to the webservergroup. + # It's too much of an ugly hack to copy the permission-fixing code + # down to here. (It would create more potential future bugs than + # it would solve problems.) + print "WARNING: Your 'defaultopsys' param is set to 'other', but" + . " Bugzilla now\n" + . " uses 'Other' (capital O).\n"; } + + # Add a DEFAULT to whine_queries stuff so that editwhines.cgi + # works on PostgreSQL. + $dbh->bz_alter_column('whine_queries', 'title', + {TYPE => 'varchar(128)', NOTNULL => 1, DEFAULT => "''"}); + } } sub _fix_attachments_submitter_id_idx { - my $dbh = Bugzilla->dbh; - # 2005-06-29 bugreport@peshkin.net, bug 299156 - if ($dbh->bz_index_info('attachments', 'attachments_submitter_id_idx') - && (scalar(@{$dbh->bz_index_info('attachments', - 'attachments_submitter_id_idx' - )->{FIELDS}}) < 2)) - { - $dbh->bz_drop_index('attachments', 'attachments_submitter_id_idx'); - } - $dbh->bz_add_index('attachments', 'attachments_submitter_id_idx', - [qw(submitter_id bug_id)]); + my $dbh = Bugzilla->dbh; + + # 2005-06-29 bugreport@peshkin.net, bug 299156 + if ( + $dbh->bz_index_info('attachments', 'attachments_submitter_id_idx') + && ( + scalar(@{ + $dbh->bz_index_info('attachments', 'attachments_submitter_id_idx')->{FIELDS} + }) < 2 + ) + ) + { + $dbh->bz_drop_index('attachments', 'attachments_submitter_id_idx'); + } + $dbh->bz_add_index('attachments', 'attachments_submitter_id_idx', + [qw(submitter_id bug_id)]); } sub _copy_attachments_thedata_to_attach_data { - my $dbh = Bugzilla->dbh; - # 2005-08-25 - bugreport@peshkin.net - Bug 305333 - if ($dbh->bz_column_info("attachments", "thedata")) { - print "Migrating attachment data to its own table...\n"; - print "(This may take a very long time)\n"; - $dbh->do("INSERT INTO attach_data (id, thedata) - SELECT attach_id, thedata FROM attachments"); - $dbh->bz_drop_column("attachments", "thedata"); - } + my $dbh = Bugzilla->dbh; + + # 2005-08-25 - bugreport@peshkin.net - Bug 305333 + if ($dbh->bz_column_info("attachments", "thedata")) { + print "Migrating attachment data to its own table...\n"; + print "(This may take a very long time)\n"; + $dbh->do( + "INSERT INTO attach_data (id, thedata) + SELECT attach_id, thedata FROM attachments" + ); + $dbh->bz_drop_column("attachments", "thedata"); + } } sub _fix_broken_all_closed_series { - my $dbh = Bugzilla->dbh; - - # 2005-11-26 - wurblzap@gmail.com - Bug 300473 - # Repair broken automatically generated series queries for non-open bugs. - my $broken_series_indicator = - 'field0-0-0=resolution&type0-0-0=notequals&value0-0-0=---'; - my $broken_nonopen_series = - $dbh->selectall_arrayref("SELECT series_id, query FROM series - WHERE query LIKE '$broken_series_indicator%'"); - if (@$broken_nonopen_series) { - print 'Repairing broken series...'; - my $sth_nuke = - $dbh->prepare('DELETE FROM series_data WHERE series_id = ?'); - # This statement is used to repair a series by replacing the broken - # query with the correct one. - my $sth_repair = - $dbh->prepare('UPDATE series SET query = ? WHERE series_id = ?'); - # The corresponding series for open bugs look like one of these two - # variations (bug 225687 changed the order of bug states). - # This depends on the set of bug states representing open bugs not - # to have changed since series creation. - my $open_bugs_query_base_old = - join("&", map { "bug_status=" . url_quote($_) } - ('UNCONFIRMED', 'NEW', 'ASSIGNED', 'REOPENED')); - my $open_bugs_query_base_new = - join("&", map { "bug_status=" . url_quote($_) } - ('NEW', 'REOPENED', 'ASSIGNED', 'UNCONFIRMED')); - my $sth_openbugs_series = - $dbh->prepare("SELECT series_id FROM series WHERE query IN (?, ?)"); - # Statement to find the series which has collected the most data. - my $sth_data_collected = - $dbh->prepare('SELECT count(*) FROM series_data - WHERE series_id = ?'); - # Statement to select a broken non-open bugs count data entry. - my $sth_select_broken_nonopen_data = - $dbh->prepare('SELECT series_date, series_value FROM series_data' . - ' WHERE series_id = ?'); - # Statement to select an open bugs count data entry. - my $sth_select_open_data = - $dbh->prepare('SELECT series_value FROM series_data' . - ' WHERE series_id = ? AND series_date = ?'); - # Statement to fix a broken non-open bugs count data entry. - my $sth_fix_broken_nonopen_data = - $dbh->prepare('UPDATE series_data SET series_value = ?' . - ' WHERE series_id = ? AND series_date = ?'); - # Statement to delete an unfixable broken non-open bugs count data - # entry. - my $sth_delete_broken_nonopen_data = - $dbh->prepare('DELETE FROM series_data' . - ' WHERE series_id = ? AND series_date = ?'); - foreach (@$broken_nonopen_series) { - my ($broken_series_id, $nonopen_bugs_query) = @$_; - - # Determine the product-and-component part of the query. - if ($nonopen_bugs_query =~ /^$broken_series_indicator(.*)$/) { - my $prodcomp = $1; - - # If there is more than one series for the corresponding - # open-bugs series, we pick the one with the most data, - # which should be the one which was generated on creation. - # It's a pity we can't do subselects. - $sth_openbugs_series->execute( - $open_bugs_query_base_old . $prodcomp, - $open_bugs_query_base_new . $prodcomp); - - my ($found_open_series_id, $datacount) = (undef, -1); - foreach my $open_ser_id ($sth_openbugs_series->fetchrow_array) { - $sth_data_collected->execute($open_ser_id); - my ($this_datacount) = $sth_data_collected->fetchrow_array; - if ($this_datacount > $datacount) { - $datacount = $this_datacount; - $found_open_series_id = $open_ser_id; - } - } - - if ($found_open_series_id) { - # Move along corrupted series data and correct it. The - # corruption consists of it being the number of all bugs - # instead of the number of non-open bugs, so we calculate - # the correct count by subtracting the number of open bugs. - # If there is no corresponding open-bugs count for some - # reason (shouldn't happen), we drop the data entry. - print " $broken_series_id..."; - $sth_select_broken_nonopen_data->execute($broken_series_id); - while (my $rowref = - $sth_select_broken_nonopen_data->fetchrow_arrayref) - { - my ($date, $broken_value) = @$rowref; - my ($openbugs_value) = - $dbh->selectrow_array($sth_select_open_data, undef, - $found_open_series_id, $date); - if (defined($openbugs_value)) { - $sth_fix_broken_nonopen_data->execute - ($broken_value - $openbugs_value, - $broken_series_id, $date); - } - else { - print <dbh; + + # 2005-11-26 - wurblzap@gmail.com - Bug 300473 + # Repair broken automatically generated series queries for non-open bugs. + my $broken_series_indicator + = 'field0-0-0=resolution&type0-0-0=notequals&value0-0-0=---'; + my $broken_nonopen_series = $dbh->selectall_arrayref( + "SELECT series_id, query FROM series + WHERE query LIKE '$broken_series_indicator%'" + ); + if (@$broken_nonopen_series) { + print 'Repairing broken series...'; + my $sth_nuke = $dbh->prepare('DELETE FROM series_data WHERE series_id = ?'); + + # This statement is used to repair a series by replacing the broken + # query with the correct one. + my $sth_repair + = $dbh->prepare('UPDATE series SET query = ? WHERE series_id = ?'); + + # The corresponding series for open bugs look like one of these two + # variations (bug 225687 changed the order of bug states). + # This depends on the set of bug states representing open bugs not + # to have changed since series creation. + my $open_bugs_query_base_old = join("&", + map { "bug_status=" . url_quote($_) } + ('UNCONFIRMED', 'NEW', 'ASSIGNED', 'REOPENED')); + my $open_bugs_query_base_new = join("&", + map { "bug_status=" . url_quote($_) } + ('NEW', 'REOPENED', 'ASSIGNED', 'UNCONFIRMED')); + my $sth_openbugs_series + = $dbh->prepare("SELECT series_id FROM series WHERE query IN (?, ?)"); + + # Statement to find the series which has collected the most data. + my $sth_data_collected = $dbh->prepare( + 'SELECT count(*) FROM series_data + WHERE series_id = ?' + ); + + # Statement to select a broken non-open bugs count data entry. + my $sth_select_broken_nonopen_data = $dbh->prepare( + 'SELECT series_date, series_value FROM series_data' . ' WHERE series_id = ?'); + + # Statement to select an open bugs count data entry. + my $sth_select_open_data = $dbh->prepare('SELECT series_value FROM series_data' + . ' WHERE series_id = ? AND series_date = ?'); + + # Statement to fix a broken non-open bugs count data entry. + my $sth_fix_broken_nonopen_data + = $dbh->prepare('UPDATE series_data SET series_value = ?' + . ' WHERE series_id = ? AND series_date = ?'); + + # Statement to delete an unfixable broken non-open bugs count data + # entry. + my $sth_delete_broken_nonopen_data = $dbh->prepare( + 'DELETE FROM series_data' . ' WHERE series_id = ? AND series_date = ?'); + foreach (@$broken_nonopen_series) { + my ($broken_series_id, $nonopen_bugs_query) = @$_; + + # Determine the product-and-component part of the query. + if ($nonopen_bugs_query =~ /^$broken_series_indicator(.*)$/) { + my $prodcomp = $1; + + # If there is more than one series for the corresponding + # open-bugs series, we pick the one with the most data, + # which should be the one which was generated on creation. + # It's a pity we can't do subselects. + $sth_openbugs_series->execute($open_bugs_query_base_old . $prodcomp, + $open_bugs_query_base_new . $prodcomp); + + my ($found_open_series_id, $datacount) = (undef, -1); + foreach my $open_ser_id ($sth_openbugs_series->fetchrow_array) { + $sth_data_collected->execute($open_ser_id); + my ($this_datacount) = $sth_data_collected->fetchrow_array; + if ($this_datacount > $datacount) { + $datacount = $this_datacount; + $found_open_series_id = $open_ser_id; + } + } + + if ($found_open_series_id) { + + # Move along corrupted series data and correct it. The + # corruption consists of it being the number of all bugs + # instead of the number of non-open bugs, so we calculate + # the correct count by subtracting the number of open bugs. + # If there is no corresponding open-bugs count for some + # reason (shouldn't happen), we drop the data entry. + print " $broken_series_id..."; + $sth_select_broken_nonopen_data->execute($broken_series_id); + while (my $rowref = $sth_select_broken_nonopen_data->fetchrow_arrayref) { + my ($date, $broken_value) = @$rowref; + my ($openbugs_value) + = $dbh->selectrow_array($sth_select_open_data, undef, $found_open_series_id, + $date); + if (defined($openbugs_value)) { + $sth_fix_broken_nonopen_data->execute($broken_value - $openbugs_value, + $broken_series_id, $date); + } + else { + print <execute - ($broken_series_id, $date); - } - } - - # Fix the broken query so that it collects correct data - # in the future. - $nonopen_bugs_query =~ - s/^$broken_series_indicator/field0-0-0=resolution&type0-0-0=regexp&value0-0-0=./; - $sth_repair->execute($nonopen_bugs_query, - $broken_series_id); - } - else { - print <execute($broken_series_id, $date); + } + } + + # Fix the broken query so that it collects correct data + # in the future. + $nonopen_bugs_query + =~ s/^$broken_series_indicator/field0-0-0=resolution&type0-0-0=regexp&value0-0-0=./; + $sth_repair->execute($nonopen_bugs_query, $broken_series_id); + } + else { + print <dbh; + my $dbh = Bugzilla->dbh; - my $regex_groups_exist = $dbh->selectrow_array( - "SELECT 1 FROM groups WHERE userregexp = '' " . $dbh->sql_limit(1)); - return if !$regex_groups_exist; + my $regex_groups_exist = $dbh->selectrow_array( + "SELECT 1 FROM groups WHERE userregexp = '' " . $dbh->sql_limit(1)); + return if !$regex_groups_exist; - my $regex_derivations = $dbh->selectrow_array( - 'SELECT 1 FROM user_group_map WHERE grant_type = ' . GRANT_REGEXP - . ' ' . $dbh->sql_limit(1)); - return if $regex_derivations; + my $regex_derivations + = $dbh->selectrow_array('SELECT 1 FROM user_group_map WHERE grant_type = ' + . GRANT_REGEXP . ' ' + . $dbh->sql_limit(1)); + return if $regex_derivations; - print "Deriving regex group memberships...\n"; + print "Deriving regex group memberships...\n"; - # Re-evaluate all regexps, to keep them up-to-date. - my $sth = $dbh->prepare( - "SELECT profiles.userid, profiles.login_name, groups.id, + # Re-evaluate all regexps, to keep them up-to-date. + my $sth = $dbh->prepare( + "SELECT profiles.userid, profiles.login_name, groups.id, groups.userregexp, user_group_map.group_id FROM (profiles CROSS JOIN groups) LEFT JOIN user_group_map ON user_group_map.user_id = profiles.userid AND user_group_map.group_id = groups.id AND user_group_map.grant_type = ? - WHERE userregexp != '' OR user_group_map.group_id IS NOT NULL"); + WHERE userregexp != '' OR user_group_map.group_id IS NOT NULL" + ); - my $sth_add = $dbh->prepare( - "INSERT INTO user_group_map (user_id, group_id, isbless, grant_type) - VALUES (?, ?, 0, " . GRANT_REGEXP . ")"); + my $sth_add = $dbh->prepare( + "INSERT INTO user_group_map (user_id, group_id, isbless, grant_type) + VALUES (?, ?, 0, " . GRANT_REGEXP . ")" + ); - my $sth_del = $dbh->prepare( - "DELETE FROM user_group_map + my $sth_del = $dbh->prepare( + "DELETE FROM user_group_map WHERE user_id = ? AND group_id = ? AND isbless = 0 - AND grant_type = " . GRANT_REGEXP); + AND grant_type = " . GRANT_REGEXP + ); - $sth->execute(GRANT_REGEXP); - while (my ($uid, $login, $gid, $rexp, $present) = - $sth->fetchrow_array()) - { - if ($login =~ m/$rexp/i) { - $sth_add->execute($uid, $gid) unless $present; - } else { - $sth_del->execute($uid, $gid) if $present; - } + $sth->execute(GRANT_REGEXP); + while (my ($uid, $login, $gid, $rexp, $present) = $sth->fetchrow_array()) { + if ($login =~ m/$rexp/i) { + $sth_add->execute($uid, $gid) unless $present; + } + else { + $sth_del->execute($uid, $gid) if $present; } + } } sub _clean_control_characters_from_short_desc { - my $dbh = Bugzilla->dbh; - - # Fixup for Bug 101380 - # "Newlines, nulls, leading/trailing spaces are getting into summaries" - - my $controlchar_bugs = - $dbh->selectall_arrayref("SELECT short_desc, bug_id FROM bugs WHERE " . - $dbh->sql_regexp('short_desc', "'[[:cntrl:]]'")); - if (scalar(@$controlchar_bugs)) { - my $msg = 'Cleaning control characters from bug summaries...'; - my $found = 0; - foreach (@$controlchar_bugs) { - my ($short_desc, $bug_id) = @$_; - my $clean_short_desc = clean_text($short_desc); - if ($clean_short_desc ne $short_desc) { - print $msg if !$found; - $found = 1; - print " $bug_id..."; - $dbh->do("UPDATE bugs SET short_desc = ? WHERE bug_id = ?", - undef, $clean_short_desc, $bug_id); - } - } - print " done.\n" if $found; + my $dbh = Bugzilla->dbh; + + # Fixup for Bug 101380 + # "Newlines, nulls, leading/trailing spaces are getting into summaries" + + my $controlchar_bugs + = $dbh->selectall_arrayref("SELECT short_desc, bug_id FROM bugs WHERE " + . $dbh->sql_regexp('short_desc', "'[[:cntrl:]]'")); + if (scalar(@$controlchar_bugs)) { + my $msg = 'Cleaning control characters from bug summaries...'; + my $found = 0; + foreach (@$controlchar_bugs) { + my ($short_desc, $bug_id) = @$_; + my $clean_short_desc = clean_text($short_desc); + if ($clean_short_desc ne $short_desc) { + print $msg if !$found; + $found = 1; + print " $bug_id..."; + $dbh->do("UPDATE bugs SET short_desc = ? WHERE bug_id = ?", + undef, $clean_short_desc, $bug_id); + } } + print " done.\n" if $found; + } } sub _stop_storing_inactive_flags { - my $dbh = Bugzilla->dbh; - # 2006-03-02 LpSolit@gmail.com - Bug 322285 - # Do not store inactive flags in the DB anymore. - if ($dbh->bz_column_info('flags', 'id')->{'TYPE'} eq 'INT3') { - # We first have to remove all existing inactive flags. - if ($dbh->bz_column_info('flags', 'is_active')) { - $dbh->do('DELETE FROM flags WHERE is_active = 0'); - } + my $dbh = Bugzilla->dbh; - # Now we convert the id column to the auto_increment format. - $dbh->bz_alter_column('flags', 'id', - {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + # 2006-03-02 LpSolit@gmail.com - Bug 322285 + # Do not store inactive flags in the DB anymore. + if ($dbh->bz_column_info('flags', 'id')->{'TYPE'} eq 'INT3') { - # And finally, we remove the is_active column. - $dbh->bz_drop_column('flags', 'is_active'); + # We first have to remove all existing inactive flags. + if ($dbh->bz_column_info('flags', 'is_active')) { + $dbh->do('DELETE FROM flags WHERE is_active = 0'); } + + # Now we convert the id column to the auto_increment format. + $dbh->bz_alter_column('flags', 'id', + {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + + # And finally, we remove the is_active column. + $dbh->bz_drop_column('flags', 'is_active'); + } } sub _change_short_desc_from_mediumtext_to_varchar { - my $dbh = Bugzilla->dbh; - # short_desc should not be a mediumtext, fix anything longer than 255 chars. - if($dbh->bz_column_info('bugs', 'short_desc')->{TYPE} eq 'MEDIUMTEXT') { - # Move extremely long summaries into a comment ("from" the Reporter), - # and then truncate the summary. - my $long_summary_bugs = $dbh->selectall_arrayref( - 'SELECT bug_id, short_desc, reporter - FROM bugs WHERE CHAR_LENGTH(short_desc) > 255'); - - if (@$long_summary_bugs) { - print "\n", install_string('update_summary_truncated'); - my $comment_sth = $dbh->prepare( - 'INSERT INTO longdescs (bug_id, who, thetext, bug_when) - VALUES (?, ?, ?, NOW())'); - my $desc_sth = $dbh->prepare('UPDATE bugs SET short_desc = ? - WHERE bug_id = ?'); - my @affected_bugs; - foreach my $bug (@$long_summary_bugs) { - my ($bug_id, $summary, $reporter_id) = @$bug; - my $summary_comment = - install_string('update_summary_truncate_comment', - { summary => $summary }); - $comment_sth->execute($bug_id, $reporter_id, $summary_comment); - my $short_summary = substr($summary, 0, 252) . "..."; - $desc_sth->execute($short_summary, $bug_id); - push(@affected_bugs, $bug_id); - } - print join(', ', @affected_bugs) . "\n\n"; - } + my $dbh = Bugzilla->dbh; + + # short_desc should not be a mediumtext, fix anything longer than 255 chars. + if ($dbh->bz_column_info('bugs', 'short_desc')->{TYPE} eq 'MEDIUMTEXT') { - $dbh->bz_alter_column('bugs', 'short_desc', {TYPE => 'varchar(255)', - NOTNULL => 1}); + # Move extremely long summaries into a comment ("from" the Reporter), + # and then truncate the summary. + my $long_summary_bugs = $dbh->selectall_arrayref( + 'SELECT bug_id, short_desc, reporter + FROM bugs WHERE CHAR_LENGTH(short_desc) > 255' + ); + + if (@$long_summary_bugs) { + print "\n", install_string('update_summary_truncated'); + my $comment_sth = $dbh->prepare( + 'INSERT INTO longdescs (bug_id, who, thetext, bug_when) + VALUES (?, ?, ?, NOW())' + ); + my $desc_sth = $dbh->prepare( + 'UPDATE bugs SET short_desc = ? + WHERE bug_id = ?' + ); + my @affected_bugs; + foreach my $bug (@$long_summary_bugs) { + my ($bug_id, $summary, $reporter_id) = @$bug; + my $summary_comment + = install_string('update_summary_truncate_comment', {summary => $summary}); + $comment_sth->execute($bug_id, $reporter_id, $summary_comment); + my $short_summary = substr($summary, 0, 252) . "..."; + $desc_sth->execute($short_summary, $bug_id); + push(@affected_bugs, $bug_id); + } + print join(', ', @affected_bugs) . "\n\n"; } + + $dbh->bz_alter_column('bugs', 'short_desc', + {TYPE => 'varchar(255)', NOTNULL => 1}); + } } sub _move_namedqueries_linkinfooter_to_its_own_table { - my $dbh = Bugzilla->dbh; - if ($dbh->bz_column_info("namedqueries", "linkinfooter")) { - # Move link-in-footer information into a table of its own. - my $sth_read = $dbh->prepare('SELECT id, userid + my $dbh = Bugzilla->dbh; + if ($dbh->bz_column_info("namedqueries", "linkinfooter")) { + + # Move link-in-footer information into a table of its own. + my $sth_read = $dbh->prepare( + 'SELECT id, userid FROM namedqueries - WHERE linkinfooter = 1'); - my $sth_write = $dbh->prepare('INSERT INTO namedqueries_link_in_footer - (namedquery_id, user_id) VALUES (?, ?)'); - $sth_read->execute(); - while (my ($id, $userid) = $sth_read->fetchrow_array()) { - $sth_write->execute($id, $userid); - } - $dbh->bz_drop_column("namedqueries", "linkinfooter"); + WHERE linkinfooter = 1' + ); + my $sth_write = $dbh->prepare( + 'INSERT INTO namedqueries_link_in_footer + (namedquery_id, user_id) VALUES (?, ?)' + ); + $sth_read->execute(); + while (my ($id, $userid) = $sth_read->fetchrow_array()) { + $sth_write->execute($id, $userid); } + $dbh->bz_drop_column("namedqueries", "linkinfooter"); + } } sub _add_classifications_sortkey { - my $dbh = Bugzilla->dbh; - # 2006-07-07 olav@bkor.dhs.org - Bug 277377 - # Add a sortkey to the classifications - if (!$dbh->bz_column_info('classifications', 'sortkey')) { - $dbh->bz_add_column('classifications', 'sortkey', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); - - my $class_ids = $dbh->selectcol_arrayref( - 'SELECT id FROM classifications ORDER BY name'); - my $sth = $dbh->prepare('UPDATE classifications SET sortkey = ? ' . - 'WHERE id = ?'); - my $sortkey = 0; - foreach my $class_id (@$class_ids) { - $sth->execute($sortkey, $class_id); - $sortkey += 100; - } + my $dbh = Bugzilla->dbh; + + # 2006-07-07 olav@bkor.dhs.org - Bug 277377 + # Add a sortkey to the classifications + if (!$dbh->bz_column_info('classifications', 'sortkey')) { + $dbh->bz_add_column('classifications', 'sortkey', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0}); + + my $class_ids + = $dbh->selectcol_arrayref('SELECT id FROM classifications ORDER BY name'); + my $sth + = $dbh->prepare('UPDATE classifications SET sortkey = ? ' . 'WHERE id = ?'); + my $sortkey = 0; + foreach my $class_id (@$class_ids) { + $sth->execute($sortkey, $class_id); + $sortkey += 100; } + } } sub _move_data_nomail_into_db { - my $dbh = Bugzilla->dbh; - my $datadir = bz_locations()->{'datadir'}; - # 2006-07-14 karl@kornel.name - Bug 100953 - # If a nomail file exists, move its contents into the DB - $dbh->bz_add_column('profiles', 'disable_mail', - { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE' }); - if (-e "$datadir/nomail") { - # We have a data/nomail file, read it in and delete it - my %nomail; - print "Found a data/nomail file. Moving nomail entries into DB...\n"; - my $nomail_file = new IO::File("$datadir/nomail", 'r'); - while (<$nomail_file>) { - $nomail{trim($_)} = 1; - } - $nomail_file->close; + my $dbh = Bugzilla->dbh; + my $datadir = bz_locations()->{'datadir'}; + + # 2006-07-14 karl@kornel.name - Bug 100953 + # If a nomail file exists, move its contents into the DB + $dbh->bz_add_column('profiles', 'disable_mail', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + if (-e "$datadir/nomail") { + + # We have a data/nomail file, read it in and delete it + my %nomail; + print "Found a data/nomail file. Moving nomail entries into DB...\n"; + my $nomail_file = new IO::File("$datadir/nomail", 'r'); + while (<$nomail_file>) { + $nomail{trim($_)} = 1; + } + $nomail_file->close; - # Go through each entry read. If a user exists, set disable_mail. - my $query = $dbh->prepare('UPDATE profiles + # Go through each entry read. If a user exists, set disable_mail. + my $query = $dbh->prepare( + 'UPDATE profiles SET disable_mail = 1 - WHERE userid = ?'); - foreach my $user_to_check (keys %nomail) { - my $uid = $dbh->selectrow_array( - 'SELECT userid FROM profiles WHERE login_name = ?', - undef, $user_to_check); - next if !$uid; - print "\tDisabling email for user $user_to_check\n"; - $query->execute($uid); - delete $nomail{$user_to_check}; - } - - # If there are any nomail entries remaining, move them to nomail.bad - # and say something to the user. - if (scalar(keys %nomail)) { - print "\n", install_string('update_nomail_bad', - { data => $datadir }), "\n"; - my $nomail_bad = new IO::File("$datadir/nomail.bad", '>>'); - foreach my $unknown_user (keys %nomail) { - print "\t$unknown_user\n"; - print $nomail_bad "$unknown_user\n"; - delete $nomail{$unknown_user}; - } - $nomail_bad->close; - print "\n"; - } + WHERE userid = ?' + ); + foreach my $user_to_check (keys %nomail) { + my $uid + = $dbh->selectrow_array('SELECT userid FROM profiles WHERE login_name = ?', + undef, $user_to_check); + next if !$uid; + print "\tDisabling email for user $user_to_check\n"; + $query->execute($uid); + delete $nomail{$user_to_check}; + } - # Now that we don't need it, get rid of the nomail file. - unlink "$datadir/nomail"; + # If there are any nomail entries remaining, move them to nomail.bad + # and say something to the user. + if (scalar(keys %nomail)) { + print "\n", install_string('update_nomail_bad', {data => $datadir}), "\n"; + my $nomail_bad = new IO::File("$datadir/nomail.bad", '>>'); + foreach my $unknown_user (keys %nomail) { + print "\t$unknown_user\n"; + print $nomail_bad "$unknown_user\n"; + delete $nomail{$unknown_user}; + } + $nomail_bad->close; + print "\n"; } + + # Now that we don't need it, get rid of the nomail file. + unlink "$datadir/nomail"; + } } sub _update_longdescs_who_index { - my $dbh = Bugzilla->dbh; - # When doing a search on who posted a comment, longdescs is joined - # against the bugs table. So we need an index on both of these, - # not just on "who". - my $who_index = $dbh->bz_index_info('longdescs', 'longdescs_who_idx'); - if (!$who_index || scalar @{$who_index->{FIELDS}} == 1) { - # If the index doesn't exist, this will harmlessly do nothing. - $dbh->bz_drop_index('longdescs', 'longdescs_who_idx'); - $dbh->bz_add_index('longdescs', 'longdescs_who_idx', [qw(who bug_id)]); - } + my $dbh = Bugzilla->dbh; + + # When doing a search on who posted a comment, longdescs is joined + # against the bugs table. So we need an index on both of these, + # not just on "who". + my $who_index = $dbh->bz_index_info('longdescs', 'longdescs_who_idx'); + if (!$who_index || scalar @{$who_index->{FIELDS}} == 1) { + + # If the index doesn't exist, this will harmlessly do nothing. + $dbh->bz_drop_index('longdescs', 'longdescs_who_idx'); + $dbh->bz_add_index('longdescs', 'longdescs_who_idx', [qw(who bug_id)]); + } } sub _fix_uppercase_custom_field_names { - # Before the final release of 3.0, custom fields could be - # created with mixed-case names. - my $dbh = Bugzilla->dbh; - my $fields = $dbh->selectall_arrayref( - 'SELECT name, type FROM fielddefs WHERE custom = 1'); - foreach my $row (@$fields) { - my ($name, $type) = @$row; - if ($name ne lc($name)) { - $dbh->bz_rename_column('bugs', $name, lc($name)); - $dbh->bz_rename_table($name, lc($name)) - if $type == FIELD_TYPE_SINGLE_SELECT; - $dbh->do('UPDATE fielddefs SET name = ? WHERE name = ?', - undef, lc($name), $name); - } + + # Before the final release of 3.0, custom fields could be + # created with mixed-case names. + my $dbh = Bugzilla->dbh; + my $fields = $dbh->selectall_arrayref( + 'SELECT name, type FROM fielddefs WHERE custom = 1'); + foreach my $row (@$fields) { + my ($name, $type) = @$row; + if ($name ne lc($name)) { + $dbh->bz_rename_column('bugs', $name, lc($name)); + $dbh->bz_rename_table($name, lc($name)) if $type == FIELD_TYPE_SINGLE_SELECT; + $dbh->do('UPDATE fielddefs SET name = ? WHERE name = ?', + undef, lc($name), $name); } + } } sub _fix_uppercase_index_names { - # We forgot to fix indexes in the above code. - my $dbh = Bugzilla->dbh; - my $fields = $dbh->selectcol_arrayref( - 'SELECT name FROM fielddefs WHERE type = ? AND custom = 1', - undef, FIELD_TYPE_SINGLE_SELECT); - foreach my $field (@$fields) { - my $indexes = $dbh->bz_table_indexes($field); - foreach my $name (keys %$indexes) { - next if $name eq lc($name); - my $index = $indexes->{$name}; - # Lowercase the name and everything in the definition. - my $new_name = lc($name); - my @new_fields = map {lc($_)} @{$index->{FIELDS}}; - my $new_def = {FIELDS => \@new_fields, TYPE => $index->{TYPE}}; - $new_def = \@new_fields if !$index->{TYPE}; - $dbh->bz_drop_index($field, $name); - $dbh->bz_add_index($field, $new_name, $new_def); - } + + # We forgot to fix indexes in the above code. + my $dbh = Bugzilla->dbh; + my $fields + = $dbh->selectcol_arrayref( + 'SELECT name FROM fielddefs WHERE type = ? AND custom = 1', + undef, FIELD_TYPE_SINGLE_SELECT); + foreach my $field (@$fields) { + my $indexes = $dbh->bz_table_indexes($field); + foreach my $name (keys %$indexes) { + next if $name eq lc($name); + my $index = $indexes->{$name}; + + # Lowercase the name and everything in the definition. + my $new_name = lc($name); + my @new_fields = map { lc($_) } @{$index->{FIELDS}}; + my $new_def = {FIELDS => \@new_fields, TYPE => $index->{TYPE}}; + $new_def = \@new_fields if !$index->{TYPE}; + $dbh->bz_drop_index($field, $name); + $dbh->bz_add_index($field, $new_name, $new_def); } + } } sub _initialize_workflow_for_upgrade { - my $old_params = shift; - my $dbh = Bugzilla->dbh; - - $dbh->bz_add_column('bug_status', 'is_open', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - - # Till now, bug statuses were not customizable. Nevertheless, local - # changes are possible and so we will try to respect these changes. - # This means: get the status of bugs having a resolution different from '' - # and mark these statuses as 'closed', even if some of these statuses are - # expected to be open statuses. Bug statuses we have no information about - # are left as 'open'. - # - # We append the default list of closed statuses *unless* we detect at least - # one closed state in the DB (i.e. with is_open = 0). This would mean that - # the DB has already been updated at least once and maybe the admin decided - # that e.g. 'RESOLVED' is now an open state, in which case we don't want to - # override this attribute. At least one bug status has to be a closed state - # anyway (due to the 'duplicate_or_move_bug_status' parameter) so it's safe - # to use this criteria. - my $num_closed_states = $dbh->selectrow_array('SELECT COUNT(*) FROM bug_status - WHERE is_open = 0'); - - if (!$num_closed_states) { - my @closed_statuses = - @{$dbh->selectcol_arrayref('SELECT DISTINCT bug_status FROM bugs - WHERE resolution != ?', undef, '')}; - @closed_statuses = - map {$dbh->quote($_)} (@closed_statuses, qw(RESOLVED VERIFIED CLOSED)); - - print "Marking closed bug statuses as such...\n"; - $dbh->do('UPDATE bug_status SET is_open = 0 WHERE value IN (' . - join(', ', @closed_statuses) . ')'); - } - - # We only populate the workflow here if we're upgrading from a version - # before 4.0 (which is where init_workflow was added). This was the - # first schema change done for 4.0, so we check this. - return if $dbh->bz_column_info('bugs_activity', 'comment_id'); - - # Populate the status_workflow table. We do nothing if the table already - # has entries. If all bug status transitions have been deleted, the - # workflow will be restored to its default schema. - my $count = $dbh->selectrow_array('SELECT COUNT(*) FROM status_workflow'); - - if (!$count) { - # Make sure the variables below are defined as - # status_workflow.require_comment cannot be NULL. - my $create = $old_params->{'commentoncreate'} || 0; - my $confirm = $old_params->{'commentonconfirm'} || 0; - my $accept = $old_params->{'commentonaccept'} || 0; - my $resolve = $old_params->{'commentonresolve'} || 0; - my $verify = $old_params->{'commentonverify'} || 0; - my $close = $old_params->{'commentonclose'} || 0; - my $reopen = $old_params->{'commentonreopen'} || 0; - # This was till recently the only way to get back to NEW for - # confirmed bugs, so we use this parameter here. - my $reassign = $old_params->{'commentonreassign'} || 0; - - # This is the default workflow for upgrading installations. - my @workflow = ([undef, 'UNCONFIRMED', $create], - [undef, 'NEW', $create], - [undef, 'ASSIGNED', $create], - ['UNCONFIRMED', 'NEW', $confirm], - ['UNCONFIRMED', 'ASSIGNED', $accept], - ['UNCONFIRMED', 'RESOLVED', $resolve], - ['NEW', 'ASSIGNED', $accept], - ['NEW', 'RESOLVED', $resolve], - ['ASSIGNED', 'NEW', $reassign], - ['ASSIGNED', 'RESOLVED', $resolve], - ['REOPENED', 'NEW', $reassign], - ['REOPENED', 'ASSIGNED', $accept], - ['REOPENED', 'RESOLVED', $resolve], - ['RESOLVED', 'UNCONFIRMED', $reopen], - ['RESOLVED', 'REOPENED', $reopen], - ['RESOLVED', 'VERIFIED', $verify], - ['RESOLVED', 'CLOSED', $close], - ['VERIFIED', 'UNCONFIRMED', $reopen], - ['VERIFIED', 'REOPENED', $reopen], - ['VERIFIED', 'CLOSED', $close], - ['CLOSED', 'UNCONFIRMED', $reopen], - ['CLOSED', 'REOPENED', $reopen]); - - print "Now filling the 'status_workflow' table with valid bug status transitions...\n"; - my $sth_select = $dbh->prepare('SELECT id FROM bug_status WHERE value = ?'); - my $sth = $dbh->prepare('INSERT INTO status_workflow (old_status, new_status, - require_comment) VALUES (?, ?, ?)'); - - foreach my $transition (@workflow) { - my ($from, $to); - # If it's an initial state, there is no "old" value. - $from = $dbh->selectrow_array($sth_select, undef, $transition->[0]) - if $transition->[0]; - $to = $dbh->selectrow_array($sth_select, undef, $transition->[1]); - # If one of the bug statuses doesn't exist, the transition is invalid. - next if (($transition->[0] && !$from) || !$to); - - $sth->execute($from, $to, $transition->[2] ? 1 : 0); - } - } + my $old_params = shift; + my $dbh = Bugzilla->dbh; + + $dbh->bz_add_column('bug_status', 'is_open', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + + # Till now, bug statuses were not customizable. Nevertheless, local + # changes are possible and so we will try to respect these changes. + # This means: get the status of bugs having a resolution different from '' + # and mark these statuses as 'closed', even if some of these statuses are + # expected to be open statuses. Bug statuses we have no information about + # are left as 'open'. + # + # We append the default list of closed statuses *unless* we detect at least + # one closed state in the DB (i.e. with is_open = 0). This would mean that + # the DB has already been updated at least once and maybe the admin decided + # that e.g. 'RESOLVED' is now an open state, in which case we don't want to + # override this attribute. At least one bug status has to be a closed state + # anyway (due to the 'duplicate_or_move_bug_status' parameter) so it's safe + # to use this criteria. + my $num_closed_states = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM bug_status + WHERE is_open = 0' + ); + + if (!$num_closed_states) { + my @closed_statuses = @{ + $dbh->selectcol_arrayref( + 'SELECT DISTINCT bug_status FROM bugs + WHERE resolution != ?', undef, '' + ) + }; + @closed_statuses + = map { $dbh->quote($_) } (@closed_statuses, qw(RESOLVED VERIFIED CLOSED)); + + print "Marking closed bug statuses as such...\n"; + $dbh->do('UPDATE bug_status SET is_open = 0 WHERE value IN (' + . join(', ', @closed_statuses) + . ')'); + } + + # We only populate the workflow here if we're upgrading from a version + # before 4.0 (which is where init_workflow was added). This was the + # first schema change done for 4.0, so we check this. + return if $dbh->bz_column_info('bugs_activity', 'comment_id'); + + # Populate the status_workflow table. We do nothing if the table already + # has entries. If all bug status transitions have been deleted, the + # workflow will be restored to its default schema. + my $count = $dbh->selectrow_array('SELECT COUNT(*) FROM status_workflow'); + + if (!$count) { + + # Make sure the variables below are defined as + # status_workflow.require_comment cannot be NULL. + my $create = $old_params->{'commentoncreate'} || 0; + my $confirm = $old_params->{'commentonconfirm'} || 0; + my $accept = $old_params->{'commentonaccept'} || 0; + my $resolve = $old_params->{'commentonresolve'} || 0; + my $verify = $old_params->{'commentonverify'} || 0; + my $close = $old_params->{'commentonclose'} || 0; + my $reopen = $old_params->{'commentonreopen'} || 0; + + # This was till recently the only way to get back to NEW for + # confirmed bugs, so we use this parameter here. + my $reassign = $old_params->{'commentonreassign'} || 0; + + # This is the default workflow for upgrading installations. + my @workflow = ( + [undef, 'UNCONFIRMED', $create], + [undef, 'NEW', $create], + [undef, 'ASSIGNED', $create], + ['UNCONFIRMED', 'NEW', $confirm], + ['UNCONFIRMED', 'ASSIGNED', $accept], + ['UNCONFIRMED', 'RESOLVED', $resolve], + ['NEW', 'ASSIGNED', $accept], + ['NEW', 'RESOLVED', $resolve], + ['ASSIGNED', 'NEW', $reassign], + ['ASSIGNED', 'RESOLVED', $resolve], + ['REOPENED', 'NEW', $reassign], + ['REOPENED', 'ASSIGNED', $accept], + ['REOPENED', 'RESOLVED', $resolve], + ['RESOLVED', 'UNCONFIRMED', $reopen], + ['RESOLVED', 'REOPENED', $reopen], + ['RESOLVED', 'VERIFIED', $verify], + ['RESOLVED', 'CLOSED', $close], + ['VERIFIED', 'UNCONFIRMED', $reopen], + ['VERIFIED', 'REOPENED', $reopen], + ['VERIFIED', 'CLOSED', $close], + ['CLOSED', 'UNCONFIRMED', $reopen], + ['CLOSED', 'REOPENED', $reopen] + ); - # Make sure the bug status used by the 'duplicate_or_move_bug_status' - # parameter has all the required transitions set. - my $dup_status = Bugzilla->params->{'duplicate_or_move_bug_status'}; - my $status_id = $dbh->selectrow_array( - 'SELECT id FROM bug_status WHERE value = ?', undef, $dup_status); - # There's a minor chance that this status isn't in the DB. - $status_id || return; + print + "Now filling the 'status_workflow' table with valid bug status transitions...\n"; + my $sth_select = $dbh->prepare('SELECT id FROM bug_status WHERE value = ?'); + my $sth = $dbh->prepare( + 'INSERT INTO status_workflow (old_status, new_status, + require_comment) VALUES (?, ?, ?)' + ); - my $missing_statuses = $dbh->selectcol_arrayref( - 'SELECT id FROM bug_status - LEFT JOIN status_workflow ON old_status = id - AND new_status = ? - WHERE old_status IS NULL', undef, $status_id); + foreach my $transition (@workflow) { + my ($from, $to); - my $sth = $dbh->prepare('INSERT INTO status_workflow - (old_status, new_status) VALUES (?, ?)'); + # If it's an initial state, there is no "old" value. + $from = $dbh->selectrow_array($sth_select, undef, $transition->[0]) + if $transition->[0]; + $to = $dbh->selectrow_array($sth_select, undef, $transition->[1]); - foreach my $old_status_id (@$missing_statuses) { - next if ($old_status_id == $status_id); - $sth->execute($old_status_id, $status_id); + # If one of the bug statuses doesn't exist, the transition is invalid. + next if (($transition->[0] && !$from) || !$to); + + $sth->execute($from, $to, $transition->[2] ? 1 : 0); } + } + + # Make sure the bug status used by the 'duplicate_or_move_bug_status' + # parameter has all the required transitions set. + my $dup_status = Bugzilla->params->{'duplicate_or_move_bug_status'}; + my $status_id + = $dbh->selectrow_array('SELECT id FROM bug_status WHERE value = ?', + undef, $dup_status); + + # There's a minor chance that this status isn't in the DB. + $status_id || return; + + my $missing_statuses = $dbh->selectcol_arrayref( + 'SELECT id FROM bug_status + LEFT JOIN status_workflow ON old_status = id + AND new_status = ? + WHERE old_status IS NULL', undef, $status_id + ); + + my $sth = $dbh->prepare( + 'INSERT INTO status_workflow + (old_status, new_status) VALUES (?, ?)' + ); + + foreach my $old_status_id (@$missing_statuses) { + next if ($old_status_id == $status_id); + $sth->execute($old_status_id, $status_id); + } } sub _make_lang_setting_dynamic { - my $dbh = Bugzilla->dbh; - my $count = $dbh->selectrow_array(q{SELECT 1 FROM setting + my $dbh = Bugzilla->dbh; + my $count = $dbh->selectrow_array( + q{SELECT 1 FROM setting WHERE name = 'lang' - AND subclass IS NULL}); - if ($count) { - $dbh->do(q{UPDATE setting SET subclass = 'Lang' WHERE name = 'lang'}); - $dbh->do(q{DELETE FROM setting_value WHERE name = 'lang'}); - } + AND subclass IS NULL} + ); + if ($count) { + $dbh->do(q{UPDATE setting SET subclass = 'Lang' WHERE name = 'lang'}); + $dbh->do(q{DELETE FROM setting_value WHERE name = 'lang'}); + } } sub _fix_attachment_modification_date { - my $dbh = Bugzilla->dbh; - if (!$dbh->bz_column_info('attachments', 'modification_time')) { - # Allow NULL values till the modification time has been set. - $dbh->bz_add_column('attachments', 'modification_time', {TYPE => 'DATETIME'}); + my $dbh = Bugzilla->dbh; + if (!$dbh->bz_column_info('attachments', 'modification_time')) { - print "Setting the modification time for attachments...\n"; - $dbh->do('UPDATE attachments SET modification_time = creation_ts'); + # Allow NULL values till the modification time has been set. + $dbh->bz_add_column('attachments', 'modification_time', {TYPE => 'DATETIME'}); - # Now force values to be always defined. - $dbh->bz_alter_column('attachments', 'modification_time', - {TYPE => 'DATETIME', NOTNULL => 1}); + print "Setting the modification time for attachments...\n"; + $dbh->do('UPDATE attachments SET modification_time = creation_ts'); - # Update the modification time for attachments which have been modified. - my $attachments = - $dbh->selectall_arrayref('SELECT attach_id, MAX(bug_when) FROM bugs_activity - WHERE attach_id IS NOT NULL ' . - $dbh->sql_group_by('attach_id')); + # Now force values to be always defined. + $dbh->bz_alter_column('attachments', 'modification_time', + {TYPE => 'DATETIME', NOTNULL => 1}); - my $sth = $dbh->prepare('UPDATE attachments SET modification_time = ? - WHERE attach_id = ?'); - $sth->execute($_->[1], $_->[0]) foreach (@$attachments); - } - # We add this here to be sure to have the index being added, due to the original - # patch omitting it. - $dbh->bz_add_index('attachments', 'attachments_modification_time_idx', - [qw(modification_time)]); + # Update the modification time for attachments which have been modified. + my $attachments = $dbh->selectall_arrayref( + 'SELECT attach_id, MAX(bug_when) FROM bugs_activity + WHERE attach_id IS NOT NULL ' + . $dbh->sql_group_by('attach_id') + ); + + my $sth = $dbh->prepare( + 'UPDATE attachments SET modification_time = ? + WHERE attach_id = ?' + ); + $sth->execute($_->[1], $_->[0]) foreach (@$attachments); + } + + # We add this here to be sure to have the index being added, due to the original + # patch omitting it. + $dbh->bz_add_index('attachments', 'attachments_modification_time_idx', + [qw(modification_time)]); } sub _change_text_types { - my $dbh = Bugzilla->dbh; - return if - $dbh->bz_column_info('namedqueries', 'query')->{TYPE} eq 'LONGTEXT'; - _check_content_length('attachments', 'mimetype', 255, 'attach_id'); - _check_content_length('fielddefs', 'description', 255, 'id'); - _check_content_length('attachments', 'description', 255, 'attach_id'); - - $dbh->bz_alter_column('bugs', 'bug_file_loc', - { TYPE => 'MEDIUMTEXT'}); - $dbh->bz_alter_column('longdescs', 'thetext', - { TYPE => 'LONGTEXT', NOTNULL => 1 }); - $dbh->bz_alter_column('attachments', 'description', - { TYPE => 'TINYTEXT', NOTNULL => 1 }); - $dbh->bz_alter_column('attachments', 'mimetype', - { TYPE => 'TINYTEXT', NOTNULL => 1 }); - # This also changes NULL to NOT NULL. - $dbh->bz_alter_column('flagtypes', 'description', - { TYPE => 'MEDIUMTEXT', NOTNULL => 1 }, ''); - $dbh->bz_alter_column('fielddefs', 'description', - { TYPE => 'TINYTEXT', NOTNULL => 1 }); - $dbh->bz_alter_column('groups', 'description', - { TYPE => 'MEDIUMTEXT', NOTNULL => 1 }); - $dbh->bz_alter_column('quips', 'quip', - { TYPE => 'MEDIUMTEXT', NOTNULL => 1 }); - $dbh->bz_alter_column('namedqueries', 'query', - { TYPE => 'LONGTEXT', NOTNULL => 1 }); + my $dbh = Bugzilla->dbh; + return if $dbh->bz_column_info('namedqueries', 'query')->{TYPE} eq 'LONGTEXT'; + _check_content_length('attachments', 'mimetype', 255, 'attach_id'); + _check_content_length('fielddefs', 'description', 255, 'id'); + _check_content_length('attachments', 'description', 255, 'attach_id'); + + $dbh->bz_alter_column('bugs', 'bug_file_loc', {TYPE => 'MEDIUMTEXT'}); + $dbh->bz_alter_column('longdescs', 'thetext', + {TYPE => 'LONGTEXT', NOTNULL => 1}); + $dbh->bz_alter_column('attachments', 'description', + {TYPE => 'TINYTEXT', NOTNULL => 1}); + $dbh->bz_alter_column('attachments', 'mimetype', + {TYPE => 'TINYTEXT', NOTNULL => 1}); + + # This also changes NULL to NOT NULL. + $dbh->bz_alter_column('flagtypes', 'description', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); + $dbh->bz_alter_column('fielddefs', 'description', + {TYPE => 'TINYTEXT', NOTNULL => 1}); + $dbh->bz_alter_column('groups', 'description', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1}); + $dbh->bz_alter_column('quips', 'quip', {TYPE => 'MEDIUMTEXT', NOTNULL => 1}); + $dbh->bz_alter_column('namedqueries', 'query', + {TYPE => 'LONGTEXT', NOTNULL => 1}); } sub _check_content_length { - my ($table_name, $field_name, $max_length, $id_field) = @_; - my $dbh = Bugzilla->dbh; - my %contents = @{ $dbh->selectcol_arrayref( - "SELECT $id_field, $field_name FROM $table_name - WHERE CHAR_LENGTH($field_name) > ?", {Columns=>[1,2]}, $max_length) }; - - if (scalar keys %contents) { - my $error = install_string('install_data_too_long', - { column => $field_name, - id_column => $id_field, - table => $table_name, - max_length => $max_length }); - foreach my $id (keys %contents) { - my $string = $contents{$id}; - # Don't dump the whole string--it could be 16MB. - if (length($string) > 80) { - $string = substr($string, 0, 30) . "..." - . substr($string, -30) . "\n"; - } - $error .= "$id: $string\n"; - } - die $error; + my ($table_name, $field_name, $max_length, $id_field) = @_; + my $dbh = Bugzilla->dbh; + my %contents = @{ + $dbh->selectcol_arrayref( + "SELECT $id_field, $field_name FROM $table_name + WHERE CHAR_LENGTH($field_name) > ?", {Columns => [1, 2]}, $max_length + ) + }; + + if (scalar keys %contents) { + my $error = install_string( + 'install_data_too_long', + { + column => $field_name, + id_column => $id_field, + table => $table_name, + max_length => $max_length + } + ); + foreach my $id (keys %contents) { + my $string = $contents{$id}; + + # Don't dump the whole string--it could be 16MB. + if (length($string) > 80) { + $string = substr($string, 0, 30) . "..." . substr($string, -30) . "\n"; + } + $error .= "$id: $string\n"; } + die $error; + } } sub _add_foreign_keys_to_multiselects { - my $dbh = Bugzilla->dbh; + my $dbh = Bugzilla->dbh; - my $names = $dbh->selectcol_arrayref( - 'SELECT name + my $names = $dbh->selectcol_arrayref( + 'SELECT name FROM fielddefs - WHERE type = ' . FIELD_TYPE_MULTI_SELECT); + WHERE type = ' . FIELD_TYPE_MULTI_SELECT + ); - foreach my $name (@$names) { - $dbh->bz_add_fk("bug_$name", "bug_id", - {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}); + foreach my $name (@$names) { + $dbh->bz_add_fk("bug_$name", "bug_id", + {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}); - $dbh->bz_add_fk("bug_$name", "value", - {TABLE => $name, COLUMN => 'value', DELETE => 'RESTRICT'}); - } + $dbh->bz_add_fk("bug_$name", "value", + {TABLE => $name, COLUMN => 'value', DELETE => 'RESTRICT'}); + } } # This subroutine is used in multiple places (for times when we update @@ -3297,593 +3531,630 @@ sub _add_foreign_keys_to_multiselects { # it to update bugs_fulltext for those bug_ids instead of populating the # whole table. sub _populate_bugs_fulltext { - my $bug_ids = shift; - my $dbh = Bugzilla->dbh; - my $fulltext = $dbh->selectrow_array('SELECT 1 FROM bugs_fulltext ' - . $dbh->sql_limit(1)); - # We only populate the table if it's empty or if we've been given a - # set of bug ids. - if ($bug_ids or !$fulltext) { - $bug_ids ||= $dbh->selectcol_arrayref('SELECT bug_id FROM bugs'); - # If there are no bugs in the bugs table, there's nothing to populate. - return if !@$bug_ids; - my $num_bugs = scalar @$bug_ids; - - my $command = "INSERT"; - my $where = ""; - if ($fulltext) { - print "Updating bugs_fulltext for $num_bugs bugs...\n"; - $where = "WHERE " . $dbh->sql_in('bugs.bug_id', $bug_ids); - # It turns out that doing a REPLACE INTO is up to 10x faster - # than any other possible method of updating the table, in MySQL, - # which matters a LOT for large installations. - if ($dbh->isa('Bugzilla::DB::Mysql')) { - $command = "REPLACE"; - } - else { - $dbh->do("DELETE FROM bugs_fulltext WHERE " - . $dbh->sql_in('bug_id', $bug_ids)); - } - } - else { - print "Populating bugs_fulltext with $num_bugs entries..."; - print " (this can take a long time.)\n"; - } + my $bug_ids = shift; + my $dbh = Bugzilla->dbh; + my $fulltext + = $dbh->selectrow_array('SELECT 1 FROM bugs_fulltext ' . $dbh->sql_limit(1)); + + # We only populate the table if it's empty or if we've been given a + # set of bug ids. + if ($bug_ids or !$fulltext) { + $bug_ids ||= $dbh->selectcol_arrayref('SELECT bug_id FROM bugs'); + + # If there are no bugs in the bugs table, there's nothing to populate. + return if !@$bug_ids; + my $num_bugs = scalar @$bug_ids; + + my $command = "INSERT"; + my $where = ""; + if ($fulltext) { + print "Updating bugs_fulltext for $num_bugs bugs...\n"; + $where = "WHERE " . $dbh->sql_in('bugs.bug_id', $bug_ids); + + # It turns out that doing a REPLACE INTO is up to 10x faster + # than any other possible method of updating the table, in MySQL, + # which matters a LOT for large installations. + if ($dbh->isa('Bugzilla::DB::Mysql')) { + $command = "REPLACE"; + } + else { + $dbh->do("DELETE FROM bugs_fulltext WHERE " . $dbh->sql_in('bug_id', $bug_ids)); + } + } + else { + print "Populating bugs_fulltext with $num_bugs entries..."; + print " (this can take a long time.)\n"; + } - # As recommended by Monty Widenius for GNOME's upgrade. - # mkanat and justdave concur it'll be helpful for bmo, too. - $dbh->do('SET SESSION myisam_sort_buffer_size = 3221225472'); + # As recommended by Monty Widenius for GNOME's upgrade. + # mkanat and justdave concur it'll be helpful for bmo, too. + $dbh->do('SET SESSION myisam_sort_buffer_size = 3221225472'); - my $newline = $dbh->quote("\n"); - $dbh->do( - qq{$command INTO bugs_fulltext (bug_id, short_desc, comments, + my $newline = $dbh->quote("\n"); + $dbh->do( + qq{$command INTO bugs_fulltext (bug_id, short_desc, comments, comments_noprivate) SELECT bugs.bug_id, bugs.short_desc, } - . $dbh->sql_group_concat('longdescs.thetext', $newline, 0) - . ', ' . $dbh->sql_group_concat('nopriv.thetext', $newline, 0) . - qq{ FROM bugs + . $dbh->sql_group_concat('longdescs.thetext', $newline, 0) . ', ' + . $dbh->sql_group_concat('nopriv.thetext', $newline, 0) + . qq{ FROM bugs LEFT JOIN longdescs ON bugs.bug_id = longdescs.bug_id LEFT JOIN longdescs AS nopriv ON longdescs.comment_id = nopriv.comment_id AND nopriv.isprivate = 0 $where } - . $dbh->sql_group_by('bugs.bug_id', 'bugs.short_desc')); - } + . $dbh->sql_group_by('bugs.bug_id', 'bugs.short_desc') + ); + } } sub _fix_illegal_flag_modification_dates { - my $dbh = Bugzilla->dbh; + my $dbh = Bugzilla->dbh; - my $rows = $dbh->do('UPDATE flags SET modification_date = creation_date - WHERE modification_date < creation_date'); - # If no rows are affected, $dbh->do returns 0E0 instead of 0. - print "$rows flags had an illegal modification date. Fixed!\n" if ($rows =~ /^\d+$/); + my $rows = $dbh->do( + 'UPDATE flags SET modification_date = creation_date + WHERE modification_date < creation_date' + ); + + # If no rows are affected, $dbh->do returns 0E0 instead of 0. + print "$rows flags had an illegal modification date. Fixed!\n" + if ($rows =~ /^\d+$/); } sub _add_visiblity_value_to_value_tables { - my $dbh = Bugzilla->dbh; - my @standard_fields = - qw(bug_status resolution priority bug_severity op_sys rep_platform); - my $custom_fields = $dbh->selectcol_arrayref( - 'SELECT name FROM fielddefs WHERE custom = 1 AND type IN(?,?)', - undef, FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT); - foreach my $field (@standard_fields, @$custom_fields) { - $dbh->bz_add_column($field, 'visibility_value_id', {TYPE => 'INT2'}); - $dbh->bz_add_index($field, "${field}_visibility_value_id_idx", - ['visibility_value_id']); - } + my $dbh = Bugzilla->dbh; + my @standard_fields + = qw(bug_status resolution priority bug_severity op_sys rep_platform); + my $custom_fields + = $dbh->selectcol_arrayref( + 'SELECT name FROM fielddefs WHERE custom = 1 AND type IN(?,?)', + undef, FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT); + foreach my $field (@standard_fields, @$custom_fields) { + $dbh->bz_add_column($field, 'visibility_value_id', {TYPE => 'INT2'}); + $dbh->bz_add_index($field, "${field}_visibility_value_id_idx", + ['visibility_value_id']); + } } sub _add_extern_id_index { - my $dbh = Bugzilla->dbh; - if (!$dbh->bz_index_info('profiles', 'profiles_extern_id_idx')) { - # Some Bugzillas have a multiple empty strings in extern_id, - # which need to be converted to NULLs before we add the index. - $dbh->do("UPDATE profiles SET extern_id = NULL WHERE extern_id = ''"); - $dbh->bz_add_index('profiles', 'profiles_extern_id_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(extern_id)]}); - } + my $dbh = Bugzilla->dbh; + if (!$dbh->bz_index_info('profiles', 'profiles_extern_id_idx')) { + + # Some Bugzillas have a multiple empty strings in extern_id, + # which need to be converted to NULLs before we add the index. + $dbh->do("UPDATE profiles SET extern_id = NULL WHERE extern_id = ''"); + $dbh->bz_add_index('profiles', 'profiles_extern_id_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(extern_id)]}); + } } sub _convert_disallownew_to_isactive { - my $dbh = Bugzilla->dbh; - if ($dbh->bz_column_info('products', 'disallownew')){ - $dbh->bz_add_column('products', 'isactive', - { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + my $dbh = Bugzilla->dbh; + if ($dbh->bz_column_info('products', 'disallownew')) { + $dbh->bz_add_column('products', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - # isactive is the boolean reverse of disallownew. - $dbh->do('UPDATE products SET isactive = 0 WHERE disallownew = 1'); - $dbh->do('UPDATE products SET isactive = 1 WHERE disallownew = 0'); + # isactive is the boolean reverse of disallownew. + $dbh->do('UPDATE products SET isactive = 0 WHERE disallownew = 1'); + $dbh->do('UPDATE products SET isactive = 1 WHERE disallownew = 0'); - $dbh->bz_drop_column('products','disallownew'); - } + $dbh->bz_drop_column('products', 'disallownew'); + } } sub _fix_logincookies_ipaddr { - my $dbh = Bugzilla->dbh; - return if !$dbh->bz_column_info('logincookies', 'ipaddr')->{NOTNULL}; + my $dbh = Bugzilla->dbh; + return if !$dbh->bz_column_info('logincookies', 'ipaddr')->{NOTNULL}; - $dbh->bz_alter_column('logincookies', 'ipaddr', {TYPE => 'varchar(40)'}); - $dbh->do('UPDATE logincookies SET ipaddr = NULL WHERE ipaddr = ?', - undef, '0.0.0.0'); + $dbh->bz_alter_column('logincookies', 'ipaddr', {TYPE => 'varchar(40)'}); + $dbh->do('UPDATE logincookies SET ipaddr = NULL WHERE ipaddr = ?', + undef, '0.0.0.0'); } sub _fix_invalid_custom_field_names { - my $fields = Bugzilla->fields({ custom => 1 }); + my $fields = Bugzilla->fields({custom => 1}); - foreach my $field (@$fields) { - next if $field->name =~ /^[a-zA-Z0-9_]+$/; - # The field name is illegal and can break the DB. Kill the field! - $field->set_obsolete(1); - print install_string('update_cf_invalid_name', - { field => $field->name }), "\n"; - eval { $field->remove_from_db(); }; - warn $@ if $@; - } + foreach my $field (@$fields) { + next if $field->name =~ /^[a-zA-Z0-9_]+$/; + + # The field name is illegal and can break the DB. Kill the field! + $field->set_obsolete(1); + print install_string('update_cf_invalid_name', {field => $field->name}), "\n"; + eval { $field->remove_from_db(); }; + warn $@ if $@; + } } sub _set_attachment_comment_type { - my ($type, $string) = @_; - my $dbh = Bugzilla->dbh; - # We check if there are any comments of this type already, first, - # because this is faster than a full LIKE search on the comments, - # and currently this will run every time we run checksetup. - my $test = $dbh->selectrow_array( - "SELECT 1 FROM longdescs WHERE type = $type " . $dbh->sql_limit(1)); - return [] if $test; - my %comments = @{ $dbh->selectcol_arrayref( - "SELECT comment_id, thetext FROM longdescs - WHERE thetext LIKE '$string%'", - {Columns=>[1,2]}) }; - my @comment_ids = keys %comments; - return [] if !scalar @comment_ids; - my $what = "update"; + my ($type, $string) = @_; + my $dbh = Bugzilla->dbh; + + # We check if there are any comments of this type already, first, + # because this is faster than a full LIKE search on the comments, + # and currently this will run every time we run checksetup. + my $test = $dbh->selectrow_array( + "SELECT 1 FROM longdescs WHERE type = $type " . $dbh->sql_limit(1)); + return [] if $test; + my %comments = @{ + $dbh->selectcol_arrayref( + "SELECT comment_id, thetext FROM longdescs + WHERE thetext LIKE '$string%'", {Columns => [1, 2]} + ) + }; + my @comment_ids = keys %comments; + return [] if !scalar @comment_ids; + my $what = "update"; + if ($type == CMT_ATTACHMENT_CREATED) { + $what = "creation"; + } + print "Setting the type field on attachment $what comments...\n"; + my $sth = $dbh->prepare( + 'UPDATE longdescs SET thetext = ?, type = ?, extra_data = ? + WHERE comment_id = ?' + ); + my $count = 0; + my $total = scalar @comment_ids; + foreach my $id (@comment_ids) { + $count++; + my $text = $comments{$id}; + next if $text !~ /^\Q$string\E(\d+)/; + my $attachment_id = $1; + my @lines = split("\n", $text); if ($type == CMT_ATTACHMENT_CREATED) { - $what = "creation"; + + # Now we have to remove the text up until we find a line that's + # just a single newline, because the old "Created an attachment" + # text included the attachment description underneath it, and in + # Bugzillas before 2.20, that could be wrapped into multiple lines, + # in the database. + while (1) { + my $line = shift @lines; + last if (!defined $line or trim($line) eq ''); + } } - print "Setting the type field on attachment $what comments...\n"; - my $sth = $dbh->prepare( - 'UPDATE longdescs SET thetext = ?, type = ?, extra_data = ? - WHERE comment_id = ?'); - my $count = 0; - my $total = scalar @comment_ids; - foreach my $id (@comment_ids) { - $count++; - my $text = $comments{$id}; - next if $text !~ /^\Q$string\E(\d+)/; - my $attachment_id = $1; - my @lines = split("\n", $text); - if ($type == CMT_ATTACHMENT_CREATED) { - # Now we have to remove the text up until we find a line that's - # just a single newline, because the old "Created an attachment" - # text included the attachment description underneath it, and in - # Bugzillas before 2.20, that could be wrapped into multiple lines, - # in the database. - while (1) { - my $line = shift @lines; - last if (!defined $line or trim($line) eq ''); - } - } - else { - # However, the "From update of attachment" line is always just - # one line--the first line of the comment. - shift @lines; - } - $text = join("\n", @lines); - $sth->execute($text, $type, $attachment_id, $id); - indicate_progress({ total => $total, current => $count, - every => 25 }); + else { + # However, the "From update of attachment" line is always just + # one line--the first line of the comment. + shift @lines; } - return \@comment_ids; + $text = join("\n", @lines); + $sth->execute($text, $type, $attachment_id, $id); + indicate_progress({total => $total, current => $count, every => 25}); + } + return \@comment_ids; } sub _set_attachment_comment_types { - my $dbh = Bugzilla->dbh; - $dbh->bz_start_transaction(); - my $created_ids = _set_attachment_comment_type( - CMT_ATTACHMENT_CREATED, 'Created an attachment (id='); - my $updated_ids = _set_attachment_comment_type( - CMT_ATTACHMENT_UPDATED, '(From update of attachment '); - $dbh->bz_commit_transaction(); - return unless (@$created_ids or @$updated_ids); - - my @comment_ids = (@$created_ids, @$updated_ids); - - my $bug_ids = $dbh->selectcol_arrayref( - 'SELECT DISTINCT bug_id FROM longdescs WHERE ' - . $dbh->sql_in('comment_id', \@comment_ids)); - _populate_bugs_fulltext($bug_ids); + my $dbh = Bugzilla->dbh; + $dbh->bz_start_transaction(); + my $created_ids = _set_attachment_comment_type(CMT_ATTACHMENT_CREATED, + 'Created an attachment (id='); + my $updated_ids = _set_attachment_comment_type(CMT_ATTACHMENT_UPDATED, + '(From update of attachment '); + $dbh->bz_commit_transaction(); + return unless (@$created_ids or @$updated_ids); + + my @comment_ids = (@$created_ids, @$updated_ids); + + my $bug_ids + = $dbh->selectcol_arrayref('SELECT DISTINCT bug_id FROM longdescs WHERE ' + . $dbh->sql_in('comment_id', \@comment_ids)); + _populate_bugs_fulltext($bug_ids); } sub _add_allows_unconfirmed_to_product_table { - my $dbh = Bugzilla->dbh; - if (!$dbh->bz_column_info('products', 'allows_unconfirmed')) { - $dbh->bz_add_column('products', 'allows_unconfirmed', - { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE' }); - if ($dbh->bz_column_info('products', 'votestoconfirm')) { - $dbh->do('UPDATE products SET allows_unconfirmed = 1 - WHERE votestoconfirm > 0'); - } + my $dbh = Bugzilla->dbh; + if (!$dbh->bz_column_info('products', 'allows_unconfirmed')) { + $dbh->bz_add_column('products', 'allows_unconfirmed', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + if ($dbh->bz_column_info('products', 'votestoconfirm')) { + $dbh->do( + 'UPDATE products SET allows_unconfirmed = 1 + WHERE votestoconfirm > 0' + ); } + } } sub _convert_flagtypes_fks_to_set_null { - my $dbh = Bugzilla->dbh; - foreach my $column (qw(request_group_id grant_group_id)) { - my $fk = $dbh->bz_fk_info('flagtypes', $column); - if ($fk and !defined $fk->{DELETE}) { - $fk->{DELETE} = 'SET NULL'; - $dbh->bz_alter_fk('flagtypes', $column, $fk); - } + my $dbh = Bugzilla->dbh; + foreach my $column (qw(request_group_id grant_group_id)) { + my $fk = $dbh->bz_fk_info('flagtypes', $column); + if ($fk and !defined $fk->{DELETE}) { + $fk->{DELETE} = 'SET NULL'; + $dbh->bz_alter_fk('flagtypes', $column, $fk); } + } } sub _fix_decimal_types { - my $dbh = Bugzilla->dbh; - my $type = {TYPE => 'decimal(7,2)', NOTNULL => 1, DEFAULT => '0'}; - $dbh->bz_alter_column('bugs', 'estimated_time', $type); - $dbh->bz_alter_column('bugs', 'remaining_time', $type); - $dbh->bz_alter_column('longdescs', 'work_time', $type); + my $dbh = Bugzilla->dbh; + my $type = {TYPE => 'decimal(7,2)', NOTNULL => 1, DEFAULT => '0'}; + $dbh->bz_alter_column('bugs', 'estimated_time', $type); + $dbh->bz_alter_column('bugs', 'remaining_time', $type); + $dbh->bz_alter_column('longdescs', 'work_time', $type); } sub _fix_series_creator_fk { - my $dbh = Bugzilla->dbh; - my $fk = $dbh->bz_fk_info('series', 'creator'); - if ($fk and $fk->{DELETE} eq 'SET NULL') { - $fk->{DELETE} = 'CASCADE'; - $dbh->bz_alter_fk('series', 'creator', $fk); - } + my $dbh = Bugzilla->dbh; + my $fk = $dbh->bz_fk_info('series', 'creator'); + if ($fk and $fk->{DELETE} eq 'SET NULL') { + $fk->{DELETE} = 'CASCADE'; + $dbh->bz_alter_fk('series', 'creator', $fk); + } } sub _remove_attachment_isurl { - my $dbh = Bugzilla->dbh; + my $dbh = Bugzilla->dbh; - if ($dbh->bz_column_info('attachments', 'isurl')) { - # Now all attachments must have a filename. - $dbh->do('UPDATE attachments SET filename = ? WHERE isurl = 1', - undef, 'url.txt'); - $dbh->bz_drop_column('attachments', 'isurl'); - $dbh->do("DELETE FROM fielddefs WHERE name='attachments.isurl'"); - } + if ($dbh->bz_column_info('attachments', 'isurl')) { + + # Now all attachments must have a filename. + $dbh->do('UPDATE attachments SET filename = ? WHERE isurl = 1', + undef, 'url.txt'); + $dbh->bz_drop_column('attachments', 'isurl'); + $dbh->do("DELETE FROM fielddefs WHERE name='attachments.isurl'"); + } } sub _add_isactive_to_product_fields { - my $dbh = Bugzilla->dbh; - - # If we add the isactive column all values should start off as active - if (!$dbh->bz_column_info('components', 'isactive')) { - $dbh->bz_add_column('components', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - } - - if (!$dbh->bz_column_info('versions', 'isactive')) { - $dbh->bz_add_column('versions', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - } - - if (!$dbh->bz_column_info('milestones', 'isactive')) { - $dbh->bz_add_column('milestones', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - } + my $dbh = Bugzilla->dbh; + + # If we add the isactive column all values should start off as active + if (!$dbh->bz_column_info('components', 'isactive')) { + $dbh->bz_add_column('components', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + } + + if (!$dbh->bz_column_info('versions', 'isactive')) { + $dbh->bz_add_column('versions', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + } + + if (!$dbh->bz_column_info('milestones', 'isactive')) { + $dbh->bz_add_column('milestones', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + } } sub _migrate_field_visibility_value { - my $dbh = Bugzilla->dbh; - - if ($dbh->bz_column_info('fielddefs', 'visibility_value_id')) { - print "Populating new field_visibility table...\n"; + my $dbh = Bugzilla->dbh; - $dbh->bz_start_transaction(); + if ($dbh->bz_column_info('fielddefs', 'visibility_value_id')) { + print "Populating new field_visibility table...\n"; - my %results = - @{ $dbh->selectcol_arrayref( - "SELECT id, visibility_value_id FROM fielddefs - WHERE visibility_value_id IS NOT NULL", - { Columns => [1,2] }) }; + $dbh->bz_start_transaction(); - my $insert_sth = - $dbh->prepare("INSERT INTO field_visibility (field_id, value_id) - VALUES (?, ?)"); + my %results = @{ + $dbh->selectcol_arrayref( + "SELECT id, visibility_value_id FROM fielddefs + WHERE visibility_value_id IS NOT NULL", {Columns => [1, 2]} + ) + }; - foreach my $id (keys %results) { - $insert_sth->execute($id, $results{$id}); - } + my $insert_sth = $dbh->prepare( + "INSERT INTO field_visibility (field_id, value_id) + VALUES (?, ?)" + ); - $dbh->bz_commit_transaction(); - $dbh->bz_drop_column('fielddefs', 'visibility_value_id'); + foreach my $id (keys %results) { + $insert_sth->execute($id, $results{$id}); } + + $dbh->bz_commit_transaction(); + $dbh->bz_drop_column('fielddefs', 'visibility_value_id'); + } } sub _fix_series_indexes { - my $dbh = Bugzilla->dbh; - return if $dbh->bz_index_info('series', 'series_category_idx'); + my $dbh = Bugzilla->dbh; + return if $dbh->bz_index_info('series', 'series_category_idx'); - $dbh->bz_drop_index('series', 'series_creator_idx'); + $dbh->bz_drop_index('series', 'series_creator_idx'); - # Fix duplicated names under the same category/subcategory before - # adding the more restrictive index. - my $duplicated_series = $dbh->selectall_arrayref( - 'SELECT s1.series_id, s1.category, s1.subcategory, s1.name + # Fix duplicated names under the same category/subcategory before + # adding the more restrictive index. + my $duplicated_series = $dbh->selectall_arrayref( + 'SELECT s1.series_id, s1.category, s1.subcategory, s1.name FROM series AS s1 INNER JOIN series AS s2 ON s1.category = s2.category AND s1.subcategory = s2.subcategory AND s1.name = s2.name - WHERE s1.series_id != s2.series_id'); - my $sth_series_update = $dbh->prepare('UPDATE series SET name = ? WHERE series_id = ?'); - my $sth_series_query = $dbh->prepare('SELECT 1 FROM series WHERE name = ? - AND category = ? AND subcategory = ?'); - - my %renamed_series; - foreach my $series (@$duplicated_series) { - my ($series_id, $category, $subcategory, $name) = @$series; - # Leave the first series alone, then rename duplicated ones. - if ($renamed_series{"${category}_${subcategory}_${name}"}++) { - print "Renaming series ${category}/${subcategory}/${name}...\n"; - my $c = 0; - my $exists = 1; - while ($exists) { - $sth_series_query->execute($name . ++$c, $category, $subcategory); - $exists = $sth_series_query->fetchrow_array; - } - $sth_series_update->execute($name . $c, $series_id); - } + WHERE s1.series_id != s2.series_id' + ); + my $sth_series_update + = $dbh->prepare('UPDATE series SET name = ? WHERE series_id = ?'); + my $sth_series_query = $dbh->prepare( + 'SELECT 1 FROM series WHERE name = ? + AND category = ? AND subcategory = ?' + ); + + my %renamed_series; + foreach my $series (@$duplicated_series) { + my ($series_id, $category, $subcategory, $name) = @$series; + + # Leave the first series alone, then rename duplicated ones. + if ($renamed_series{"${category}_${subcategory}_${name}"}++) { + print "Renaming series ${category}/${subcategory}/${name}...\n"; + my $c = 0; + my $exists = 1; + while ($exists) { + $sth_series_query->execute($name . ++$c, $category, $subcategory); + $exists = $sth_series_query->fetchrow_array; + } + $sth_series_update->execute($name . $c, $series_id); } + } - $dbh->bz_add_index('series', 'series_creator_idx', ['creator']); - $dbh->bz_add_index('series', 'series_category_idx', - {FIELDS => [qw(category subcategory name)], TYPE => 'UNIQUE'}); + $dbh->bz_add_index('series', 'series_creator_idx', ['creator']); + $dbh->bz_add_index('series', 'series_category_idx', + {FIELDS => [qw(category subcategory name)], TYPE => 'UNIQUE'}); } sub _migrate_user_tags { - my $dbh = Bugzilla->dbh; - return unless $dbh->bz_column_info('namedqueries', 'query_type'); + my $dbh = Bugzilla->dbh; + return unless $dbh->bz_column_info('namedqueries', 'query_type'); - my $tags = $dbh->selectall_arrayref('SELECT id, userid, name, query + my $tags = $dbh->selectall_arrayref( + 'SELECT id, userid, name, query FROM namedqueries - WHERE query_type != 0'); - - my $sth_tags = $dbh->prepare( - 'INSERT INTO tag (user_id, name) VALUES (?, ?)'); - my $sth_tag_id = $dbh->prepare( - 'SELECT id FROM tag WHERE user_id = ? AND name = ?'); - my $sth_bug_tag = $dbh->prepare('INSERT INTO bug_tag (bug_id, tag_id) - VALUES (?, ?)'); - my $sth_nq = $dbh->prepare('UPDATE namedqueries SET query = ? - WHERE id = ?'); - - if (scalar @$tags) { - print install_string('update_queries_to_tags'), "\n"; + WHERE query_type != 0' + ); + + my $sth_tags = $dbh->prepare('INSERT INTO tag (user_id, name) VALUES (?, ?)'); + my $sth_tag_id + = $dbh->prepare('SELECT id FROM tag WHERE user_id = ? AND name = ?'); + my $sth_bug_tag = $dbh->prepare( + 'INSERT INTO bug_tag (bug_id, tag_id) + VALUES (?, ?)' + ); + my $sth_nq = $dbh->prepare( + 'UPDATE namedqueries SET query = ? + WHERE id = ?' + ); + + if (scalar @$tags) { + print install_string('update_queries_to_tags'), "\n"; + } + + my $total = scalar(@$tags); + my $current = 0; + + $dbh->bz_start_transaction(); + foreach my $tag (@$tags) { + my ($query_id, $user_id, $name, $query) = @$tag; + + # Tags are all lowercase. + my $tag_name = lc($name); + + $sth_tags->execute($user_id, $tag_name); + + my $tag_id = $dbh->selectrow_array($sth_tag_id, undef, $user_id, $tag_name); + + indicate_progress({current => ++$current, total => $total, every => 25}); + + my $uri = URI->new("buglist.cgi?$query", 'http'); + my $bug_id_list = $uri->query_param_delete('bug_id'); + if (!$bug_id_list) { + warn "No bug_id param for tag $name from user $user_id: $query"; + next; } + my @bug_ids = split(/[\s,]+/, $bug_id_list); - my $total = scalar(@$tags); - my $current = 0; - - $dbh->bz_start_transaction(); - foreach my $tag (@$tags) { - my ($query_id, $user_id, $name, $query) = @$tag; - # Tags are all lowercase. - my $tag_name = lc($name); - - $sth_tags->execute($user_id, $tag_name); - - my $tag_id = $dbh->selectrow_array($sth_tag_id, - undef, $user_id, $tag_name); - - indicate_progress({ current => ++$current, total => $total, - every => 25 }); + # Make sure that things like "001" get converted to "1" + @bug_ids = map { int($_) } @bug_ids; - my $uri = URI->new("buglist.cgi?$query", 'http'); - my $bug_id_list = $uri->query_param_delete('bug_id'); - if (!$bug_id_list) { - warn "No bug_id param for tag $name from user $user_id: $query"; - next; - } - my @bug_ids = split(/[\s,]+/, $bug_id_list); - # Make sure that things like "001" get converted to "1" - @bug_ids = map { int($_) } @bug_ids; - # And remove duplicates - @bug_ids = uniq @bug_ids; - foreach my $bug_id (@bug_ids) { - # If "int" above failed this might be undef. We also - # don't want to accept bug 0. - next if !$bug_id; - $sth_bug_tag->execute($bug_id, $tag_id); - } + # And remove duplicates + @bug_ids = uniq @bug_ids; + foreach my $bug_id (@bug_ids) { - # Existing tags may be used in whines, or shared with - # other users. So we convert them rather than delete them. - $uri->query_param('tag', $tag_name); - $sth_nq->execute($uri->query, $query_id); + # If "int" above failed this might be undef. We also + # don't want to accept bug 0. + next if !$bug_id; + $sth_bug_tag->execute($bug_id, $tag_id); } - $dbh->bz_commit_transaction(); + # Existing tags may be used in whines, or shared with + # other users. So we convert them rather than delete them. + $uri->query_param('tag', $tag_name); + $sth_nq->execute($uri->query, $query_id); + } - $dbh->bz_drop_column('namedqueries', 'query_type'); + $dbh->bz_commit_transaction(); + + $dbh->bz_drop_column('namedqueries', 'query_type'); } sub _populate_bug_see_also_class { - my $dbh = Bugzilla->dbh; + my $dbh = Bugzilla->dbh; - if ($dbh->bz_column_info('bug_see_also', 'class')) { - # The length was incorrectly set to 64 instead of 255. - $dbh->bz_alter_column('bug_see_also', 'class', - {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}); - return; - } + if ($dbh->bz_column_info('bug_see_also', 'class')) { - $dbh->bz_add_column('bug_see_also', 'class', - {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}, ''); + # The length was incorrectly set to 64 instead of 255. + $dbh->bz_alter_column('bug_see_also', 'class', + {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}); + return; + } - my $result = $dbh->selectall_arrayref( - "SELECT id, value FROM bug_see_also"); + $dbh->bz_add_column('bug_see_also', 'class', + {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}, ''); - my $update_sth = - $dbh->prepare("UPDATE bug_see_also SET class = ? WHERE id = ?"); + my $result = $dbh->selectall_arrayref("SELECT id, value FROM bug_see_also"); - $dbh->bz_start_transaction(); - foreach my $see_also (@$result) { - my ($id, $value) = @$see_also; - my $class = Bugzilla::BugUrl->class_for($value); - $update_sth->execute($class, $id); - } - $dbh->bz_commit_transaction(); + my $update_sth + = $dbh->prepare("UPDATE bug_see_also SET class = ? WHERE id = ?"); + + $dbh->bz_start_transaction(); + foreach my $see_also (@$result) { + my ($id, $value) = @$see_also; + my $class = Bugzilla::BugUrl->class_for($value); + $update_sth->execute($class, $id); + } + $dbh->bz_commit_transaction(); } sub _migrate_disabledtext_boolean { - my $dbh = Bugzilla->dbh; - if (!$dbh->bz_column_info('profiles', 'is_enabled')) { - $dbh->bz_add_column("profiles", 'is_enabled', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - $dbh->do("UPDATE profiles SET is_enabled = 0 - WHERE disabledtext != ''"); - } + my $dbh = Bugzilla->dbh; + if (!$dbh->bz_column_info('profiles', 'is_enabled')) { + $dbh->bz_add_column("profiles", 'is_enabled', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + $dbh->do( + "UPDATE profiles SET is_enabled = 0 + WHERE disabledtext != ''" + ); + } } sub _rename_tags_to_tag { - my $dbh = Bugzilla->dbh; - if ($dbh->bz_table_info('tags')) { - # If we get here, it's because the schema created "tag" as an empty - # table while "tags" still exists. We get rid of the empty - # tag table so we can do the rename over the top of it. - $dbh->bz_drop_table('tag'); - $dbh->bz_drop_index('tags', 'tags_user_id_idx'); - $dbh->bz_rename_table('tags','tag'); - $dbh->bz_add_index('tag', 'tag_user_id_idx', - {FIELDS => [qw(user_id name)], TYPE => 'UNIQUE'}); - } - if (my $bug_tag_fk = $dbh->bz_fk_info('bug_tag', 'tag_id')) { - # bz_rename_table() didn't handle FKs correctly. - if ($bug_tag_fk->{TABLE} eq 'tags') { - $bug_tag_fk->{TABLE} = 'tag'; - $dbh->bz_alter_fk('bug_tag', 'tag_id', $bug_tag_fk); - } + my $dbh = Bugzilla->dbh; + if ($dbh->bz_table_info('tags')) { + + # If we get here, it's because the schema created "tag" as an empty + # table while "tags" still exists. We get rid of the empty + # tag table so we can do the rename over the top of it. + $dbh->bz_drop_table('tag'); + $dbh->bz_drop_index('tags', 'tags_user_id_idx'); + $dbh->bz_rename_table('tags', 'tag'); + $dbh->bz_add_index('tag', 'tag_user_id_idx', + {FIELDS => [qw(user_id name)], TYPE => 'UNIQUE'}); + } + if (my $bug_tag_fk = $dbh->bz_fk_info('bug_tag', 'tag_id')) { + + # bz_rename_table() didn't handle FKs correctly. + if ($bug_tag_fk->{TABLE} eq 'tags') { + $bug_tag_fk->{TABLE} = 'tag'; + $dbh->bz_alter_fk('bug_tag', 'tag_id', $bug_tag_fk); } + } } sub _on_delete_set_null_for_audit_log_userid { - my $dbh = Bugzilla->dbh; - my $fk = $dbh->bz_fk_info('audit_log', 'user_id'); - if ($fk and !defined $fk->{DELETE}) { - $fk->{DELETE} = 'SET NULL'; - $dbh->bz_alter_fk('audit_log', 'user_id', $fk); - } + my $dbh = Bugzilla->dbh; + my $fk = $dbh->bz_fk_info('audit_log', 'user_id'); + if ($fk and !defined $fk->{DELETE}) { + $fk->{DELETE} = 'SET NULL'; + $dbh->bz_alter_fk('audit_log', 'user_id', $fk); + } } sub _fix_notnull_defaults { - my $dbh = Bugzilla->dbh; + my $dbh = Bugzilla->dbh; - $dbh->bz_alter_column('bugs', 'bug_file_loc', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1, - DEFAULT => "''"}, ''); + $dbh->bz_alter_column('bugs', 'bug_file_loc', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}, ''); - my $custom_fields = Bugzilla::Field->match({ - custom => 1, type => [ FIELD_TYPE_FREETEXT, FIELD_TYPE_TEXTAREA ] + my $custom_fields + = Bugzilla::Field->match({ + custom => 1, type => [FIELD_TYPE_FREETEXT, FIELD_TYPE_TEXTAREA] }); - foreach my $field (@$custom_fields) { - if ($field->type == FIELD_TYPE_FREETEXT) { - $dbh->bz_alter_column('bugs', $field->name, - {TYPE => 'varchar(255)', NOTNULL => 1, - DEFAULT => "''"}, ''); - } - if ($field->type == FIELD_TYPE_TEXTAREA) { - $dbh->bz_alter_column('bugs', $field->name, - {TYPE => 'MEDIUMTEXT', NOTNULL => 1, - DEFAULT => "''"}, ''); - } + foreach my $field (@$custom_fields) { + if ($field->type == FIELD_TYPE_FREETEXT) { + $dbh->bz_alter_column('bugs', $field->name, + {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}, ''); + } + if ($field->type == FIELD_TYPE_TEXTAREA) { + $dbh->bz_alter_column('bugs', $field->name, + {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}, ''); } + } } sub _fix_longdescs_primary_key { - my $dbh = Bugzilla->dbh; - if ($dbh->bz_column_info('longdescs', 'comment_id')->{TYPE} ne 'INTSERIAL') { - $dbh->bz_drop_related_fks('longdescs', 'comment_id'); - $dbh->bz_alter_column('bugs_activity', 'comment_id', {TYPE => 'INT4'}); - $dbh->bz_alter_column('longdescs', 'comment_id', - {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - } + my $dbh = Bugzilla->dbh; + if ($dbh->bz_column_info('longdescs', 'comment_id')->{TYPE} ne 'INTSERIAL') { + $dbh->bz_drop_related_fks('longdescs', 'comment_id'); + $dbh->bz_alter_column('bugs_activity', 'comment_id', {TYPE => 'INT4'}); + $dbh->bz_alter_column('longdescs', 'comment_id', + {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + } } sub _fix_dependencies_dupes { - my $dbh = Bugzilla->dbh; - my $blocked_idx = $dbh->bz_index_info('dependencies', 'dependencies_blocked_idx'); - if ($blocked_idx && scalar @{$blocked_idx->{'FIELDS'}} < 2) { - # Remove duplicated entries - my $dupes = $dbh->selectall_arrayref(" + my $dbh = Bugzilla->dbh; + my $blocked_idx + = $dbh->bz_index_info('dependencies', 'dependencies_blocked_idx'); + if ($blocked_idx && scalar @{$blocked_idx->{'FIELDS'}} < 2) { + + # Remove duplicated entries + my $dupes = $dbh->selectall_arrayref(" SELECT blocked, dependson, COUNT(*) AS count - FROM dependencies " . - $dbh->sql_group_by('blocked, dependson') . " - HAVING COUNT(*) > 1", - { Slice => {} }); - print "Removing duplicated entries from the 'dependencies' table...\n" if @$dupes; - foreach my $dupe (@$dupes) { - $dbh->do("DELETE FROM dependencies - WHERE blocked = ? AND dependson = ?", - undef, $dupe->{blocked}, $dupe->{dependson}); - $dbh->do("INSERT INTO dependencies (blocked, dependson) VALUES (?, ?)", - undef, $dupe->{blocked}, $dupe->{dependson}); - } - $dbh->bz_drop_index('dependencies', 'dependencies_blocked_idx'); - $dbh->bz_add_index('dependencies', 'dependencies_blocked_idx', - { FIELDS => [qw(blocked dependson)], TYPE => 'UNIQUE' }); + FROM dependencies " . $dbh->sql_group_by('blocked, dependson') . " + HAVING COUNT(*) > 1", {Slice => {}}); + print "Removing duplicated entries from the 'dependencies' table...\n" + if @$dupes; + foreach my $dupe (@$dupes) { + $dbh->do( + "DELETE FROM dependencies + WHERE blocked = ? AND dependson = ?", undef, $dupe->{blocked}, + $dupe->{dependson} + ); + $dbh->do("INSERT INTO dependencies (blocked, dependson) VALUES (?, ?)", + undef, $dupe->{blocked}, $dupe->{dependson}); } + $dbh->bz_drop_index('dependencies', 'dependencies_blocked_idx'); + $dbh->bz_add_index('dependencies', 'dependencies_blocked_idx', + {FIELDS => [qw(blocked dependson)], TYPE => 'UNIQUE'}); + } } sub _fix_flagclusions_indexes { - my $dbh = Bugzilla->dbh; - foreach my $table ('flaginclusions', 'flagexclusions') { - my $index = $table . '_type_id_idx'; - my $idx_info = $dbh->bz_index_info($table, $index); - if ($idx_info && $idx_info->{'TYPE'} ne 'UNIQUE') { - # Remove duplicated entries - my $dupes = $dbh->selectall_arrayref(" + my $dbh = Bugzilla->dbh; + foreach my $table ('flaginclusions', 'flagexclusions') { + my $index = $table . '_type_id_idx'; + my $idx_info = $dbh->bz_index_info($table, $index); + if ($idx_info && $idx_info->{'TYPE'} ne 'UNIQUE') { + + # Remove duplicated entries + my $dupes = $dbh->selectall_arrayref(" SELECT type_id, product_id, component_id, COUNT(*) AS count - FROM $table " . - $dbh->sql_group_by('type_id, product_id, component_id') . " - HAVING COUNT(*) > 1", - { Slice => {} }); - print "Removing duplicated entries from the '$table' table...\n" if @$dupes; - foreach my $dupe (@$dupes) { - $dbh->do("DELETE FROM $table + FROM $table " + . $dbh->sql_group_by('type_id, product_id, component_id') . " + HAVING COUNT(*) > 1", {Slice => {}}); + print "Removing duplicated entries from the '$table' table...\n" if @$dupes; + foreach my $dupe (@$dupes) { + $dbh->do( + "DELETE FROM $table WHERE type_id = ? AND product_id = ? AND component_id = ?", - undef, $dupe->{type_id}, $dupe->{product_id}, $dupe->{component_id}); - $dbh->do("INSERT INTO $table (type_id, product_id, component_id) VALUES (?, ?, ?)", - undef, $dupe->{type_id}, $dupe->{product_id}, $dupe->{component_id}); - } - $dbh->bz_drop_index($table, $index); - $dbh->bz_add_index($table, $index, - { FIELDS => [qw(type_id product_id component_id)], - TYPE => 'UNIQUE' }); - } + undef, $dupe->{type_id}, $dupe->{product_id}, $dupe->{component_id} + ); + $dbh->do( + "INSERT INTO $table (type_id, product_id, component_id) VALUES (?, ?, ?)", + undef, $dupe->{type_id}, $dupe->{product_id}, $dupe->{component_id}); + } + $dbh->bz_drop_index($table, $index); + $dbh->bz_add_index($table, $index, + {FIELDS => [qw(type_id product_id component_id)], TYPE => 'UNIQUE'}); } + } } sub _fix_user_api_keys_indexes { - my $dbh = Bugzilla->dbh; - - if ($dbh->bz_index_info('user_api_keys', 'user_api_keys_key')) { - $dbh->bz_drop_index('user_api_keys', 'user_api_keys_key'); - $dbh->bz_add_index('user_api_keys', 'user_api_keys_api_key_idx', - { FIELDS => ['api_key'], TYPE => 'UNIQUE' }); - } - if ($dbh->bz_index_info('user_api_keys', 'user_api_keys_user_id')) { - $dbh->bz_drop_index('user_api_keys', 'user_api_keys_user_id'); - $dbh->bz_add_index('user_api_keys', 'user_api_keys_user_id_idx', ['user_id']); - } + my $dbh = Bugzilla->dbh; + + if ($dbh->bz_index_info('user_api_keys', 'user_api_keys_key')) { + $dbh->bz_drop_index('user_api_keys', 'user_api_keys_key'); + $dbh->bz_add_index('user_api_keys', 'user_api_keys_api_key_idx', + {FIELDS => ['api_key'], TYPE => 'UNIQUE'}); + } + if ($dbh->bz_index_info('user_api_keys', 'user_api_keys_user_id')) { + $dbh->bz_drop_index('user_api_keys', 'user_api_keys_user_id'); + $dbh->bz_add_index('user_api_keys', 'user_api_keys_user_id_idx', ['user_id']); + } } sub _add_attach_size { - my $dbh = Bugzilla->dbh; + my $dbh = Bugzilla->dbh; - return if $dbh->bz_column_info('attachments', 'attach_size'); + return if $dbh->bz_column_info('attachments', 'attach_size'); - $dbh->bz_add_column('attachments', 'attach_size', - {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0}); + $dbh->bz_add_column('attachments', 'attach_size', + {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0}); - print "Setting attach_size...\n"; - $dbh->do(" + print "Setting attach_size...\n"; + $dbh->do(" UPDATE attachments INNER JOIN attach_data ON attach_data.id = attachments.attach_id SET attachments.attach_size = LENGTH(attach_data.thedata) @@ -3891,57 +4162,61 @@ sub _add_attach_size { } sub _fix_disable_mail { - # you can no longer have disabled accounts with enabled mail - Bugzilla->dbh->do("UPDATE profiles SET disable_mail = 1 WHERE is_enabled = 0"); + + # you can no longer have disabled accounts with enabled mail + Bugzilla->dbh->do("UPDATE profiles SET disable_mail = 1 WHERE is_enabled = 0"); } sub _add_restrict_ipaddr { - my $dbh = Bugzilla->dbh; - return if $dbh->bz_column_info('logincookies', 'restrict_ipaddr'); + my $dbh = Bugzilla->dbh; + return if $dbh->bz_column_info('logincookies', 'restrict_ipaddr'); - $dbh->bz_add_column('logincookies', 'restrict_ipaddr', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0}); - $dbh->do("UPDATE logincookies SET restrict_ipaddr = 1 WHERE ipaddr IS NOT NULL"); + $dbh->bz_add_column('logincookies', 'restrict_ipaddr', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0}); + $dbh->do( + "UPDATE logincookies SET restrict_ipaddr = 1 WHERE ipaddr IS NOT NULL"); } sub _migrate_group_owners { - my $dbh = Bugzilla->dbh; - return if $dbh->bz_column_info('groups', 'owner_user_id'); - $dbh->bz_add_column('groups', 'owner_user_id', {TYPE => 'INT3'}); - my $nobody = Bugzilla::User->new({ name => Bugzilla->params->{'nobody_user'}, cache => 1 }); - unless ($nobody) { - $nobody = Bugzilla::User->create( - { - login_name => Bugzilla->params->{'nobody_user'}, - realname => 'Nobody (ok to assign bugs to)', - cryptpassword => '*', - } - ); - } - $dbh->do('UPDATE groups SET owner_user_id = ?', undef, $nobody->id); + my $dbh = Bugzilla->dbh; + return if $dbh->bz_column_info('groups', 'owner_user_id'); + $dbh->bz_add_column('groups', 'owner_user_id', {TYPE => 'INT3'}); + my $nobody = Bugzilla::User->new( + {name => Bugzilla->params->{'nobody_user'}, cache => 1}); + unless ($nobody) { + $nobody = Bugzilla::User->create({ + login_name => Bugzilla->params->{'nobody_user'}, + realname => 'Nobody (ok to assign bugs to)', + cryptpassword => '*', + }); + } + $dbh->do('UPDATE groups SET owner_user_id = ?', undef, $nobody->id); } sub _migrate_nicknames { - my $dbh = Bugzilla->dbh; - my $sth = $dbh->prepare('SELECT userid FROM profiles WHERE realname LIKE "%:%" AND is_enabled = 1 AND NOT nickname'); - $sth->execute(); - while (my ($user_id) = $sth->fetchrow_array) { - my $user = Bugzilla::User->new($user_id); - $user->set_name($user->name); - $user->update(); - } + my $dbh = Bugzilla->dbh; + my $sth + = $dbh->prepare( + 'SELECT userid FROM profiles WHERE realname LIKE "%:%" AND is_enabled = 1 AND NOT nickname' + ); + $sth->execute(); + while (my ($user_id) = $sth->fetchrow_array) { + my $user = Bugzilla::User->new($user_id); + $user->set_name($user->name); + $user->update(); + } } sub _migrate_preference_categories { - my $dbh = Bugzilla->dbh; - return if $dbh->bz_column_info('setting', 'category'); - $dbh->bz_add_column('setting', 'category', - {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "'General'"}); - my @settings = @{ Bugzilla::Install::SETTINGS() }; - foreach my $params (@settings) { - $dbh->do('UPDATE setting SET category = ? WHERE name = ?', - undef, $params->{category}, $params->{name}); - } + my $dbh = Bugzilla->dbh; + return if $dbh->bz_column_info('setting', 'category'); + $dbh->bz_add_column('setting', 'category', + {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "'General'"}); + my @settings = @{Bugzilla::Install::SETTINGS()}; + foreach my $params (@settings) { + $dbh->do('UPDATE setting SET category = ? WHERE name = ?', + undef, $params->{category}, $params->{name}); + } } 1; diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm index cb1b1ad15..da019b760 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -40,10 +40,10 @@ use English qw(-no_match_vars $OSNAME); use base qw(Exporter); our @EXPORT = qw( - update_filesystem - fix_all_file_permissions - fix_dir_permissions - fix_file_permissions + update_filesystem + fix_all_file_permissions + fix_dir_permissions + fix_file_permissions ); use constant INDEX_HTML => <<'EOT'; @@ -59,12 +59,12 @@ use constant INDEX_HTML => <<'EOT'; EOT use constant HTTPD_ENV => qw( - LOCALCONFIG_ENV - BUGZILLA_UNSAFE_AUTH_DELEGATION - LOG4PERL_CONFIG_FILE - LOG4PERL_STDERR_DISABLE - USE_NYTPROF - NYTPROF_DIR + LOCALCONFIG_ENV + BUGZILLA_UNSAFE_AUTH_DELEGATION + LOG4PERL_CONFIG_FILE + LOG4PERL_STDERR_DISABLE + USE_NYTPROF + NYTPROF_DIR ); ############### @@ -72,47 +72,55 @@ use constant HTTPD_ENV => qw( ############### # Used by the permissions "constants" below. -sub _suexec { Bugzilla->localconfig->{'use_suexec'} }; -sub _group { Bugzilla->localconfig->{'webservergroup'} }; +sub _suexec { Bugzilla->localconfig->{'use_suexec'} } +sub _group { Bugzilla->localconfig->{'webservergroup'} } # Writeable by the owner only. use constant OWNER_WRITE => 0600; + # Executable by the owner only. use constant OWNER_EXECUTE => 0700; + # A directory which is only writeable by the owner. use constant DIR_OWNER_WRITE => 0700; # A cgi script that the webserver can execute. -sub WS_EXECUTE { _group() ? 0750 : 0755 }; +sub WS_EXECUTE { _group() ? 0750 : 0755 } + # A file that is read by cgi scripts, but is not ever read # directly by the webserver. -sub CGI_READ { _group() ? 0640 : 0644 }; +sub CGI_READ { _group() ? 0640 : 0644 } + # A file that is written to by cgi scripts, but is not ever # read or written directly by the webserver. -sub CGI_WRITE { _group() ? 0660 : 0666 }; +sub CGI_WRITE { _group() ? 0660 : 0666 } + # A file that is served directly by the web server. -sub WS_SERVE { (_group() and !_suexec()) ? 0640 : 0644 }; +sub WS_SERVE { (_group() and !_suexec()) ? 0640 : 0644 } # A directory whose contents can be read or served by the # webserver (so even directories containing cgi scripts # would have this permission). -sub DIR_WS_SERVE { (_group() and !_suexec()) ? 0750 : 0755 }; +sub DIR_WS_SERVE { (_group() and !_suexec()) ? 0750 : 0755 } + # A directory that is read by cgi scripts, but is never accessed # directly by the webserver -sub DIR_CGI_READ { _group() ? 0750 : 0755 }; +sub DIR_CGI_READ { _group() ? 0750 : 0755 } + # A directory that is written to by cgi scripts, but where the # scripts never needs to overwrite files created by other # users. -sub DIR_CGI_WRITE { _group() ? 0770 : 01777 }; +sub DIR_CGI_WRITE { _group() ? 0770 : 01777 } + # A directory that is written to by cgi scripts, where the # scripts need to overwrite files created by other users. -sub DIR_CGI_OVERWRITE { _group() ? 0770 : 0777 }; +sub DIR_CGI_OVERWRITE { _group() ? 0770 : 0777 } # This can be combined (using "|") with other permissions for # directories that, in addition to their normal permissions (such # as DIR_CGI_WRITE) also have content served directly from them # (or their subdirectories) to the user, via the webserver. -sub DIR_ALSO_WS_SERVE { _suexec() ? 0001 : 0 }; +sub DIR_ALSO_WS_SERVE { _suexec() ? 0001 : 0 } sub DIR_ALSO_WS_STICKY { $OSNAME eq 'linux' ? 02000 : 0 } @@ -127,469 +135,437 @@ sub DIR_ALSO_WS_STICKY { $OSNAME eq 'linux' ? 02000 : 0 } # by this group. Otherwise someone may find it possible to change the cgis # when exploiting some security flaw somewhere (not necessarily in Bugzilla!) sub FILESYSTEM { - my $datadir = bz_locations()->{'datadir'}; - my $confdir = bz_locations()->{'confdir'}; - my $attachdir = bz_locations()->{'attachdir'}; - my $extensionsdir = bz_locations()->{'extensionsdir'}; - my $webdotdir = bz_locations()->{'webdotdir'}; - my $templatedir = bz_locations()->{'templatedir'}; - my $libdir = bz_locations()->{'libpath'}; - my $extlib = bz_locations()->{'ext_libpath'}; - my $skinsdir = bz_locations()->{'skinsdir'}; - my $localconfig = bz_locations()->{'localconfig'}; - my $template_cache = bz_locations()->{'template_cache'}; - my $graphsdir = bz_locations()->{'graphsdir'}; - my $assetsdir = bz_locations()->{'assetsdir'}; - my $logsdir = bz_locations()->{'logsdir'}; - - # We want to set the permissions the same for all localconfig files - # across all PROJECTs, so we do something special with $localconfig, - # lower down in the permissions section. - if ($ENV{PROJECT}) { - $localconfig =~ s/\.\Q$ENV{PROJECT}\E$//; - } - - # Note: When being processed by checksetup, these have their permissions - # set in this order: %all_dirs, %recurse_dirs, %all_files. - # - # Each is processed in alphabetical order of keys, so shorter keys - # will have their permissions set before longer keys (thus setting - # the permissions on parent directories before setting permissions - # on their children). - - # --- FILE PERMISSIONS (Non-created files) --- # - my %files = ( - '*' => { perms => OWNER_WRITE }, - # Some .pl files are WS_EXECUTE because we want - # users to be able to cron them or otherwise run - # them as a secure user, like the webserver owner. - '*.cgi' => { perms => WS_EXECUTE }, - '*.psgi' => { perms => CGI_READ }, - 'whineatnews.pl' => { perms => WS_EXECUTE }, - 'collectstats.pl' => { perms => WS_EXECUTE }, - 'importxml.pl' => { perms => WS_EXECUTE }, - 'testserver.pl' => { perms => WS_EXECUTE }, - 'whine.pl' => { perms => WS_EXECUTE }, - 'email_in.pl' => { perms => WS_EXECUTE }, - 'sanitycheck.pl' => { perms => WS_EXECUTE }, - 'checksetup.pl' => { perms => OWNER_EXECUTE }, - 'runtests.pl' => { perms => OWNER_EXECUTE }, - 'jobqueue.pl' => { perms => OWNER_EXECUTE }, - 'migrate.pl' => { perms => OWNER_EXECUTE }, - 'Makefile.PL' => { perms => OWNER_EXECUTE }, - 'gen-cpanfile.pl' => { perms => OWNER_EXECUTE }, - 'jobqueue-worker.pl' => { perms => OWNER_EXECUTE }, - 'clean-bug-user-last-visit.pl' => { perms => WS_EXECUTE }, - - 'bugzilla.pl' => { perms => OWNER_EXECUTE }, - 'Bugzilla.pm' => { perms => CGI_READ }, - "$localconfig*" => { perms => CGI_READ }, - 'META.*' => { perms => CGI_READ }, - 'MYMETA.*' => { perms => CGI_READ }, - 'bugzilla.dtd' => { perms => WS_SERVE }, - 'mod_perl.pl' => { perms => WS_SERVE }, - 'cvs-update.log' => { perms => WS_SERVE }, - 'scripts/sendunsentbugmail.pl' => { perms => WS_EXECUTE }, - 'docs/bugzilla.ent' => { perms => OWNER_WRITE }, - 'docs/makedocs.pl' => { perms => OWNER_EXECUTE }, - 'docs/style.css' => { perms => WS_SERVE }, - 'docs/*/rel_notes.txt' => { perms => WS_SERVE }, - 'docs/*/README.docs' => { perms => OWNER_WRITE }, - "$datadir/params" => { perms => CGI_WRITE }, - "$datadir/old-params.txt" => { perms => OWNER_WRITE }, - "$extensionsdir/create.pl" => { perms => OWNER_EXECUTE }, - "$extensionsdir/*/*.pl" => { perms => WS_EXECUTE }, - "$extensionsdir/*/bin/*" => { perms => WS_EXECUTE }, - - # google webmaster tools verification files - 'google*.html' => { perms => WS_SERVE }, - 'contribute.json' => { perms => WS_SERVE }, + my $datadir = bz_locations()->{'datadir'}; + my $confdir = bz_locations()->{'confdir'}; + my $attachdir = bz_locations()->{'attachdir'}; + my $extensionsdir = bz_locations()->{'extensionsdir'}; + my $webdotdir = bz_locations()->{'webdotdir'}; + my $templatedir = bz_locations()->{'templatedir'}; + my $libdir = bz_locations()->{'libpath'}; + my $extlib = bz_locations()->{'ext_libpath'}; + my $skinsdir = bz_locations()->{'skinsdir'}; + my $localconfig = bz_locations()->{'localconfig'}; + my $template_cache = bz_locations()->{'template_cache'}; + my $graphsdir = bz_locations()->{'graphsdir'}; + my $assetsdir = bz_locations()->{'assetsdir'}; + my $logsdir = bz_locations()->{'logsdir'}; + + # We want to set the permissions the same for all localconfig files + # across all PROJECTs, so we do something special with $localconfig, + # lower down in the permissions section. + if ($ENV{PROJECT}) { + $localconfig =~ s/\.\Q$ENV{PROJECT}\E$//; + } + + # Note: When being processed by checksetup, these have their permissions + # set in this order: %all_dirs, %recurse_dirs, %all_files. + # + # Each is processed in alphabetical order of keys, so shorter keys + # will have their permissions set before longer keys (thus setting + # the permissions on parent directories before setting permissions + # on their children). + + # --- FILE PERMISSIONS (Non-created files) --- # + my %files = ( + '*' => {perms => OWNER_WRITE}, + + # Some .pl files are WS_EXECUTE because we want + # users to be able to cron them or otherwise run + # them as a secure user, like the webserver owner. + '*.cgi' => {perms => WS_EXECUTE}, + '*.psgi' => {perms => CGI_READ}, + 'whineatnews.pl' => {perms => WS_EXECUTE}, + 'collectstats.pl' => {perms => WS_EXECUTE}, + 'importxml.pl' => {perms => WS_EXECUTE}, + 'testserver.pl' => {perms => WS_EXECUTE}, + 'whine.pl' => {perms => WS_EXECUTE}, + 'email_in.pl' => {perms => WS_EXECUTE}, + 'sanitycheck.pl' => {perms => WS_EXECUTE}, + 'checksetup.pl' => {perms => OWNER_EXECUTE}, + 'runtests.pl' => {perms => OWNER_EXECUTE}, + 'jobqueue.pl' => {perms => OWNER_EXECUTE}, + 'migrate.pl' => {perms => OWNER_EXECUTE}, + 'Makefile.PL' => {perms => OWNER_EXECUTE}, + 'gen-cpanfile.pl' => {perms => OWNER_EXECUTE}, + 'jobqueue-worker.pl' => {perms => OWNER_EXECUTE}, + 'clean-bug-user-last-visit.pl' => {perms => WS_EXECUTE}, + + 'bugzilla.pl' => {perms => OWNER_EXECUTE}, + 'Bugzilla.pm' => {perms => CGI_READ}, + "$localconfig*" => {perms => CGI_READ}, + 'META.*' => {perms => CGI_READ}, + 'MYMETA.*' => {perms => CGI_READ}, + 'bugzilla.dtd' => {perms => WS_SERVE}, + 'mod_perl.pl' => {perms => WS_SERVE}, + 'cvs-update.log' => {perms => WS_SERVE}, + 'scripts/sendunsentbugmail.pl' => {perms => WS_EXECUTE}, + 'docs/bugzilla.ent' => {perms => OWNER_WRITE}, + 'docs/makedocs.pl' => {perms => OWNER_EXECUTE}, + 'docs/style.css' => {perms => WS_SERVE}, + 'docs/*/rel_notes.txt' => {perms => WS_SERVE}, + 'docs/*/README.docs' => {perms => OWNER_WRITE}, + "$datadir/params" => {perms => CGI_WRITE}, + "$datadir/old-params.txt" => {perms => OWNER_WRITE}, + "$extensionsdir/create.pl" => {perms => OWNER_EXECUTE}, + "$extensionsdir/*/*.pl" => {perms => WS_EXECUTE}, + "$extensionsdir/*/bin/*" => {perms => WS_EXECUTE}, + + # google webmaster tools verification files + 'google*.html' => {perms => WS_SERVE}, + 'contribute.json' => {perms => WS_SERVE}, + ); + + # Directories that we want to set the perms on, but not + # recurse through. These are directories we didn't create + # in checkesetup.pl. + # + # Purpose of BMO change: unknown. + my %non_recurse_dirs = ('.' => 0755, docs => DIR_WS_SERVE,); + + # This sets the permissions for each item inside each of these + # directories, including the directory itself. + # 'CVS' directories are special, though, and are never readable by + # the webserver. + my %recurse_dirs = ( + + # Writeable directories + $template_cache => {files => CGI_READ, dirs => DIR_CGI_OVERWRITE}, + $attachdir => {files => CGI_WRITE, dirs => DIR_CGI_WRITE}, + $webdotdir => {files => WS_SERVE, dirs => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE}, + $graphsdir => {files => WS_SERVE, dirs => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE}, + "$datadir/db" => {files => CGI_WRITE, dirs => DIR_CGI_WRITE}, + $logsdir => {files => CGI_WRITE, dirs => DIR_CGI_WRITE | DIR_ALSO_WS_STICKY}, + $assetsdir => + {files => WS_SERVE, dirs => DIR_CGI_OVERWRITE | DIR_ALSO_WS_SERVE}, + + # Readable directories + "$datadir/mining" => {files => CGI_READ, dirs => DIR_CGI_READ}, + "$libdir/Bugzilla" => {files => CGI_READ, dirs => DIR_CGI_READ}, + $extlib => {files => CGI_READ, dirs => DIR_CGI_READ}, + $templatedir => {files => CGI_READ, dirs => DIR_CGI_READ}, + + # Directories in the extensions/ dir are WS_SERVE so that + # the web/ directories can be served by the web server. + # But, for extra security, we deny direct webserver access to + # the lib/ and template/ directories of extensions. + $extensionsdir => {files => CGI_READ, dirs => DIR_WS_SERVE}, + "$extensionsdir/*/lib" => {files => CGI_READ, dirs => DIR_CGI_READ}, + "$extensionsdir/*/template" => {files => CGI_READ, dirs => DIR_CGI_READ}, + + # Content served directly by the webserver + images => {files => WS_SERVE, dirs => DIR_WS_SERVE}, + js => {files => WS_SERVE, dirs => DIR_WS_SERVE}, + static => {files => WS_SERVE, dirs => DIR_WS_SERVE}, + $skinsdir => {files => WS_SERVE, dirs => DIR_WS_SERVE}, + 'docs/*/html' => {files => WS_SERVE, dirs => DIR_WS_SERVE}, + 'docs/*/pdf' => {files => WS_SERVE, dirs => DIR_WS_SERVE}, + 'docs/*/txt' => {files => WS_SERVE, dirs => DIR_WS_SERVE}, + 'docs/*/images' => {files => WS_SERVE, dirs => DIR_WS_SERVE}, + "$extensionsdir/*/web" => {files => WS_SERVE, dirs => DIR_WS_SERVE}, + $confdir => {files => WS_SERVE, dirs => DIR_WS_SERVE,}, + + # Purpose: allow webserver to read .bzr so we execute bzr commands + # in backticks and look at the result over the web. Used to show + # bzr history. + '.bzr' => {files => WS_SERVE, dirs => DIR_WS_SERVE}, + + # Directories only for the owner, not for the webserver. + t => {files => OWNER_WRITE, dirs => DIR_OWNER_WRITE}, + xt => {files => OWNER_WRITE, dirs => DIR_OWNER_WRITE}, + 'docs/lib' => {files => OWNER_WRITE, dirs => DIR_OWNER_WRITE}, + 'docs/*/xml' => {files => OWNER_WRITE, dirs => DIR_OWNER_WRITE}, + 'contrib' => {files => OWNER_EXECUTE, dirs => DIR_OWNER_WRITE,}, + 'scripts' => {files => OWNER_EXECUTE, dirs => DIR_WS_SERVE,}, + ); + + # --- FILES TO CREATE --- # + + # The name of each directory that we should actually *create*, + # pointing at its default permissions. + my %create_dirs = ( + + # This is DIR_ALSO_WS_SERVE because it contains $webdotdir and + # $assetsdir. + $datadir => DIR_CGI_OVERWRITE | DIR_ALSO_WS_SERVE, + + # Directories that are read-only for cgi scripts + "$datadir/mining" => DIR_CGI_READ, + "$datadir/extensions" => DIR_CGI_READ, + $extensionsdir => DIR_CGI_READ, + + # Directories that cgi scripts can write to. + "$datadir/db" => DIR_CGI_WRITE, + $attachdir => DIR_CGI_WRITE, + $graphsdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE, + $webdotdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE, + $assetsdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE, + $template_cache => DIR_CGI_WRITE, + $logsdir => DIR_CGI_WRITE | DIR_ALSO_WS_STICKY, + + # Directories that contain content served directly by the web server. + "$skinsdir/custom" => DIR_WS_SERVE, + "$skinsdir/contrib" => DIR_WS_SERVE, + $confdir => DIR_CGI_READ, + ); + + my $yui_all_css = sub { + return join( + "\n", + map { + my $css = read_file($_); + _css_url_fix($css, $_, "skins/yui.css.list") + } read_file("skins/yui.css.list", {chomp => 1}) ); - - # Directories that we want to set the perms on, but not - # recurse through. These are directories we didn't create - # in checkesetup.pl. - # - # Purpose of BMO change: unknown. - my %non_recurse_dirs = ( - '.' => 0755, - docs => DIR_WS_SERVE, - ); - - # This sets the permissions for each item inside each of these - # directories, including the directory itself. - # 'CVS' directories are special, though, and are never readable by - # the webserver. - my %recurse_dirs = ( - # Writeable directories - $template_cache => { files => CGI_READ, - dirs => DIR_CGI_OVERWRITE }, - $attachdir => { files => CGI_WRITE, - dirs => DIR_CGI_WRITE }, - $webdotdir => { files => WS_SERVE, - dirs => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE }, - $graphsdir => { files => WS_SERVE, - dirs => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE }, - "$datadir/db" => { files => CGI_WRITE, - dirs => DIR_CGI_WRITE }, - $logsdir => { files => CGI_WRITE, - dirs => DIR_CGI_WRITE | DIR_ALSO_WS_STICKY }, - $assetsdir => { files => WS_SERVE, - dirs => DIR_CGI_OVERWRITE | DIR_ALSO_WS_SERVE }, - - # Readable directories - "$datadir/mining" => { files => CGI_READ, - dirs => DIR_CGI_READ }, - "$libdir/Bugzilla" => { files => CGI_READ, - dirs => DIR_CGI_READ }, - $extlib => { files => CGI_READ, - dirs => DIR_CGI_READ }, - $templatedir => { files => CGI_READ, - dirs => DIR_CGI_READ }, - # Directories in the extensions/ dir are WS_SERVE so that - # the web/ directories can be served by the web server. - # But, for extra security, we deny direct webserver access to - # the lib/ and template/ directories of extensions. - $extensionsdir => { files => CGI_READ, - dirs => DIR_WS_SERVE }, - "$extensionsdir/*/lib" => { files => CGI_READ, - dirs => DIR_CGI_READ }, - "$extensionsdir/*/template" => { files => CGI_READ, - dirs => DIR_CGI_READ }, - - # Content served directly by the webserver - images => { files => WS_SERVE, - dirs => DIR_WS_SERVE }, - js => { files => WS_SERVE, - dirs => DIR_WS_SERVE }, - static => { files => WS_SERVE, - dirs => DIR_WS_SERVE }, - $skinsdir => { files => WS_SERVE, - dirs => DIR_WS_SERVE }, - 'docs/*/html' => { files => WS_SERVE, - dirs => DIR_WS_SERVE }, - 'docs/*/pdf' => { files => WS_SERVE, - dirs => DIR_WS_SERVE }, - 'docs/*/txt' => { files => WS_SERVE, - dirs => DIR_WS_SERVE }, - 'docs/*/images' => { files => WS_SERVE, - dirs => DIR_WS_SERVE }, - "$extensionsdir/*/web" => { files => WS_SERVE, - dirs => DIR_WS_SERVE }, - $confdir => { files => WS_SERVE, - dirs => DIR_WS_SERVE, }, - - # Purpose: allow webserver to read .bzr so we execute bzr commands - # in backticks and look at the result over the web. Used to show - # bzr history. - '.bzr' => { files => WS_SERVE, - dirs => DIR_WS_SERVE }, - # Directories only for the owner, not for the webserver. - t => { files => OWNER_WRITE, - dirs => DIR_OWNER_WRITE }, - xt => { files => OWNER_WRITE, - dirs => DIR_OWNER_WRITE }, - 'docs/lib' => { files => OWNER_WRITE, - dirs => DIR_OWNER_WRITE }, - 'docs/*/xml' => { files => OWNER_WRITE, - dirs => DIR_OWNER_WRITE }, - 'contrib' => { files => OWNER_EXECUTE, - dirs => DIR_OWNER_WRITE, }, - 'scripts' => { files => OWNER_EXECUTE, - dirs => DIR_WS_SERVE, }, - ); - - # --- FILES TO CREATE --- # - - # The name of each directory that we should actually *create*, - # pointing at its default permissions. - my %create_dirs = ( - # This is DIR_ALSO_WS_SERVE because it contains $webdotdir and - # $assetsdir. - $datadir => DIR_CGI_OVERWRITE | DIR_ALSO_WS_SERVE, - # Directories that are read-only for cgi scripts - "$datadir/mining" => DIR_CGI_READ, - "$datadir/extensions" => DIR_CGI_READ, - $extensionsdir => DIR_CGI_READ, - # Directories that cgi scripts can write to. - "$datadir/db" => DIR_CGI_WRITE, - $attachdir => DIR_CGI_WRITE, - $graphsdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE, - $webdotdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE, - $assetsdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE, - $template_cache => DIR_CGI_WRITE, - $logsdir => DIR_CGI_WRITE | DIR_ALSO_WS_STICKY, - # Directories that contain content served directly by the web server. - "$skinsdir/custom" => DIR_WS_SERVE, - "$skinsdir/contrib" => DIR_WS_SERVE, - $confdir => DIR_CGI_READ, - ); - - my $yui_all_css = sub { - return join("\n", - map { - my $css = read_file($_); - _css_url_fix($css, $_, "skins/yui.css.list") - } read_file("skins/yui.css.list", { chomp => 1 }) - ); - }; - - my $yui_all_js = sub { - return join("\n", - map { scalar read_file($_) } read_file("js/yui.js.list", { chomp => 1 }) - ); - }; - - my $yui3_all_css = sub { - return join("\n", - map { - my $css = read_file($_); - _css_url_fix($css, $_, "skins/yui3.css.list") - } read_file("skins/yui3.css.list", { chomp => 1 }) - ); - }; - - my $yui3_all_js = sub { - return join("\n", - map { scalar read_file($_) } read_file("js/yui3.js.list", { chomp => 1 }) - ); - }; - - # The name of each file, pointing at its default permissions and - # default contents. - my %create_files = ( - "$datadir/extensions/additional" => { perms => CGI_READ, - contents => '' }, - # We create this file so that it always has the right owner - # and permissions. Otherwise, the webserver creates it as - # owned by itself, which can cause problems if jobqueue.pl - # or something else is not running as the webserver or root. - "$datadir/mailer.testfile" => { perms => CGI_WRITE, - contents => '' }, - "js/yui.js" => { perms => CGI_READ, - overwrite => 1, - contents => $yui_all_js }, - "skins/yui.css" => { perms => CGI_READ, - overwrite => 1, - contents => $yui_all_css }, - "js/yui3.js" => { perms => CGI_READ, - overwrite => 1, - contents => $yui3_all_js }, - "skins/yui3.css" => { perms => CGI_READ, - overwrite => 1, - contents => $yui3_all_css }, + }; + + my $yui_all_js = sub { + return join("\n", + map { scalar read_file($_) } read_file("js/yui.js.list", {chomp => 1})); + }; + + my $yui3_all_css = sub { + return join( + "\n", + map { + my $css = read_file($_); + _css_url_fix($css, $_, "skins/yui3.css.list") + } read_file("skins/yui3.css.list", {chomp => 1}) ); + }; + + my $yui3_all_js = sub { + return join("\n", + map { scalar read_file($_) } read_file("js/yui3.js.list", {chomp => 1})); + }; + + # The name of each file, pointing at its default permissions and + # default contents. + my %create_files = ( + "$datadir/extensions/additional" => {perms => CGI_READ, contents => ''}, + + # We create this file so that it always has the right owner + # and permissions. Otherwise, the webserver creates it as + # owned by itself, which can cause problems if jobqueue.pl + # or something else is not running as the webserver or root. + "$datadir/mailer.testfile" => {perms => CGI_WRITE, contents => ''}, + "js/yui.js" => {perms => CGI_READ, overwrite => 1, contents => $yui_all_js}, + "skins/yui.css" => + {perms => CGI_READ, overwrite => 1, contents => $yui_all_css}, + "js/yui3.js" => {perms => CGI_READ, overwrite => 1, contents => $yui3_all_js}, + "skins/yui3.css" => + {perms => CGI_READ, overwrite => 1, contents => $yui3_all_css}, + ); + + # Create static error pages. + $create_dirs{"errors"} = DIR_CGI_READ; + + # Because checksetup controls the creation of index.html separately + # from all other files, it gets its very own hash. + my %index_html = ('index.html' => {perms => WS_SERVE, contents => INDEX_HTML}); + + Bugzilla::Hook::process( + 'install_filesystem', + { + files => \%files, + create_dirs => \%create_dirs, + non_recurse_dirs => \%non_recurse_dirs, + recurse_dirs => \%recurse_dirs, + create_files => \%create_files, + } + ); - # Create static error pages. - $create_dirs{"errors"} = DIR_CGI_READ; + my %all_files = (%create_files, %index_html, %files); + my %all_dirs = (%create_dirs, %non_recurse_dirs); - # Because checksetup controls the creation of index.html separately - # from all other files, it gets its very own hash. - my %index_html = ( - 'index.html' => { perms => WS_SERVE, contents => INDEX_HTML } - ); + return { + create_dirs => \%create_dirs, + recurse_dirs => \%recurse_dirs, + all_dirs => \%all_dirs, - Bugzilla::Hook::process('install_filesystem', { - files => \%files, - create_dirs => \%create_dirs, - non_recurse_dirs => \%non_recurse_dirs, - recurse_dirs => \%recurse_dirs, - create_files => \%create_files, - }); - - my %all_files = (%create_files, %index_html, %files); - my %all_dirs = (%create_dirs, %non_recurse_dirs); - - return { - create_dirs => \%create_dirs, - recurse_dirs => \%recurse_dirs, - all_dirs => \%all_dirs, - - create_files => \%create_files, - index_html => \%index_html, - all_files => \%all_files, - }; + create_files => \%create_files, + index_html => \%index_html, + all_files => \%all_files, + }; } sub update_filesystem { - my ($params) = @_; - my $fs = FILESYSTEM(); - my %dirs = %{$fs->{create_dirs}}; - my %files = %{$fs->{create_files}}; - - my $datadir = bz_locations->{'datadir'}; - my $graphsdir = bz_locations->{'graphsdir'}; - my $assetsdir = bz_locations->{'assetsdir'}; - # If the graphs/ directory doesn't exist, we're upgrading from - # a version old enough that we need to update the $datadir/mining - # format. - if (-d "$datadir/mining" && !-d $graphsdir) { - _update_old_charts($datadir); + my ($params) = @_; + my $fs = FILESYSTEM(); + my %dirs = %{$fs->{create_dirs}}; + my %files = %{$fs->{create_files}}; + + my $datadir = bz_locations->{'datadir'}; + my $graphsdir = bz_locations->{'graphsdir'}; + my $assetsdir = bz_locations->{'assetsdir'}; + + # If the graphs/ directory doesn't exist, we're upgrading from + # a version old enough that we need to update the $datadir/mining + # format. + if (-d "$datadir/mining" && !-d $graphsdir) { + _update_old_charts($datadir); + } + + # By sorting the dirs, we assure that shorter-named directories + # (meaning parent directories) are always created before their + # child directories. + foreach my $dir (sort keys %dirs) { + unless (-d $dir) { + print "Creating $dir directory...\n"; + mkdir $dir or die "mkdir $dir failed: $!"; + + # For some reason, passing in the permissions to "mkdir" + # doesn't work right, but doing a "chmod" does. + chmod $dirs{$dir}, $dir or warn "Cannot chmod $dir: $!"; } - - # By sorting the dirs, we assure that shorter-named directories - # (meaning parent directories) are always created before their - # child directories. - foreach my $dir (sort keys %dirs) { - unless (-d $dir) { - print "Creating $dir directory...\n"; - mkdir $dir or die "mkdir $dir failed: $!"; - # For some reason, passing in the permissions to "mkdir" - # doesn't work right, but doing a "chmod" does. - chmod $dirs{$dir}, $dir or warn "Cannot chmod $dir: $!"; - } - } - - # Move the testfile if we can't write to it, so that we can re-create - # it with the correct permissions below. - my $testfile = "$datadir/mailer.testfile"; - if (-e $testfile and !-w $testfile) { - _rename_file($testfile, "$testfile.old"); - } - - # If old-params.txt exists in the root directory, move it to datadir. - my $oldparamsfile = "old_params.txt"; - if (-e $oldparamsfile) { - _rename_file($oldparamsfile, "$datadir/$oldparamsfile"); - } - - _create_files(%files); - if ($params->{index_html}) { - _create_files(%{$fs->{index_html}}); - } - elsif (-e 'index.html') { - my $templatedir = bz_locations()->{'templatedir'}; - print "*** It appears that you still have an old index.html hanging around.\n", - "Either the contents of this file should be moved into a template and\n", - "placed in the '$templatedir/en/custom' directory, or you should delete\n", - "the file.\n"; - } - - # Delete old files that no longer need to exist - - # 2001-04-29 jake@bugzilla.org - Remove oldemailtech - # http://bugzilla.mozilla.org/show_bug.cgi?id=71552 - if (-d 'shadow') { - print "Removing shadow directory...\n"; - rmtree("shadow"); - } - - if (-e "$datadir/versioncache") { - print "Removing versioncache...\n"; - unlink "$datadir/versioncache"; - } - - if (-e "$datadir/duplicates.rdf") { - print "Removing duplicates.rdf...\n"; - unlink "$datadir/duplicates.rdf"; - unlink "$datadir/duplicates-old.rdf"; - } - - if (-e "$datadir/duplicates") { - print "Removing duplicates directory...\n"; - rmtree("$datadir/duplicates"); - } - - _remove_empty_css_files(); - _convert_single_file_skins(); + } + + # Move the testfile if we can't write to it, so that we can re-create + # it with the correct permissions below. + my $testfile = "$datadir/mailer.testfile"; + if (-e $testfile and !-w $testfile) { + _rename_file($testfile, "$testfile.old"); + } + + # If old-params.txt exists in the root directory, move it to datadir. + my $oldparamsfile = "old_params.txt"; + if (-e $oldparamsfile) { + _rename_file($oldparamsfile, "$datadir/$oldparamsfile"); + } + + _create_files(%files); + if ($params->{index_html}) { + _create_files(%{$fs->{index_html}}); + } + elsif (-e 'index.html') { + my $templatedir = bz_locations()->{'templatedir'}; + print "*** It appears that you still have an old index.html hanging around.\n", + "Either the contents of this file should be moved into a template and\n", + "placed in the '$templatedir/en/custom' directory, or you should delete\n", + "the file.\n"; + } + + # Delete old files that no longer need to exist + + # 2001-04-29 jake@bugzilla.org - Remove oldemailtech + # http://bugzilla.mozilla.org/show_bug.cgi?id=71552 + if (-d 'shadow') { + print "Removing shadow directory...\n"; + rmtree("shadow"); + } + + if (-e "$datadir/versioncache") { + print "Removing versioncache...\n"; + unlink "$datadir/versioncache"; + } + + if (-e "$datadir/duplicates.rdf") { + print "Removing duplicates.rdf...\n"; + unlink "$datadir/duplicates.rdf"; + unlink "$datadir/duplicates-old.rdf"; + } + + if (-e "$datadir/duplicates") { + print "Removing duplicates directory...\n"; + rmtree("$datadir/duplicates"); + } + + _remove_empty_css_files(); + _convert_single_file_skins(); } sub _css_url_fix { - my ($content, $from, $to) = @_; - my $from_dir = dirname(File::Spec->rel2abs($from, bz_locations()->{libpath})); - my $to_dir = dirname(File::Spec->rel2abs($to, bz_locations()->{libpath})); - - return css_url_rewrite( - $content, - sub { - my ($url) = @_; - if ( $url =~ m{^(?:/|data:)} ) { - return sprintf 'url(%s)', $url; - } - else { - my $new_url = File::Spec->abs2rel( - Cwd::realpath( - File::Spec->rel2abs( $url, $from_dir ) - ), - $to_dir - ); - return sprintf "url(%s)", $new_url; - } - } - ); + my ($content, $from, $to) = @_; + my $from_dir = dirname(File::Spec->rel2abs($from, bz_locations()->{libpath})); + my $to_dir = dirname(File::Spec->rel2abs($to, bz_locations()->{libpath})); + + return css_url_rewrite( + $content, + sub { + my ($url) = @_; + if ($url =~ m{^(?:/|data:)}) { + return sprintf 'url(%s)', $url; + } + else { + my $new_url + = File::Spec->abs2rel(Cwd::realpath(File::Spec->rel2abs($url, $from_dir)), + $to_dir); + return sprintf "url(%s)", $new_url; + } + } + ); } sub _remove_empty_css_files { - my $skinsdir = bz_locations()->{'skinsdir'}; - foreach my $css_file (glob("$skinsdir/custom/*.css"), - glob("$skinsdir/contrib/*/*.css")) - { - _remove_empty_css($css_file); - } + my $skinsdir = bz_locations()->{'skinsdir'}; + foreach my $css_file (glob("$skinsdir/custom/*.css"), + glob("$skinsdir/contrib/*/*.css")) + { + _remove_empty_css($css_file); + } } # A simple helper for the update code that removes "empty" CSS files. sub _remove_empty_css { - my ($file) = @_; - my $basename = basename($file); - my $empty_contents = "/* Custom rules for $basename.\n" - . " * The rules you put here override rules in that stylesheet. */"; - if (length($empty_contents) == -s $file) { - open(my $fh, '<', $file) or warn "$file: $!"; - my $file_contents; - { local $/; $file_contents = <$fh>; } - if ($file_contents eq $empty_contents) { - print install_string('file_remove', { name => $file }), "\n"; - unlink $file or warn "$file: $!"; - } - }; + my ($file) = @_; + my $basename = basename($file); + my $empty_contents = "/* Custom rules for $basename.\n" + . " * The rules you put here override rules in that stylesheet. */"; + if (length($empty_contents) == -s $file) { + open(my $fh, '<', $file) or warn "$file: $!"; + my $file_contents; + { local $/; $file_contents = <$fh>; } + if ($file_contents eq $empty_contents) { + print install_string('file_remove', {name => $file}), "\n"; + unlink $file or warn "$file: $!"; + } + } } # We used to allow a single css file in the skins/contrib/ directory # to be a whole skin. sub _convert_single_file_skins { - my $skinsdir = bz_locations()->{'skinsdir'}; - foreach my $skin_file (glob "$skinsdir/contrib/*.css") { - my $dir_name = $skin_file; - $dir_name =~ s/\.css$//; - mkdir $dir_name or warn "$dir_name: $!"; - _rename_file($skin_file, "$dir_name/global.css"); - } + my $skinsdir = bz_locations()->{'skinsdir'}; + foreach my $skin_file (glob "$skinsdir/contrib/*.css") { + my $dir_name = $skin_file; + $dir_name =~ s/\.css$//; + mkdir $dir_name or warn "$dir_name: $!"; + _rename_file($skin_file, "$dir_name/global.css"); + } } sub _rename_file { - my ($from, $to) = @_; - print install_string('file_rename', { from => $from, to => $to }), "\n"; - if (-e $to) { - warn "$to already exists, not moving\n"; - } - else { - move($from, $to) or warn $!; - } + my ($from, $to) = @_; + print install_string('file_rename', {from => $from, to => $to}), "\n"; + if (-e $to) { + warn "$to already exists, not moving\n"; + } + else { + move($from, $to) or warn $!; + } } # A helper for the above functions. sub _create_files { - my (%files) = @_; - - # It's not necessary to sort these, but it does make the - # output of checksetup.pl look a bit nicer. - foreach my $file (sort keys %files) { - my $info = $files{$file}; - if ($info->{overwrite} or not -f $file) { - print "Creating $file...\n"; - my $fh = IO::File->new( $file, O_WRONLY | O_CREAT | O_TRUNC, $info->{perms} ) - or die "unable to write $file: $!"; - my $contents = $info->{contents}; - if (defined $contents && ref($contents) eq 'CODE') { - print $fh $contents->(); - } - elsif (defined $contents) { - print $fh $contents; - } - $fh->close; - } + my (%files) = @_; + + # It's not necessary to sort these, but it does make the + # output of checksetup.pl look a bit nicer. + foreach my $file (sort keys %files) { + my $info = $files{$file}; + if ($info->{overwrite} or not -f $file) { + print "Creating $file...\n"; + my $fh = IO::File->new($file, O_WRONLY | O_CREAT | O_TRUNC, $info->{perms}) + or die "unable to write $file: $!"; + my $contents = $info->{contents}; + if (defined $contents && ref($contents) eq 'CODE') { + print $fh $contents->(); + } + elsif (defined $contents) { + print $fh $contents; + } + $fh->close; } + } } # If you ran a REALLY old version of Bugzilla, your chart files are in the @@ -597,243 +573,266 @@ sub _create_files { # when moving it into this module, I couldn't test it so I left it almost # completely alone. sub _update_old_charts { - my ($datadir) = @_; - print "Updating old chart storage format...\n"; - foreach my $in_file (glob("$datadir/mining/*")) { - # Don't try and upgrade image or db files! - next if (($in_file =~ /\.gif$/i) || - ($in_file =~ /\.png$/i) || - ($in_file =~ /\.db$/i) || - ($in_file =~ /\.orig$/i)); - - rename("$in_file", "$in_file.orig") or next; - open(IN, "<", "$in_file.orig") or next; - open(OUT, '>', $in_file) or next; - - # Fields in the header - my @declared_fields; - - # Fields we changed to half way through by mistake - # This list comes from an old version of collectstats.pl - # This part is only for people who ran later versions of 2.11 (devel) - my @intermediate_fields = qw(DATE UNCONFIRMED NEW ASSIGNED REOPENED - RESOLVED VERIFIED CLOSED); - - # Fields we actually want (matches the current collectstats.pl) - my @out_fields = qw(DATE NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED - VERIFIED CLOSED FIXED INVALID WONTFIX LATER REMIND - DUPLICATE WORKSFORME MOVED); - - while () { - if (/^# fields?: (.*)\s$/) { - @declared_fields = map uc, (split /\||\r/, $1); - print OUT "# fields: ", join('|', @out_fields), "\n"; - } - elsif (/^(\d+\|.*)/) { - my @data = split(/\||\r/, $1); - my %data; - if (@data == @declared_fields) { - # old format - for my $i (0 .. $#declared_fields) { - $data{$declared_fields[$i]} = $data[$i]; - } - } - elsif (@data == @intermediate_fields) { - # Must have changed over at this point - for my $i (0 .. $#intermediate_fields) { - $data{$intermediate_fields[$i]} = $data[$i]; - } - } - elsif (@data == @out_fields) { - # This line's fine - it has the right number of entries - for my $i (0 .. $#out_fields) { - $data{$out_fields[$i]} = $data[$i]; - } - } - else { - print "Oh dear, input line $. of $in_file had " . - scalar(@data) . " fields\nThis was unexpected.", - " You may want to check your data files.\n"; - } - - print OUT join('|', - map { defined ($data{$_}) ? ($data{$_}) : "" } @out_fields), - "\n"; - } - else { - print OUT; - } + my ($datadir) = @_; + print "Updating old chart storage format...\n"; + foreach my $in_file (glob("$datadir/mining/*")) { + + # Don't try and upgrade image or db files! + next + if (($in_file =~ /\.gif$/i) + || ($in_file =~ /\.png$/i) + || ($in_file =~ /\.db$/i) + || ($in_file =~ /\.orig$/i)); + + rename("$in_file", "$in_file.orig") or next; + open(IN, "<", "$in_file.orig") or next; + open(OUT, '>', $in_file) or next; + + # Fields in the header + my @declared_fields; + + # Fields we changed to half way through by mistake + # This list comes from an old version of collectstats.pl + # This part is only for people who ran later versions of 2.11 (devel) + my @intermediate_fields = qw(DATE UNCONFIRMED NEW ASSIGNED REOPENED + RESOLVED VERIFIED CLOSED); + + # Fields we actually want (matches the current collectstats.pl) + my @out_fields = qw(DATE NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED + VERIFIED CLOSED FIXED INVALID WONTFIX LATER REMIND + DUPLICATE WORKSFORME MOVED); + + while () { + if (/^# fields?: (.*)\s$/) { + @declared_fields = map uc, (split /\||\r/, $1); + print OUT "# fields: ", join('|', @out_fields), "\n"; + } + elsif (/^(\d+\|.*)/) { + my @data = split(/\||\r/, $1); + my %data; + if (@data == @declared_fields) { + + # old format + for my $i (0 .. $#declared_fields) { + $data{$declared_fields[$i]} = $data[$i]; + } } + elsif (@data == @intermediate_fields) { - close(IN); - close(OUT); + # Must have changed over at this point + for my $i (0 .. $#intermediate_fields) { + $data{$intermediate_fields[$i]} = $data[$i]; + } + } + elsif (@data == @out_fields) { + + # This line's fine - it has the right number of entries + for my $i (0 .. $#out_fields) { + $data{$out_fields[$i]} = $data[$i]; + } + } + else { + print "Oh dear, input line $. of $in_file had " + . scalar(@data) + . " fields\nThis was unexpected.", + " You may want to check your data files.\n"; + } + + print OUT join('|', map { defined($data{$_}) ? ($data{$_}) : "" } @out_fields), + "\n"; + } + else { + print OUT; + } } + + close(IN); + close(OUT); + } } sub fix_dir_permissions { - my ($dir) = @_; - return if ON_WINDOWS; - # Note that _get_owner_and_group is always silent here. - my ($owner_id, $group_id) = _get_owner_and_group(); - - my $perms; - my $fs = FILESYSTEM(); - if ($perms = $fs->{recurse_dirs}->{$dir}) { - _fix_perms_recursively($dir, $owner_id, $group_id, $perms); - } - elsif ($perms = $fs->{all_dirs}->{$dir}) { - _fix_perms($dir, $owner_id, $group_id, $perms); - } - else { - # Do nothing. We know nothing about this directory. - warn "Unknown directory $dir"; - } + my ($dir) = @_; + return if ON_WINDOWS; + + # Note that _get_owner_and_group is always silent here. + my ($owner_id, $group_id) = _get_owner_and_group(); + + my $perms; + my $fs = FILESYSTEM(); + if ($perms = $fs->{recurse_dirs}->{$dir}) { + _fix_perms_recursively($dir, $owner_id, $group_id, $perms); + } + elsif ($perms = $fs->{all_dirs}->{$dir}) { + _fix_perms($dir, $owner_id, $group_id, $perms); + } + else { + # Do nothing. We know nothing about this directory. + warn "Unknown directory $dir"; + } } sub fix_file_permissions { - my ($file) = @_; - return if ON_WINDOWS; - my $perms = FILESYSTEM()->{all_files}->{$file}->{perms}; - # Note that _get_owner_and_group is always silent here. - my ($owner_id, $group_id) = _get_owner_and_group(); - _fix_perms($file, $owner_id, $group_id, $perms); + my ($file) = @_; + return if ON_WINDOWS; + my $perms = FILESYSTEM()->{all_files}->{$file}->{perms}; + + # Note that _get_owner_and_group is always silent here. + my ($owner_id, $group_id) = _get_owner_and_group(); + _fix_perms($file, $owner_id, $group_id, $perms); } sub fix_all_file_permissions { - my ($output) = @_; + my ($output) = @_; - # _get_owner_and_group also checks that the webservergroup is valid. - my ($owner_id, $group_id) = _get_owner_and_group($output); + # _get_owner_and_group also checks that the webservergroup is valid. + my ($owner_id, $group_id) = _get_owner_and_group($output); - return if ON_WINDOWS; + return if ON_WINDOWS; - my $fs = FILESYSTEM(); - my %files = %{$fs->{all_files}}; - my %dirs = %{$fs->{all_dirs}}; - my %recurse_dirs = %{$fs->{recurse_dirs}}; + my $fs = FILESYSTEM(); + my %files = %{$fs->{all_files}}; + my %dirs = %{$fs->{all_dirs}}; + my %recurse_dirs = %{$fs->{recurse_dirs}}; - print get_text('install_file_perms_fix') . "\n" if $output; + print get_text('install_file_perms_fix') . "\n" if $output; - foreach my $dir (sort keys %dirs) { - next unless -d $dir; - _fix_perms($dir, $owner_id, $group_id, $dirs{$dir}); - } + foreach my $dir (sort keys %dirs) { + next unless -d $dir; + _fix_perms($dir, $owner_id, $group_id, $dirs{$dir}); + } - foreach my $pattern (sort keys %recurse_dirs) { - my $perms = $recurse_dirs{$pattern}; - # %recurse_dirs supports globs - foreach my $dir (glob $pattern) { - next unless -d $dir; - _fix_perms_recursively($dir, $owner_id, $group_id, $perms); - } + foreach my $pattern (sort keys %recurse_dirs) { + my $perms = $recurse_dirs{$pattern}; + + # %recurse_dirs supports globs + foreach my $dir (glob $pattern) { + next unless -d $dir; + _fix_perms_recursively($dir, $owner_id, $group_id, $perms); } + } - foreach my $file (sort keys %files) { - # %files supports globs - foreach my $filename (glob $file) { - # Don't touch directories. - next if -d $filename || !-e $filename; - _fix_perms($filename, $owner_id, $group_id, - $files{$file}->{perms}); - } + foreach my $file (sort keys %files) { + + # %files supports globs + foreach my $filename (glob $file) { + + # Don't touch directories. + next if -d $filename || !-e $filename; + _fix_perms($filename, $owner_id, $group_id, $files{$file}->{perms}); } + } - _fix_cvs_dirs($owner_id, '.'); + _fix_cvs_dirs($owner_id, '.'); } sub _get_owner_and_group { - my ($output) = @_; - my $group_id = _check_web_server_group($output); - return () if ON_WINDOWS; + my ($output) = @_; + my $group_id = _check_web_server_group($output); + return () if ON_WINDOWS; - my $owner_id = POSIX::getuid(); - $group_id = POSIX::getgid() unless defined $group_id; - return ($owner_id, $group_id); + my $owner_id = POSIX::getuid(); + $group_id = POSIX::getgid() unless defined $group_id; + return ($owner_id, $group_id); } # A helper for fix_all_file_permissions sub _fix_cvs_dirs { - my ($owner_id, $dir) = @_; - my $owner_gid = POSIX::getgid(); - find({ no_chdir => 1, wanted => sub { + my ($owner_id, $dir) = @_; + my $owner_gid = POSIX::getgid(); + find( + { + no_chdir => 1, + wanted => sub { my $name = $File::Find::name; - if ($File::Find::dir =~ /\/CVS/ || $_ eq '.cvsignore' - || (-d $name && $_ =~ /CVS$/)) + if ( $File::Find::dir =~ /\/CVS/ + || $_ eq '.cvsignore' + || (-d $name && $_ =~ /CVS$/)) { - my $perms = 0600; - if (-d $name) { - $perms = 0700; - } - _fix_perms($name, $owner_id, $owner_gid, $perms); + my $perms = 0600; + if (-d $name) { + $perms = 0700; + } + _fix_perms($name, $owner_id, $owner_gid, $perms); } - }}, $dir); + } + }, + $dir + ); } sub _fix_perms { - my ($name, $owner, $group, $perms) = @_; - #printf ("Changing $name to %o\n", $perms); - - # The webserver should never try to chown files. - if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) { - chown $owner, $group, $name - or warn install_string('chown_failed', { path => $name, - error => $! }) . "\n"; - } - chmod $perms, $name - or warn install_string('chmod_failed', { path => $name, - error => $! }) . "\n"; + my ($name, $owner, $group, $perms) = @_; + + #printf ("Changing $name to %o\n", $perms); + + # The webserver should never try to chown files. + if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) { + chown $owner, $group, $name + or warn install_string('chown_failed', {path => $name, error => $!}) . "\n"; + } + chmod $perms, $name + or warn install_string('chmod_failed', {path => $name, error => $!}) . "\n"; } sub _fix_perms_recursively { - my ($dir, $owner_id, $group_id, $perms) = @_; - # Set permissions on the directory itself. - _fix_perms($dir, $owner_id, $group_id, $perms->{dirs}); - # Now recurse through the directory and set the correct permissions - # on subdirectories and files. - find({ no_chdir => 1, wanted => sub { + my ($dir, $owner_id, $group_id, $perms) = @_; + + # Set permissions on the directory itself. + _fix_perms($dir, $owner_id, $group_id, $perms->{dirs}); + + # Now recurse through the directory and set the correct permissions + # on subdirectories and files. + find( + { + no_chdir => 1, + wanted => sub { my $name = $File::Find::name; if (-d $name) { - _fix_perms($name, $owner_id, $group_id, $perms->{dirs}); + _fix_perms($name, $owner_id, $group_id, $perms->{dirs}); } else { - _fix_perms($name, $owner_id, $group_id, $perms->{files}); + _fix_perms($name, $owner_id, $group_id, $perms->{files}); } - }}, $dir); + } + }, + $dir + ); } sub _check_web_server_group { - my ($output) = @_; - - my $group = Bugzilla->localconfig->{'webservergroup'}; - my $filename = bz_locations()->{'localconfig'}; - my $group_id; - - # If we are on Windows, webservergroup does nothing - if (ON_WINDOWS && $group && $output) { - print "\n\n" . get_text('install_webservergroup_windows') . "\n\n"; - } - - # If we're not on Windows, make sure that webservergroup isn't - # empty. - elsif (!ON_WINDOWS && !$group && $output) { - print "\n\n" . get_text('install_webservergroup_empty') . "\n\n"; - } - - # If we're not on Windows, make sure we are actually a member of - # the webservergroup. - elsif (!ON_WINDOWS && $group) { - $group_id = getgrnam($group); - ThrowCodeError('invalid_webservergroup', { group => $group }) - unless defined $group_id; - - # If on unix, see if we need to print a warning about a webservergroup - # that we can't chgrp to - if ($output && $< != 0 && !grep($_ eq $group_id, split(" ", $)))) { - print "\n\n" . get_text('install_webservergroup_not_in') . "\n\n"; - } + my ($output) = @_; + + my $group = Bugzilla->localconfig->{'webservergroup'}; + my $filename = bz_locations()->{'localconfig'}; + my $group_id; + + # If we are on Windows, webservergroup does nothing + if (ON_WINDOWS && $group && $output) { + print "\n\n" . get_text('install_webservergroup_windows') . "\n\n"; + } + + # If we're not on Windows, make sure that webservergroup isn't + # empty. + elsif (!ON_WINDOWS && !$group && $output) { + print "\n\n" . get_text('install_webservergroup_empty') . "\n\n"; + } + + # If we're not on Windows, make sure we are actually a member of + # the webservergroup. + elsif (!ON_WINDOWS && $group) { + $group_id = getgrnam($group); + ThrowCodeError('invalid_webservergroup', {group => $group}) + unless defined $group_id; + + # If on unix, see if we need to print a warning about a webservergroup + # that we can't chgrp to + if ($output && $< != 0 && !grep($_ eq $group_id, split(" ", $)))) { + print "\n\n" . get_text('install_webservergroup_not_in') . "\n\n"; } + } - return $group_id; + return $group_id; } diff --git a/Bugzilla/Install/Localconfig.pm b/Bugzilla/Install/Localconfig.pm index 6650eca27..fad7404d8 100644 --- a/Bugzilla/Install/Localconfig.pm +++ b/Bugzilla/Install/Localconfig.pm @@ -36,276 +36,206 @@ use Sys::Hostname qw(hostname); use parent qw(Exporter); our @EXPORT_OK = qw( - read_localconfig - update_localconfig - ENV_KEYS + read_localconfig + update_localconfig + ENV_KEYS ); # might want to change this for upstream -use constant ENV_PREFIX => 'BMO_'; -use constant PARAM_OVERRIDE => qw( use_mailer_queue mail_delivery_method shadowdb shadowdbhost shadowdbport shadowdbsock ); +use constant ENV_PREFIX => 'BMO_'; +use constant PARAM_OVERRIDE => + qw( use_mailer_queue mail_delivery_method shadowdb shadowdbhost shadowdbport shadowdbsock ); sub _sensible_group { - return '' if ON_WINDOWS; - return scalar getgrgid($EGID); + return '' if ON_WINDOWS; + return scalar getgrgid($EGID); } sub _migrate_param { - my ( $name, $fallback_value ) = @_; - - return sub { - if ( Bugzilla->can('params') ) { - return Bugzilla->params->{$name} // $fallback_value; - } - else { - return $fallback_value; - } - }; + my ($name, $fallback_value) = @_; + + return sub { + if (Bugzilla->can('params')) { + return Bugzilla->params->{$name} // $fallback_value; + } + else { + return $fallback_value; + } + }; } use constant LOCALCONFIG_VARS => ( - { - name => 'create_htaccess', - default => 1, - }, - { - name => 'webservergroup', - default => \&_sensible_group, - }, - { - name => 'use_suexec', - default => 0, - }, - { - name => 'db_driver', - default => 'mysql', - }, - { - name => 'db_host', - default => 'localhost', - }, - { - name => 'db_name', - default => 'bugs', - }, - { - - name => 'db_user', - default => 'bugs', - }, - { - name => 'db_pass', - default => '', - }, - { - name => 'db_port', - default => 0, - }, - { - name => 'db_sock', - default => '', - }, - { - name => 'db_check', - default => 1, - }, - { - name => 'index_html', - default => 0, - }, - { - name => 'cvsbin', - default => sub { bin_loc('cvs') }, - }, - { - name => 'interdiffbin', - default => sub { bin_loc('interdiff') }, - }, - { - name => 'diffpath', - default => sub { dirname( bin_loc('diff') ) }, - }, - { - name => 'tct_bin', - default => sub { bin_loc('tct') }, - }, - { - name => 'site_wide_secret', - - # 64 characters is roughly the equivalent of a 384-bit key, which - # is larger than anybody would ever be able to brute-force. - default => sub { generate_random_password(64) }, - }, - { - name => 'param_override', - default => { - use_mailer_queue => undef, - mail_delivery_method => undef, - shadowdb => undef, - shadowdbhost => undef, - shadowdbport => undef, - shadowdbsock => undef, - }, - }, - { - name => 'apache_size_limit', - default => 600000, - }, - { - name => 'memcached_servers', - default => _migrate_param( "memcached_servers", "" ), - }, - { - name => 'memcached_namespace', - default => _migrate_param( "memcached_namespace", "bugzilla:" ), - }, - { - name => 'urlbase', - default => _migrate_param( "urlbase", "" ), - }, - { - name => 'canonical_urlbase', - default => '', - }, - { - name => 'attachment_base', - default => _migrate_param( "attachment_base", '' ), - }, - { - name => 'ses_username', - default => '', - }, - { - name => 'ses_password', - default => '', - }, - { - name => 'inbound_proxies', - default => _migrate_param( 'inbound_proxies', '' ), - }, - { - name => 'shadowdb_user', - default => '', - }, - { - name => 'shadowdb_pass', - default => '', - }, - { - name => 'datadog_host', - default => '', - }, - { - name => 'datadog_port', - default => 8125, + {name => 'create_htaccess', default => 1,}, + {name => 'webservergroup', default => \&_sensible_group,}, + {name => 'use_suexec', default => 0,}, + {name => 'db_driver', default => 'mysql',}, + {name => 'db_host', default => 'localhost',}, + {name => 'db_name', default => 'bugs',}, + { + + name => 'db_user', + default => 'bugs', + }, + {name => 'db_pass', default => '',}, + {name => 'db_port', default => 0,}, + {name => 'db_sock', default => '',}, + {name => 'db_check', default => 1,}, + {name => 'index_html', default => 0,}, + {name => 'cvsbin', default => sub { bin_loc('cvs') },}, + {name => 'interdiffbin', default => sub { bin_loc('interdiff') },}, + {name => 'diffpath', default => sub { dirname(bin_loc('diff')) },}, + {name => 'tct_bin', default => sub { bin_loc('tct') },}, + { + name => 'site_wide_secret', + + # 64 characters is roughly the equivalent of a 384-bit key, which + # is larger than anybody would ever be able to brute-force. + default => sub { generate_random_password(64) }, + }, + { + name => 'param_override', + default => { + use_mailer_queue => undef, + mail_delivery_method => undef, + shadowdb => undef, + shadowdbhost => undef, + shadowdbport => undef, + shadowdbsock => undef, }, + }, + {name => 'apache_size_limit', default => 600000,}, + { + name => 'memcached_servers', + default => _migrate_param("memcached_servers", ""), + }, + { + name => 'memcached_namespace', + default => _migrate_param("memcached_namespace", "bugzilla:"), + }, + {name => 'urlbase', default => _migrate_param("urlbase", ""),}, + {name => 'canonical_urlbase', default => '',}, + {name => 'attachment_base', default => _migrate_param("attachment_base", ''),}, + {name => 'ses_username', default => '',}, + {name => 'ses_password', default => '',}, + {name => 'inbound_proxies', default => _migrate_param('inbound_proxies', ''),}, + {name => 'shadowdb_user', default => '',}, + {name => 'shadowdb_pass', default => '',}, + {name => 'datadog_host', default => '',}, + {name => 'datadog_port', default => 8125,}, ); use constant ENV_KEYS => ( - (map { ENV_PREFIX . $_->{name} } LOCALCONFIG_VARS), - (map { ENV_PREFIX . $_ } PARAM_OVERRIDE), + (map { ENV_PREFIX . $_->{name} } LOCALCONFIG_VARS), + (map { ENV_PREFIX . $_ } PARAM_OVERRIDE), ); sub _read_localconfig_from_env { - my %localconfig; - - foreach my $var ( LOCALCONFIG_VARS ) { - my $name = $var->{name}; - my $key = ENV_PREFIX . $name; - if ($name eq 'param_override') { - foreach my $override (PARAM_OVERRIDE) { - my $o_key = ENV_PREFIX . $override; - $localconfig{param_override}{$override} = $ENV{$o_key}; - untaint($localconfig{param_override}{$override}); - } - } - elsif (exists $ENV{$key}) { - $localconfig{$name} = $ENV{$key}; - untaint($localconfig{$name}); - } - else { - my $default = $var->{default}; - $localconfig{$name} = ref($default) eq 'CODE' ? $default->() : $default; - untaint($localconfig{$name}); - } + my %localconfig; + + foreach my $var (LOCALCONFIG_VARS) { + my $name = $var->{name}; + my $key = ENV_PREFIX . $name; + if ($name eq 'param_override') { + foreach my $override (PARAM_OVERRIDE) { + my $o_key = ENV_PREFIX . $override; + $localconfig{param_override}{$override} = $ENV{$o_key}; + untaint($localconfig{param_override}{$override}); + } + } + elsif (exists $ENV{$key}) { + $localconfig{$name} = $ENV{$key}; + untaint($localconfig{$name}); } + else { + my $default = $var->{default}; + $localconfig{$name} = ref($default) eq 'CODE' ? $default->() : $default; + untaint($localconfig{$name}); + } + } - return \%localconfig; + return \%localconfig; } sub _read_localconfig_from_file { - my ($include_deprecated) = @_; - my $filename = bz_locations()->{'localconfig'}; - - my %localconfig; - if (-e $filename) { - my $s = new Safe; - # Some people like to store their database password in another file. - $s->permit('dofile'); - - $s->rdo($filename); - if ($@ || $!) { - my $err_msg = $@ ? $@ : $!; - die install_string('error_localconfig_read', - { error => $err_msg, localconfig => $filename }), "\n"; - } - - my @read_symbols; - if ($include_deprecated) { - # First we have to get the whole symbol table - my $safe_root = $s->root; - my %safe_package; - { no strict 'refs'; %safe_package = %{$safe_root . "::"}; } - # And now we read the contents of every var in the symbol table. - # However: - # * We only include symbols that start with an alphanumeric - # character. This excludes symbols like "_<./localconfig" - # that show up in some perls. - # * We ignore the INC symbol, which exists in every package. - # * Perl 5.10 imports a lot of random symbols that all - # contain "::", and we want to ignore those. - @read_symbols = grep { /^[A-Za-z0-1]/ and !/^INC$/ and !/::/ } - (keys %safe_package); - } - else { - @read_symbols = map($_->{name}, LOCALCONFIG_VARS); - } - foreach my $var (@read_symbols) { - my $glob = $s->varglob($var); - # We can't get the type of a variable out of a Safe automatically. - # We can only get the glob itself. So we figure out its type this - # way, by trying first a scalar, then an array, then a hash. - # - # The interesting thing is that this converts all deprecated - # array or hash vars into hashrefs or arrayrefs, but that's - # fine since as I write this all modern localconfig vars are - # actually scalars. - if (defined $$glob) { - $localconfig{$var} = $$glob; - } - elsif (@$glob) { - $localconfig{$var} = \@$glob; - } - elsif (%$glob) { - $localconfig{$var} = \%$glob; - } - } + my ($include_deprecated) = @_; + my $filename = bz_locations()->{'localconfig'}; + + my %localconfig; + if (-e $filename) { + my $s = new Safe; + + # Some people like to store their database password in another file. + $s->permit('dofile'); + + $s->rdo($filename); + if ($@ || $!) { + my $err_msg = $@ ? $@ : $!; + die install_string( + 'error_localconfig_read', {error => $err_msg, localconfig => $filename} + ), + "\n"; + } + + my @read_symbols; + if ($include_deprecated) { + + # First we have to get the whole symbol table + my $safe_root = $s->root; + my %safe_package; + { no strict 'refs'; %safe_package = %{$safe_root . "::"}; } + + # And now we read the contents of every var in the symbol table. + # However: + # * We only include symbols that start with an alphanumeric + # character. This excludes symbols like "_<./localconfig" + # that show up in some perls. + # * We ignore the INC symbol, which exists in every package. + # * Perl 5.10 imports a lot of random symbols that all + # contain "::", and we want to ignore those. + @read_symbols + = grep { /^[A-Za-z0-1]/ and !/^INC$/ and !/::/ } (keys %safe_package); } + else { + @read_symbols = map($_->{name}, LOCALCONFIG_VARS); + } + foreach my $var (@read_symbols) { + my $glob = $s->varglob($var); + + # We can't get the type of a variable out of a Safe automatically. + # We can only get the glob itself. So we figure out its type this + # way, by trying first a scalar, then an array, then a hash. + # + # The interesting thing is that this converts all deprecated + # array or hash vars into hashrefs or arrayrefs, but that's + # fine since as I write this all modern localconfig vars are + # actually scalars. + if (defined $$glob) { + $localconfig{$var} = $$glob; + } + elsif (@$glob) { + $localconfig{$var} = \@$glob; + } + elsif (%$glob) { + $localconfig{$var} = \%$glob; + } + } + } - return \%localconfig; + return \%localconfig; } sub read_localconfig { - my ($include_deprecated) = @_; - my $config = $ENV{LOCALCONFIG_ENV} - ? _read_localconfig_from_env() - : _read_localconfig_from_file($include_deprecated); + my ($include_deprecated) = @_; + my $config + = $ENV{LOCALCONFIG_ENV} + ? _read_localconfig_from_env() + : _read_localconfig_from_file($include_deprecated); - # Use the site's URL as the default Canonical URL - $config->{canonical_urlbase} //= $config->{urlbase}; + # Use the site's URL as the default Canonical URL + $config->{canonical_urlbase} //= $config->{urlbase}; - return $config; + return $config; } # @@ -333,96 +263,99 @@ sub read_localconfig { # Cute, ey? # sub update_localconfig { - my ($params) = @_; - - if ($ENV{LOCALCONFIG_ENV}) { - require Carp; - Carp::croak("update_localconfig() called with LOCALCONFIG_ENV enabled"); + my ($params) = @_; + + if ($ENV{LOCALCONFIG_ENV}) { + require Carp; + Carp::croak("update_localconfig() called with LOCALCONFIG_ENV enabled"); + } + + my $output = $params->{output} || 0; + my $answer = Bugzilla->installation_answers; + my $localconfig = read_localconfig('include deprecated'); + + my @new_vars; + foreach my $var (LOCALCONFIG_VARS) { + my $name = $var->{name}; + my $value = $localconfig->{$name}; + + # Regenerate site_wide_secret if it was made by our old, weak + # generate_random_password. Previously we used to generate + # a 256-character string for site_wide_secret. + $value = undef + if ($name eq 'site_wide_secret' and defined $value and length($value) == 256); + + if (!defined $value) { + push(@new_vars, $name); + $var->{default} = &{$var->{default}} if ref($var->{default}) eq 'CODE'; + if (exists $answer->{$name}) { + $localconfig->{$name} = $answer->{$name}; + } + else { + $localconfig->{$name} = $var->{default}; + } } + } - my $output = $params->{output} || 0; - my $answer = Bugzilla->installation_answers; - my $localconfig = read_localconfig('include deprecated'); - - my @new_vars; - foreach my $var (LOCALCONFIG_VARS) { - my $name = $var->{name}; - my $value = $localconfig->{$name}; - # Regenerate site_wide_secret if it was made by our old, weak - # generate_random_password. Previously we used to generate - # a 256-character string for site_wide_secret. - $value = undef if ($name eq 'site_wide_secret' and defined $value - and length($value) == 256); - - if (!defined $value) { - push(@new_vars, $name); - $var->{default} = &{$var->{default}} if ref($var->{default}) eq 'CODE'; - if (exists $answer->{$name}) { - $localconfig->{$name} = $answer->{$name}; - } - else { - $localconfig->{$name} = $var->{default}; - } - } - } + if (!$localconfig->{'interdiffbin'} && $output) { + print "\n", install_string('patchutils_missing'), "\n"; + } - if (!$localconfig->{'interdiffbin'} && $output) { - print "\n", install_string('patchutils_missing'), "\n"; - } + my @old_vars; + foreach my $var (keys %$localconfig) { + push(@old_vars, $var) if !grep($_->{name} eq $var, LOCALCONFIG_VARS); + } - my @old_vars; - foreach my $var (keys %$localconfig) { - push(@old_vars, $var) if !grep($_->{name} eq $var, LOCALCONFIG_VARS); - } + my $filename = bz_locations->{'localconfig'}; - my $filename = bz_locations->{'localconfig'}; - - # Ensure output is sorted and deterministic - local $Data::Dumper::Sortkeys = 1; - - # Move any custom or old variables into a separate file. - if (scalar @old_vars) { - my $filename_old = "$filename.old"; - open(my $old_file, ">>:utf8", $filename_old) - or die "$filename_old: $!"; - local $Data::Dumper::Purity = 1; - foreach my $var (@old_vars) { - print $old_file Data::Dumper->Dump([$localconfig->{$var}], - ["*$var"]) . "\n\n"; - } - close $old_file; - my $oldstuff = join(', ', @old_vars); - print install_string('lc_old_vars', - { localconfig => $filename, old_file => $filename_old, - vars => $oldstuff }), "\n"; - } + # Ensure output is sorted and deterministic + local $Data::Dumper::Sortkeys = 1; - # Re-write localconfig - open(my $fh, ">:utf8", $filename) or die "$filename: $!"; - foreach my $var (LOCALCONFIG_VARS) { - my $name = $var->{name}; - my $desc = install_string("localconfig_$name", { root => ROOT_USER }); - chomp($desc); - # Make the description into a comment. - $desc =~ s/^/# /mg; - print $fh $desc, "\n", - Data::Dumper->Dump([$localconfig->{$name}], - ["*$name"]), "\n"; - } - - if (@new_vars) { - my $newstuff = join(', ', @new_vars); - print "\n"; - print colored(install_string('lc_new_vars', { localconfig => $filename, - new_vars => wrap_hard($newstuff, 70) }), - COLOR_ERROR), "\n"; - exit unless $params->{use_defaults}; + # Move any custom or old variables into a separate file. + if (scalar @old_vars) { + my $filename_old = "$filename.old"; + open(my $old_file, ">>:utf8", $filename_old) or die "$filename_old: $!"; + local $Data::Dumper::Purity = 1; + foreach my $var (@old_vars) { + print $old_file Data::Dumper->Dump([$localconfig->{$var}], ["*$var"]) . "\n\n"; } - - # Reset the cache for Bugzilla->localconfig so that it will be re-read - delete Bugzilla->process_cache->{localconfig}; - - return { old_vars => \@old_vars, new_vars => \@new_vars }; + close $old_file; + my $oldstuff = join(', ', @old_vars); + print install_string('lc_old_vars', + {localconfig => $filename, old_file => $filename_old, vars => $oldstuff}), + "\n"; + } + + # Re-write localconfig + open(my $fh, ">:utf8", $filename) or die "$filename: $!"; + foreach my $var (LOCALCONFIG_VARS) { + my $name = $var->{name}; + my $desc = install_string("localconfig_$name", {root => ROOT_USER}); + chomp($desc); + + # Make the description into a comment. + $desc =~ s/^/# /mg; + print $fh $desc, "\n", Data::Dumper->Dump([$localconfig->{$name}], ["*$name"]), + "\n"; + } + + if (@new_vars) { + my $newstuff = join(', ', @new_vars); + print "\n"; + print colored( + install_string( + 'lc_new_vars', {localconfig => $filename, new_vars => wrap_hard($newstuff, 70)} + ), + COLOR_ERROR + ), + "\n"; + exit unless $params->{use_defaults}; + } + + # Reset the cache for Bugzilla->localconfig so that it will be re-read + delete Bugzilla->process_cache->{localconfig}; + + return {old_vars => \@old_vars, new_vars => \@new_vars}; } 1; diff --git a/Bugzilla/Install/Requirements.pm b/Bugzilla/Install/Requirements.pm index beed721f3..4b11d83a1 100644 --- a/Bugzilla/Install/Requirements.pm +++ b/Bugzilla/Install/Requirements.pm @@ -30,14 +30,14 @@ use parent qw(Exporter); use autodie; our @EXPORT = qw( - FEATURE_FILES - - check_cpan_requirements - check_cpan_feature - check_all_cpan_features - check_webdotbase - check_font_file - map_files_to_features + FEATURE_FILES + + check_cpan_requirements + check_cpan_feature + check_all_cpan_features + check_webdotbase + check_font_file + map_files_to_features ); our $checking_for_indent = 0; @@ -52,11 +52,11 @@ use constant TABLE_WIDTH => 71; # The keys are the names of the modules, the values are what the module # is called in the output of "apachectl -t -D DUMP_MODULES". use constant APACHE_MODULES => { - mod_headers => 'headers_module', - mod_env => 'env_module', - mod_expires => 'expires_module', - mod_rewrite => 'rewrite_module', - mod_version => 'version_module' + mod_headers => 'headers_module', + mod_env => 'env_module', + mod_expires => 'expires_module', + mod_rewrite => 'rewrite_module', + mod_version => 'version_module' }; # These are all of the binaries that we could possibly use that can @@ -69,235 +69,255 @@ use constant APACHE => qw(apachectl httpd apache2 apache); # If we don't find any of the above binaries in the normal PATH, # these are extra places we look. -use constant APACHE_PATH => [qw( +use constant APACHE_PATH => [ + qw( /usr/sbin /usr/local/sbin /usr/libexec /usr/local/libexec -)]; + ) +]; # This maps features to the files that require that feature in order # to compile. It is used by t/001compile.t and mod_perl.pl. use constant FEATURE_FILES => ( - jsonrpc => ['Bugzilla/WebService/Server/JSONRPC.pm', 'jsonrpc.cgi'], - xmlrpc => ['Bugzilla/WebService/Server/XMLRPC.pm', 'xmlrpc.cgi', - 'Bugzilla/WebService.pm', 'Bugzilla/WebService/*.pm'], - rest => ['Bugzilla/API/Server.pm', 'rest.cgi', 'Bugzilla/API/*/*.pm', - 'Bugzilla/API/*/Server.pm', 'Bugzilla/API/*/Resource/*.pm'], - csp => ['Bugzilla/CGI/ContentSecurityPolicy.pm'], - psgi => ['app.psgi'], - moving => ['importxml.pl'], - auth_ldap => ['Bugzilla/Auth/Verify/LDAP.pm'], - auth_radius => ['Bugzilla/Auth/Verify/RADIUS.pm'], - documentation => ['docs/makedocs.pl'], - inbound_email => ['email_in.pl'], - jobqueue => ['Bugzilla/Job/*', 'Bugzilla/JobQueue.pm', - 'Bugzilla/JobQueue/*', 'jobqueue.pl'], - patch_viewer => ['Bugzilla/Attachment/PatchReader.pm'], - updates => ['Bugzilla/Update.pm'], - mfa => ['Bugzilla/MFA/*.pm'], - memcached => ['Bugzilla/Memcache.pm'], - s3 => ['Bugzilla/S3.pm', 'Bugzilla/S3/Bucket.pm', 'Bugzilla/Attachment/S3.pm'] + jsonrpc => ['Bugzilla/WebService/Server/JSONRPC.pm', 'jsonrpc.cgi'], + xmlrpc => [ + 'Bugzilla/WebService/Server/XMLRPC.pm', 'xmlrpc.cgi', + 'Bugzilla/WebService.pm', 'Bugzilla/WebService/*.pm' + ], + rest => [ + 'Bugzilla/API/Server.pm', 'rest.cgi', + 'Bugzilla/API/*/*.pm', 'Bugzilla/API/*/Server.pm', + 'Bugzilla/API/*/Resource/*.pm' + ], + csp => ['Bugzilla/CGI/ContentSecurityPolicy.pm'], + psgi => ['app.psgi'], + moving => ['importxml.pl'], + auth_ldap => ['Bugzilla/Auth/Verify/LDAP.pm'], + auth_radius => ['Bugzilla/Auth/Verify/RADIUS.pm'], + documentation => ['docs/makedocs.pl'], + inbound_email => ['email_in.pl'], + jobqueue => [ + 'Bugzilla/Job/*', 'Bugzilla/JobQueue.pm', + 'Bugzilla/JobQueue/*', 'jobqueue.pl' + ], + patch_viewer => ['Bugzilla/Attachment/PatchReader.pm'], + updates => ['Bugzilla/Update.pm'], + mfa => ['Bugzilla/MFA/*.pm'], + memcached => ['Bugzilla/Memcache.pm'], + s3 => ['Bugzilla/S3.pm', 'Bugzilla/S3/Bucket.pm', 'Bugzilla/Attachment/S3.pm'] ); sub check_all_cpan_features { - my ($meta, $dirs, $output) = @_; - my %report; - - local $checking_for_indent = 2; - - print "\nOptional features:\n" if $output; - my @features = sort { $a->identifier cmp $b->identifier } $meta->features; - foreach my $feature (@features) { - next if $feature->identifier eq 'features'; - printf "Feature '%s': %s\n", $feature->identifier, $feature->description if $output; - my $result = check_cpan_feature($feature, $dirs, $output); - print "\n" if $output; - - $report{$feature->identifier} = { - description => $feature->description, - result => $result, - }; - } + my ($meta, $dirs, $output) = @_; + my %report; + + local $checking_for_indent = 2; + + print "\nOptional features:\n" if $output; + my @features = sort { $a->identifier cmp $b->identifier } $meta->features; + foreach my $feature (@features) { + next if $feature->identifier eq 'features'; + printf "Feature '%s': %s\n", $feature->identifier, $feature->description + if $output; + my $result = check_cpan_feature($feature, $dirs, $output); + print "\n" if $output; - return \%report; + $report{$feature->identifier} + = {description => $feature->description, result => $result,}; + } + + return \%report; } sub check_cpan_feature { - my ($feature, $dirs, $output) = @_; + my ($feature, $dirs, $output) = @_; - return _check_prereqs($feature->prereqs, $dirs, $output); + return _check_prereqs($feature->prereqs, $dirs, $output); } sub check_cpan_requirements { - my ($meta, $dirs, $output) = @_; + my ($meta, $dirs, $output) = @_; - my $result = _check_prereqs($meta->effective_prereqs, $dirs, $output); - print colored(install_string('installation_failed'), COLOR_ERROR), "\n" if !$result->{ok} && $output; - return $result; + my $result = _check_prereqs($meta->effective_prereqs, $dirs, $output); + print colored(install_string('installation_failed'), COLOR_ERROR), "\n" + if !$result->{ok} && $output; + return $result; } sub _check_prereqs { - my ($prereqs, $dirs, $output) = @_; - $dirs //= \@INC; - my $reqs = Bugzilla::CPAN->cpan_requirements($prereqs); - my @found; - my @missing; - - foreach my $module (sort $reqs->required_modules) { - my $ok = _check_module($reqs, $module, $dirs, $output); - if ($ok) { - push @found, $module; - } - else { - push @missing, $module; - } + my ($prereqs, $dirs, $output) = @_; + $dirs //= \@INC; + my $reqs = Bugzilla::CPAN->cpan_requirements($prereqs); + my @found; + my @missing; + + foreach my $module (sort $reqs->required_modules) { + my $ok = _check_module($reqs, $module, $dirs, $output); + if ($ok) { + push @found, $module; + } + else { + push @missing, $module; } + } - return { ok => (@missing == 0), found => \@found, missing => \@missing }; + return {ok => (@missing == 0), found => \@found, missing => \@missing}; } sub _check_module { - my ($reqs, $module, $dirs, $output) = @_; - my $required_version = $reqs->requirements_for_module($module); - - if ($module eq 'perl') { - my $ok = $reqs->accepts_module($module, $]); - _checking_for({package => "perl", found => $], wanted => $required_version, ok => $ok}) if $output; - return $ok; - } else { - my $metadata = Module::Metadata->new_from_module($module, inc => $dirs); - my $version = eval { $metadata->version }; - my $ok = $metadata && $version && $reqs->accepts_module($module, $version || 0); - _checking_for({package => $module, $version ? ( found => $version ) : (), wanted => $required_version, ok => $ok}) if $output; - - return $ok; - } + my ($reqs, $module, $dirs, $output) = @_; + my $required_version = $reqs->requirements_for_module($module); + + if ($module eq 'perl') { + my $ok = $reqs->accepts_module($module, $]); + _checking_for( + {package => "perl", found => $], wanted => $required_version, ok => $ok}) + if $output; + return $ok; + } + else { + my $metadata = Module::Metadata->new_from_module($module, inc => $dirs); + my $version = eval { $metadata->version }; + my $ok = $metadata && $version && $reqs->accepts_module($module, $version || 0); + _checking_for({ + package => $module, + $version ? (found => $version) : (), + wanted => $required_version, + ok => $ok + }) + if $output; + + return $ok; + } } sub _get_apachectl { - foreach my $bin_name (APACHE) { - my $bin = bin_loc($bin_name); - return $bin if $bin; - } - # Try again with a possibly different path. - foreach my $bin_name (APACHE) { - my $bin = bin_loc($bin_name, APACHE_PATH); - return $bin if $bin; - } - return undef; + foreach my $bin_name (APACHE) { + my $bin = bin_loc($bin_name); + return $bin if $bin; + } + + # Try again with a possibly different path. + foreach my $bin_name (APACHE) { + my $bin = bin_loc($bin_name, APACHE_PATH); + return $bin if $bin; + } + return undef; } sub check_webdotbase { - my ($output) = @_; + my ($output) = @_; - my $webdotbase = Bugzilla->localconfig->{'webdotbase'}; - return 1 if $webdotbase =~ /^https?:/; + my $webdotbase = Bugzilla->localconfig->{'webdotbase'}; + return 1 if $webdotbase =~ /^https?:/; - my $return; - $return = 1 if -x $webdotbase; + my $return; + $return = 1 if -x $webdotbase; - if ($output) { - _checking_for({ package => 'GraphViz', ok => $return }); - } + if ($output) { + _checking_for({package => 'GraphViz', ok => $return}); + } - if (!$return) { - print install_string('bad_executable', { bin => $webdotbase }), "\n"; - } + if (!$return) { + print install_string('bad_executable', {bin => $webdotbase}), "\n"; + } + + my $webdotdir = bz_locations()->{'webdotdir'}; - my $webdotdir = bz_locations()->{'webdotdir'}; - # Check .htaccess allows access to generated images - if (-e "$webdotdir/.htaccess") { - my $htaccess = new IO::File("$webdotdir/.htaccess", 'r') - || die "$webdotdir/.htaccess: " . $!; - if (!grep(/ \\\.png\$/, $htaccess->getlines)) { - print STDERR install_string('webdot_bad_htaccess', - { dir => $webdotdir }), "\n"; - } - $htaccess->close; + # Check .htaccess allows access to generated images + if (-e "$webdotdir/.htaccess") { + my $htaccess = new IO::File("$webdotdir/.htaccess", 'r') + || die "$webdotdir/.htaccess: " . $!; + if (!grep(/ \\\.png\$/, $htaccess->getlines)) { + print STDERR install_string('webdot_bad_htaccess', {dir => $webdotdir}), "\n"; } + $htaccess->close; + } - return $return; + return $return; } sub check_font_file { - my ($output) = @_; + my ($output) = @_; - my $font_file = Bugzilla->localconfig->{'font_file'}; + my $font_file = Bugzilla->localconfig->{'font_file'}; - my $readable; - $readable = 1 if -r $font_file; + my $readable; + $readable = 1 if -r $font_file; - my $ttf; - $ttf = 1 if $font_file =~ /\.(ttf|otf)$/; + my $ttf; + $ttf = 1 if $font_file =~ /\.(ttf|otf)$/; - if ($output) { - _checking_for({ package => 'Font file', ok => $readable && $ttf}); - } + if ($output) { + _checking_for({package => 'Font file', ok => $readable && $ttf}); + } - if (!$readable) { - print install_string('bad_font_file', { file => $font_file }), "\n"; - } - elsif (!$ttf) { - print install_string('bad_font_file_name', { file => $font_file }), "\n"; - } + if (!$readable) { + print install_string('bad_font_file', {file => $font_file}), "\n"; + } + elsif (!$ttf) { + print install_string('bad_font_file_name', {file => $font_file}), "\n"; + } - return $readable && $ttf; + return $readable && $ttf; } sub _checking_for { - my ($params) = @_; - my ($package, $ok, $wanted, $blacklisted, $found) = - @$params{qw(package ok wanted blacklisted found)}; - - my $ok_string = $ok ? install_string('module_ok') : ''; - - # If we're actually checking versions (like for Perl modules), then - # we have some rather complex logic to determine what we want to - # show. If we're not checking versions (like for GraphViz) we just - # show "ok" or "not found". - if (exists $params->{found}) { - my $found_string; - # We do a string compare in case it's non-numeric. We make sure - # it's not a version object as negative versions are forbidden. - if ($found && !ref($found) && $found eq '-1') { - $found_string = install_string('module_not_found'); - } - elsif ($found) { - $found_string = install_string('module_found', { ver => $found }); - } - else { - $found_string = install_string('module_unknown_version'); - } - $ok_string = $ok ? "$ok_string: $found_string" : $found_string; + my ($params) = @_; + my ($package, $ok, $wanted, $blacklisted, $found) + = @$params{qw(package ok wanted blacklisted found)}; + + my $ok_string = $ok ? install_string('module_ok') : ''; + + # If we're actually checking versions (like for Perl modules), then + # we have some rather complex logic to determine what we want to + # show. If we're not checking versions (like for GraphViz) we just + # show "ok" or "not found". + if (exists $params->{found}) { + my $found_string; + + # We do a string compare in case it's non-numeric. We make sure + # it's not a version object as negative versions are forbidden. + if ($found && !ref($found) && $found eq '-1') { + $found_string = install_string('module_not_found'); } - elsif (!$ok) { - $ok_string = install_string('module_not_found'); + elsif ($found) { + $found_string = install_string('module_found', {ver => $found}); } - - my $black_string = $blacklisted ? install_string('blacklisted') : ''; - my $want_string = $wanted ? "$wanted" : install_string('any'); - - my $str = sprintf "%s %20s %-11s $ok_string $black_string\n", - ( ' ' x $checking_for_indent ) . install_string('checking_for'), - $package, "($want_string)"; - print $ok ? $str : colored($str, COLOR_ERROR); + else { + $found_string = install_string('module_unknown_version'); + } + $ok_string = $ok ? "$ok_string: $found_string" : $found_string; + } + elsif (!$ok) { + $ok_string = install_string('module_not_found'); + } + + my $black_string = $blacklisted ? install_string('blacklisted') : ''; + my $want_string = $wanted ? "$wanted" : install_string('any'); + + my $str = sprintf "%s %20s %-11s $ok_string $black_string\n", + (' ' x $checking_for_indent) . install_string('checking_for'), $package, + "($want_string)"; + print $ok ? $str : colored($str, COLOR_ERROR); } # This does a reverse mapping for FEATURE_FILES. sub map_files_to_features { - my %features = FEATURE_FILES; - my %files; - foreach my $feature (keys %features) { - my @my_files = @{ $features{$feature} }; - foreach my $pattern (@my_files) { - foreach my $file (glob $pattern) { - $files{$file} = $feature; - } - } + my %features = FEATURE_FILES; + my %files; + foreach my $feature (keys %features) { + my @my_files = @{$features{$feature}}; + foreach my $pattern (@my_files) { + foreach my $file (glob $pattern) { + $files{$file} = $feature; + } } - return \%files; + } + return \%files; } 1; diff --git a/Bugzilla/Install/Util.pm b/Bugzilla/Install/Util.pm index b6d28d9c7..a5522e134 100644 --- a/Bugzilla/Install/Util.pm +++ b/Bugzilla/Install/Util.pm @@ -27,367 +27,391 @@ use PerlIO; use base qw(Exporter); our @EXPORT_OK = qw( - bin_loc - get_version_and_os - extension_code_files - i_am_persistent - indicate_progress - install_string - include_languages - success - template_include_path - init_console + bin_loc + get_version_and_os + extension_code_files + i_am_persistent + indicate_progress + install_string + include_languages + success + template_include_path + init_console ); sub bin_loc { - my ($bin, $path) = @_; - # This module is not needed most of the time and is a bit slow, - # so we only load it when calling bin_loc(). - require ExtUtils::MM; - - # If the binary is a full path... - if ($bin =~ m{[/\\]}) { - return MM->maybe_command($bin) || ''; - } - - # Otherwise we look for it in the path in a cross-platform way. - my @path = $path ? @$path : File::Spec->path; - foreach my $dir (@path) { - next if !-d $dir; - my $full_path = File::Spec->catfile($dir, $bin); - # MM is an alias for ExtUtils::MM. maybe_command is nice - # because it checks .com, .bat, .exe (etc.) on Windows. - my $command = MM->maybe_command($full_path); - return $command if $command; - } - - return ''; + my ($bin, $path) = @_; + + # This module is not needed most of the time and is a bit slow, + # so we only load it when calling bin_loc(). + require ExtUtils::MM; + + # If the binary is a full path... + if ($bin =~ m{[/\\]}) { + return MM->maybe_command($bin) || ''; + } + + # Otherwise we look for it in the path in a cross-platform way. + my @path = $path ? @$path : File::Spec->path; + foreach my $dir (@path) { + next if !-d $dir; + my $full_path = File::Spec->catfile($dir, $bin); + + # MM is an alias for ExtUtils::MM. maybe_command is nice + # because it checks .com, .bat, .exe (etc.) on Windows. + my $command = MM->maybe_command($full_path); + return $command if $command; + } + + return ''; } sub get_version_and_os { - # Display version information - my @os_details = POSIX::uname; - # 0 is the name of the OS, 2 is the major version, - my $os_name = $os_details[0] . ' ' . $os_details[2]; - if (ON_WINDOWS) { - require Win32; - $os_name = Win32::GetOSName(); - } - # $os_details[3] is the minor version. - return { bz_ver => BUGZILLA_VERSION, - perl_ver => sprintf('%vd', $^V), - os_name => $os_name, - os_ver => $os_details[3] }; + + # Display version information + my @os_details = POSIX::uname; + + # 0 is the name of the OS, 2 is the major version, + my $os_name = $os_details[0] . ' ' . $os_details[2]; + if (ON_WINDOWS) { + require Win32; + $os_name = Win32::GetOSName(); + } + + # $os_details[3] is the minor version. + return { + bz_ver => BUGZILLA_VERSION, + perl_ver => sprintf('%vd', $^V), + os_name => $os_name, + os_ver => $os_details[3] + }; } sub _extension_paths { - my $dir = bz_locations()->{'extensionsdir'}; - my @extension_items = glob("$dir/*"); - my @paths; - foreach my $item (@extension_items) { - my $basename = basename($item); - # Skip CVS directories and any hidden files/dirs. - next if ($basename eq 'CVS' or $basename =~ /^\./); - if (-d $item) { - if (!-e "$item/disabled") { - push(@paths, $item); - } - } - elsif ($item =~ /\.pm$/i) { - push(@paths, $item); - } - } - return @paths; + my $dir = bz_locations()->{'extensionsdir'}; + my @extension_items = glob("$dir/*"); + my @paths; + foreach my $item (@extension_items) { + my $basename = basename($item); + + # Skip CVS directories and any hidden files/dirs. + next if ($basename eq 'CVS' or $basename =~ /^\./); + if (-d $item) { + if (!-e "$item/disabled") { + push(@paths, $item); + } + } + elsif ($item =~ /\.pm$/i) { + push(@paths, $item); + } + } + return @paths; } sub extension_code_files { - my ($requirements_only) = @_; - my @files; - foreach my $path (_extension_paths()) { - my @load_files; - if (-d $path) { - my $extension_file = "$path/Extension.pm"; - my $config_file = "$path/Config.pm"; - if (-e $extension_file) { - push(@load_files, $extension_file); - } - if (-e $config_file) { - push(@load_files, $config_file); - } - - # Don't load Extension.pm if we just want Config.pm and - # we found both. - if ($requirements_only and scalar(@load_files) == 2) { - shift(@load_files); - } - } - else { - push(@load_files, $path); - } - next if !scalar(@load_files); - # We know that these paths are safe, because they came from - # extensionsdir and we checked them specifically for their format. - # Also, the only thing we ever do with them is pass them to "require". - trick_taint($_) foreach @load_files; - push(@files, \@load_files); + my ($requirements_only) = @_; + my @files; + foreach my $path (_extension_paths()) { + my @load_files; + if (-d $path) { + my $extension_file = "$path/Extension.pm"; + my $config_file = "$path/Config.pm"; + if (-e $extension_file) { + push(@load_files, $extension_file); + } + if (-e $config_file) { + push(@load_files, $config_file); + } + + # Don't load Extension.pm if we just want Config.pm and + # we found both. + if ($requirements_only and scalar(@load_files) == 2) { + shift(@load_files); + } } + else { + push(@load_files, $path); + } + next if !scalar(@load_files); + + # We know that these paths are safe, because they came from + # extensionsdir and we checked them specifically for their format. + # Also, the only thing we ever do with them is pass them to "require". + trick_taint($_) foreach @load_files; + push(@files, \@load_files); + } - return (\@files); + return (\@files); } sub indicate_progress { - my ($params) = @_; - my $current = $params->{current}; - my $total = $params->{total}; - my $every = $params->{every} || 1; - - print "." if !($current % $every); - if ($current == $total || $current % ($every * 60) == 0) { - print "$current/$total (" . int($current * 100 / $total) . "%)\n"; - } + my ($params) = @_; + my $current = $params->{current}; + my $total = $params->{total}; + my $every = $params->{every} || 1; + + print "." if !($current % $every); + if ($current == $total || $current % ($every * 60) == 0) { + print "$current/$total (" . int($current * 100 / $total) . "%)\n"; + } } sub feature_description { - my ($feature_name) = @_; - eval { - my $meta = Bugzilla::CPAN->cpan_meta; + my ($feature_name) = @_; + eval { + my $meta = Bugzilla::CPAN->cpan_meta; - return $meta->feature($feature_name)->description - } or warn $@; + return $meta->feature($feature_name)->description; + } or warn $@; } sub install_string { - my ($string_id, $vars) = @_; - _cache()->{install_string_path} ||= template_include_path(); - my $path = _cache()->{install_string_path}; - - my $string_template; - # Find the first template that defines this string. - foreach my $dir (@$path) { - my $base = "$dir/setup/strings"; - $string_template = _get_string_from_file($string_id, "$base.txt.pl") - if !defined $string_template; - last if defined $string_template; - } - - die "No language defines the string '$string_id'" - if !defined $string_template; - - utf8::decode($string_template) if !utf8::is_utf8($string_template); - - $vars ||= {}; - my @replace_keys = keys %$vars; - foreach my $key (@replace_keys) { - my $replacement = $vars->{$key}; - die "'$key' in '$string_id' is tainted: '$replacement'" - if tainted($replacement); - # We don't want people to start getting clever and inserting - # ##variable## into their values. So we check if any other - # key is listed in the *replacement* string, before doing - # the replacement. This is mostly to protect programmers from - # making mistakes. - if (grep($replacement =~ /##$key##/, @replace_keys)) { - die "Unsafe replacement for '$key' in '$string_id': '$replacement'"; - } - $string_template =~ s/\Q##$key##\E/$replacement/g; - } - - return $string_template; + my ($string_id, $vars) = @_; + _cache()->{install_string_path} ||= template_include_path(); + my $path = _cache()->{install_string_path}; + + my $string_template; + + # Find the first template that defines this string. + foreach my $dir (@$path) { + my $base = "$dir/setup/strings"; + $string_template = _get_string_from_file($string_id, "$base.txt.pl") + if !defined $string_template; + last if defined $string_template; + } + + die "No language defines the string '$string_id'" if !defined $string_template; + + utf8::decode($string_template) if !utf8::is_utf8($string_template); + + $vars ||= {}; + my @replace_keys = keys %$vars; + foreach my $key (@replace_keys) { + my $replacement = $vars->{$key}; + die "'$key' in '$string_id' is tainted: '$replacement'" + if tainted($replacement); + + # We don't want people to start getting clever and inserting + # ##variable## into their values. So we check if any other + # key is listed in the *replacement* string, before doing + # the replacement. This is mostly to protect programmers from + # making mistakes. + if (grep($replacement =~ /##$key##/, @replace_keys)) { + die "Unsafe replacement for '$key' in '$string_id': '$replacement'"; + } + $string_template =~ s/\Q##$key##\E/$replacement/g; + } + + return $string_template; } sub _wanted_languages { - my ($requested, @wanted); - - # Checking SERVER_SOFTWARE is the same as i_am_cgi() in Bugzilla::Util. - if (exists $ENV{'SERVER_SOFTWARE'}) { - my $cgi = eval { Bugzilla->cgi } || eval { require CGI; return CGI->new() }; - $requested = $cgi->http('Accept-Language') || ''; - my $lang = $cgi->cookie('LANG'); - push(@wanted, $lang) if $lang; - } - else { - $requested = get_console_locale(); - } - - push(@wanted, _sort_accept_language($requested)); - return \@wanted; + my ($requested, @wanted); + + # Checking SERVER_SOFTWARE is the same as i_am_cgi() in Bugzilla::Util. + if (exists $ENV{'SERVER_SOFTWARE'}) { + my $cgi = eval { Bugzilla->cgi } || eval { require CGI; return CGI->new() }; + $requested = $cgi->http('Accept-Language') || ''; + my $lang = $cgi->cookie('LANG'); + push(@wanted, $lang) if $lang; + } + else { + $requested = get_console_locale(); + } + + push(@wanted, _sort_accept_language($requested)); + return \@wanted; } sub _wanted_to_actual_languages { - my ($wanted, $supported) = @_; - - my @actual; - foreach my $lang (@$wanted) { - # If we support the language we want, or *any version* of - # the language we want, it gets pushed into @actual. - # - # Per RFC 1766 and RFC 2616, things like 'en' match 'en-us' and - # 'en-uk', but not the other way around. (This is unfortunately - # not very clearly stated in those RFC; see comment just over 14.5 - # in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4) - my @found = grep(/^\Q$lang\E(-.+)?$/i, @$supported); - push(@actual, @found) if @found; - } + my ($wanted, $supported) = @_; - # We always include English at the bottom if it's not there, even if - # it wasn't selected by the user. - if (!grep($_ eq 'en', @actual)) { - push(@actual, 'en'); - } + my @actual; + foreach my $lang (@$wanted) { - return \@actual; + # If we support the language we want, or *any version* of + # the language we want, it gets pushed into @actual. + # + # Per RFC 1766 and RFC 2616, things like 'en' match 'en-us' and + # 'en-uk', but not the other way around. (This is unfortunately + # not very clearly stated in those RFC; see comment just over 14.5 + # in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4) + my @found = grep(/^\Q$lang\E(-.+)?$/i, @$supported); + push(@actual, @found) if @found; + } + + # We always include English at the bottom if it's not there, even if + # it wasn't selected by the user. + if (!grep($_ eq 'en', @actual)) { + push(@actual, 'en'); + } + + return \@actual; } sub supported_languages { - my $cache = _cache(); - return $cache->{supported_languages} if $cache->{supported_languages}; - - my @dirs = glob(bz_locations()->{'templatedir'} . "/*"); - my @languages; - foreach my $dir (@dirs) { - # It's a language directory only if it contains "default" or - # "custom". This auto-excludes CVS directories as well. - next if (!-d "$dir/default" and !-d "$dir/custom"); - my $lang = basename($dir); - # Check for language tag format conforming to RFC 1766. - next unless $lang =~ /^[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?$/; - push(@languages, $lang); - } + my $cache = _cache(); + return $cache->{supported_languages} if $cache->{supported_languages}; + + my @dirs = glob(bz_locations()->{'templatedir'} . "/*"); + my @languages; + foreach my $dir (@dirs) { - $cache->{supported_languages} = \@languages; - return \@languages; + # It's a language directory only if it contains "default" or + # "custom". This auto-excludes CVS directories as well. + next if (!-d "$dir/default" and !-d "$dir/custom"); + my $lang = basename($dir); + + # Check for language tag format conforming to RFC 1766. + next unless $lang =~ /^[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?$/; + push(@languages, $lang); + } + + $cache->{supported_languages} = \@languages; + return \@languages; } sub include_languages { - my ($params) = @_; - - # Basically, the way this works is that we have a list of languages - # that we *want*, and a list of languages that Bugzilla actually - # supports. If there is only one language installed, we take it. - my $supported = supported_languages(); - return @$supported if @$supported == 1; - - my $wanted; - if ($params->{language}) { - # We can pass several languages at once as an arrayref - # or a single language. - $wanted = $params->{language}; - $wanted = [$wanted] unless ref $wanted; - } - else { - $wanted = _wanted_languages(); - } - my $actual = _wanted_to_actual_languages($wanted, $supported); - return @$actual; + my ($params) = @_; + + # Basically, the way this works is that we have a list of languages + # that we *want*, and a list of languages that Bugzilla actually + # supports. If there is only one language installed, we take it. + my $supported = supported_languages(); + return @$supported if @$supported == 1; + + my $wanted; + if ($params->{language}) { + + # We can pass several languages at once as an arrayref + # or a single language. + $wanted = $params->{language}; + $wanted = [$wanted] unless ref $wanted; + } + else { + $wanted = _wanted_languages(); + } + my $actual = _wanted_to_actual_languages($wanted, $supported); + return @$actual; } # Used by template_include_path sub _template_lang_directories { - my ($languages, $templatedir) = @_; - - my @add = qw(custom default); - my $project = bz_locations->{'project'}; - unshift(@add, $project) if $project; - - my @result; - foreach my $lang (@$languages) { - foreach my $dir (@add) { - my $full_dir = "$templatedir/$lang/$dir"; - if (-d $full_dir) { - trick_taint($full_dir); - push(@result, $full_dir); - } - } - } - return @result; + my ($languages, $templatedir) = @_; + + my @add = qw(custom default); + my $project = bz_locations->{'project'}; + unshift(@add, $project) if $project; + + my @result; + foreach my $lang (@$languages) { + foreach my $dir (@add) { + my $full_dir = "$templatedir/$lang/$dir"; + if (-d $full_dir) { + trick_taint($full_dir); + push(@result, $full_dir); + } + } + } + return @result; } # Used by template_include_path. sub _template_base_directories { - my @template_dirs; + my @template_dirs; - foreach my $path (_extension_paths()) { - next if !-d $path; - if ( -d "$path/template") { - push(@template_dirs, "$path/template"); - } + foreach my $path (_extension_paths()) { + next if !-d $path; + if (-d "$path/template") { + push(@template_dirs, "$path/template"); } + } - state $bz_locations = bz_locations(); - push(@template_dirs, $bz_locations->{'templatedir'}); - return \@template_dirs; + state $bz_locations = bz_locations(); + push(@template_dirs, $bz_locations->{'templatedir'}); + return \@template_dirs; } sub template_include_path { - my ($params) = @_; - my @used_languages = include_languages($params); - # Now, we add template directories in the order they will be searched: - my $template_dirs = _template_base_directories(); - - my @include_path; - foreach my $template_dir (@$template_dirs) { - my @lang_dirs = _template_lang_directories(\@used_languages, - $template_dir); - # Hooks get each set of extension directories separately. - if ($params->{hook}) { - push(@include_path, \@lang_dirs); - } - # Whereas everything else just gets a whole INCLUDE_PATH. - else { - push(@include_path, @lang_dirs); - } + my ($params) = @_; + my @used_languages = include_languages($params); + + # Now, we add template directories in the order they will be searched: + my $template_dirs = _template_base_directories(); + + my @include_path; + foreach my $template_dir (@$template_dirs) { + my @lang_dirs = _template_lang_directories(\@used_languages, $template_dir); + + # Hooks get each set of extension directories separately. + if ($params->{hook}) { + push(@include_path, \@lang_dirs); } - return \@include_path; + + # Whereas everything else just gets a whole INCLUDE_PATH. + else { + push(@include_path, @lang_dirs); + } + } + return \@include_path; } # This is taken straight from Sort::Versions 1.5, which is not included # with perl by default. sub vers_cmp { - my ($a, $b) = @_; - - # Remove leading zeroes - Bug 344661 - $a =~ s/^0*(\d.+)/$1/; - $b =~ s/^0*(\d.+)/$1/; - - my @A = ($a =~ /([-.]|\d+|[^-.\d]+)/g); - my @B = ($b =~ /([-.]|\d+|[^-.\d]+)/g); - - my ($A, $B); - while (@A and @B) { - $A = shift @A; - $B = shift @B; - if ($A eq '-' and $B eq '-') { - next; - } elsif ( $A eq '-' ) { - return -1; - } elsif ( $B eq '-') { - return 1; - } elsif ($A eq '.' and $B eq '.') { - next; - } elsif ( $A eq '.' ) { - return -1; - } elsif ( $B eq '.' ) { - return 1; - } elsif ($A =~ /^\d+$/ and $B =~ /^\d+$/) { - if ($A =~ /^0/ || $B =~ /^0/) { - return $A cmp $B if $A cmp $B; - } else { - return $A <=> $B if $A <=> $B; - } - } else { - $A = uc $A; - $B = uc $B; - return $A cmp $B if $A cmp $B; - } + my ($a, $b) = @_; + + # Remove leading zeroes - Bug 344661 + $a =~ s/^0*(\d.+)/$1/; + $b =~ s/^0*(\d.+)/$1/; + + my @A = ($a =~ /([-.]|\d+|[^-.\d]+)/g); + my @B = ($b =~ /([-.]|\d+|[^-.\d]+)/g); + + my ($A, $B); + while (@A and @B) { + $A = shift @A; + $B = shift @B; + if ($A eq '-' and $B eq '-') { + next; + } + elsif ($A eq '-') { + return -1; + } + elsif ($B eq '-') { + return 1; + } + elsif ($A eq '.' and $B eq '.') { + next; + } + elsif ($A eq '.') { + return -1; + } + elsif ($B eq '.') { + return 1; + } + elsif ($A =~ /^\d+$/ and $B =~ /^\d+$/) { + if ($A =~ /^0/ || $B =~ /^0/) { + return $A cmp $B if $A cmp $B; + } + else { + return $A <=> $B if $A <=> $B; + } + } + else { + $A = uc $A; + $B = uc $B; + return $A cmp $B if $A cmp $B; } - @A <=> @B; + } + @A <=> @B; } sub no_checksetup_from_cgi { - print "Content-Type: text/html; charset=UTF-8\r\n\r\n"; - print install_string('no_checksetup_from_cgi'); - exit; + print "Content-Type: text/html; charset=UTF-8\r\n\r\n"; + print install_string('no_checksetup_from_cgi'); + exit; } ###################### @@ -396,160 +420,169 @@ sub no_checksetup_from_cgi { # Used by install_string sub _get_string_from_file { - my ($string_id, $file) = @_; - # If we already loaded the file, then use its copy from the cache. - if (my $strings = _cache()->{strings_from_file}->{$file}) { - return $strings->{$string_id}; - } - - # This module is only needed by checksetup.pl, - # so only load it when needed. - require Safe; - - return undef if !-e $file; - my $safe = new Safe; - $safe->rdo($file); - my %strings = %{$safe->varglob('strings')}; - _cache()->{strings_from_file}->{$file} = \%strings; - return $strings{$string_id}; + my ($string_id, $file) = @_; + + # If we already loaded the file, then use its copy from the cache. + if (my $strings = _cache()->{strings_from_file}->{$file}) { + return $strings->{$string_id}; + } + + # This module is only needed by checksetup.pl, + # so only load it when needed. + require Safe; + + return undef if !-e $file; + my $safe = new Safe; + $safe->rdo($file); + my %strings = %{$safe->varglob('strings')}; + _cache()->{strings_from_file}->{$file} = \%strings; + return $strings{$string_id}; } # Make an ordered list out of a HTTP Accept-Language header (see RFC 2616, 14.4) # We ignore '*' and ;q=0 # For languages with the same priority q the order remains unchanged. sub _sort_accept_language { - sub sortQvalue { $b->{'qvalue'} <=> $a->{'qvalue'} } - my $accept_language = $_[0]; - - # clean up string. - $accept_language =~ s/[^A-Za-z;q=0-9\.\-,]//g; - my @qlanguages; - my @languages; - foreach(split /,/, $accept_language) { - if (m/([A-Za-z\-]+)(?:;q=(\d(?:\.\d+)))?/) { - my $lang = $1; - my $qvalue = $2; - $qvalue = 1 if not defined $qvalue; - next if $qvalue == 0; - $qvalue = 1 if $qvalue > 1; - push(@qlanguages, {'qvalue' => $qvalue, 'language' => $lang}); - } - } - - return map($_->{'language'}, (sort sortQvalue @qlanguages)); + sub sortQvalue { $b->{'qvalue'} <=> $a->{'qvalue'} } + my $accept_language = $_[0]; + + # clean up string. + $accept_language =~ s/[^A-Za-z;q=0-9\.\-,]//g; + my @qlanguages; + my @languages; + foreach (split /,/, $accept_language) { + if (m/([A-Za-z\-]+)(?:;q=(\d(?:\.\d+)))?/) { + my $lang = $1; + my $qvalue = $2; + $qvalue = 1 if not defined $qvalue; + next if $qvalue == 0; + $qvalue = 1 if $qvalue > 1; + push(@qlanguages, {'qvalue' => $qvalue, 'language' => $lang}); + } + } + + return map($_->{'language'}, (sort sortQvalue @qlanguages)); } sub get_console_locale { - require Locale::Language; - my $locale = setlocale(LC_CTYPE); - my $language; - # Some distros set e.g. LC_CTYPE = fr_CH.UTF-8. We clean it up. - if ($locale =~ /^([^\.]+)/) { - $locale = $1; - } - $locale =~ s/_/-/; - # It's pretty sure that there is no language pack of the form fr-CH - # installed, so we also include fr as a wanted language. - if ($locale =~ /^(\S+)\-/) { - $language = $1; - $locale .= ",$language"; - } - else { - $language = $locale; - } - - # Some OSs or distributions may have setlocale return a string of the form - # German_Germany.1252 (this example taken from a Windows XP system), which - # is unsuitable for our needs because Bugzilla works on language codes. - # We try and convert them here. - if ($language = Locale::Language::language2code($language)) { - $locale .= ",$language"; - } - - return $locale; + require Locale::Language; + my $locale = setlocale(LC_CTYPE); + my $language; + + # Some distros set e.g. LC_CTYPE = fr_CH.UTF-8. We clean it up. + if ($locale =~ /^([^\.]+)/) { + $locale = $1; + } + $locale =~ s/_/-/; + + # It's pretty sure that there is no language pack of the form fr-CH + # installed, so we also include fr as a wanted language. + if ($locale =~ /^(\S+)\-/) { + $language = $1; + $locale .= ",$language"; + } + else { + $language = $locale; + } + + # Some OSs or distributions may have setlocale return a string of the form + # German_Germany.1252 (this example taken from a Windows XP system), which + # is unsuitable for our needs because Bugzilla works on language codes. + # We try and convert them here. + if ($language = Locale::Language::language2code($language)) { + $locale .= ",$language"; + } + + return $locale; } sub set_output_encoding { - # If we've already set an encoding layer on STDOUT, don't - # add another one. - my @stdout_layers = PerlIO::get_layers(STDOUT); - return if grep(/^encoding/, @stdout_layers); - - my $encoding; - if (ON_WINDOWS and eval { require Win32::Console }) { - # Although setlocale() works on Windows, it doesn't always return - # the current *console's* encoding. So we use OutputCP here instead, - # when we can. - $encoding = Win32::Console::OutputCP(); - } - else { - my $locale = setlocale(LC_CTYPE); - if ($locale =~ /\.([^\.]+)$/) { - $encoding = $1; - } - } - $encoding = "cp$encoding" if ON_WINDOWS; - $encoding = Encode::resolve_alias($encoding) if $encoding; - if ($encoding and $encoding !~ /utf-8/i) { - binmode STDOUT, ":encoding($encoding)"; - binmode STDERR, ":encoding($encoding)"; - } - else { - binmode STDOUT, ':utf8'; - binmode STDERR, ':utf8'; - } + # If we've already set an encoding layer on STDOUT, don't + # add another one. + my @stdout_layers = PerlIO::get_layers(STDOUT); + return if grep(/^encoding/, @stdout_layers); + + my $encoding; + if (ON_WINDOWS and eval { require Win32::Console }) { + + # Although setlocale() works on Windows, it doesn't always return + # the current *console's* encoding. So we use OutputCP here instead, + # when we can. + $encoding = Win32::Console::OutputCP(); + } + else { + my $locale = setlocale(LC_CTYPE); + if ($locale =~ /\.([^\.]+)$/) { + $encoding = $1; + } + } + $encoding = "cp$encoding" if ON_WINDOWS; + + $encoding = Encode::resolve_alias($encoding) if $encoding; + if ($encoding and $encoding !~ /utf-8/i) { + binmode STDOUT, ":encoding($encoding)"; + binmode STDERR, ":encoding($encoding)"; + } + else { + binmode STDOUT, ':utf8'; + binmode STDERR, ':utf8'; + } } sub init_console { - eval { ON_WINDOWS && require Win32::Console::ANSI; }; - $ENV{'ANSI_COLORS_DISABLED'} = 1 if ($@ || !-t *STDOUT); - $SIG{__DIE__} = \&_console_die; - prevent_windows_dialog_boxes(); - set_output_encoding(); + eval { ON_WINDOWS && require Win32::Console::ANSI; }; + $ENV{'ANSI_COLORS_DISABLED'} = 1 if ($@ || !-t *STDOUT); + $SIG{__DIE__} = \&_console_die; + prevent_windows_dialog_boxes(); + set_output_encoding(); } sub _console_die { - my ($message) = @_; - # $^S means "we are in an eval" - if ($^S) { - die $message; - } - # Remove newlines from the message before we color it, and then - # add them back in on display. Otherwise the ANSI escape code - # for resetting the color comes after the newline, and Perl thinks - # that it should put "at Bugzilla/Install.pm line 1234" after the - # message. - $message =~ s/\n+$//; - # We put quotes around the message to stringify any object exceptions, - # like Template::Exception. - die colored("$message", COLOR_ERROR) . "\n"; + my ($message) = @_; + + # $^S means "we are in an eval" + if ($^S) { + die $message; + } + + # Remove newlines from the message before we color it, and then + # add them back in on display. Otherwise the ANSI escape code + # for resetting the color comes after the newline, and Perl thinks + # that it should put "at Bugzilla/Install.pm line 1234" after the + # message. + $message =~ s/\n+$//; + + # We put quotes around the message to stringify any object exceptions, + # like Template::Exception. + die colored("$message", COLOR_ERROR) . "\n"; } sub success { - my ($message) = @_; - print colored($message, COLOR_SUCCESS), "\n"; + my ($message) = @_; + print colored($message, COLOR_SUCCESS), "\n"; } sub prevent_windows_dialog_boxes { - # This code comes from http://bugs.activestate.com/show_bug.cgi?id=82183 - # and prevents Perl modules from popping up dialog boxes, particularly - # during checksetup (since loading DBD::Oracle during checksetup when - # Oracle isn't installed causes a scary popup and pauses checksetup). - # - # Win32::API ships with ActiveState by default, though there could - # theoretically be a Windows installation without it, I suppose. - if (ON_WINDOWS and eval { require Win32::API }) { - # Call kernel32.SetErrorMode with arguments that mean: - # "The system does not display the critical-error-handler message box. - # Instead, the system sends the error to the calling process." and - # "A child process inherits the error mode of its parent process." - my $SetErrorMode = Win32::API->new('kernel32', 'SetErrorMode', - 'I', 'I'); - my $SEM_FAILCRITICALERRORS = 0x0001; - my $SEM_NOGPFAULTERRORBOX = 0x0002; - $SetErrorMode->Call($SEM_FAILCRITICALERRORS | $SEM_NOGPFAULTERRORBOX); - } + + # This code comes from http://bugs.activestate.com/show_bug.cgi?id=82183 + # and prevents Perl modules from popping up dialog boxes, particularly + # during checksetup (since loading DBD::Oracle during checksetup when + # Oracle isn't installed causes a scary popup and pauses checksetup). + # + # Win32::API ships with ActiveState by default, though there could + # theoretically be a Windows installation without it, I suppose. + if (ON_WINDOWS and eval { require Win32::API }) { + + # Call kernel32.SetErrorMode with arguments that mean: + # "The system does not display the critical-error-handler message box. + # Instead, the system sends the error to the calling process." and + # "A child process inherits the error mode of its parent process." + my $SetErrorMode = Win32::API->new('kernel32', 'SetErrorMode', 'I', 'I'); + my $SEM_FAILCRITICALERRORS = 0x0001; + my $SEM_NOGPFAULTERRORBOX = 0x0002; + $SetErrorMode->Call($SEM_FAILCRITICALERRORS | $SEM_NOGPFAULTERRORBOX); + } } # This is like request_cache, but it's used only by installation code @@ -561,20 +594,20 @@ use constant _cache => {}; ############################## sub trick_taint { - require Carp; - Carp::confess("Undef to trick_taint") unless defined $_[0]; - my $match = $_[0] =~ /^(.*)$/s; - $_[0] = $match ? $1 : undef; - return (defined($_[0])); + require Carp; + Carp::confess("Undef to trick_taint") unless defined $_[0]; + my $match = $_[0] =~ /^(.*)$/s; + $_[0] = $match ? $1 : undef; + return (defined($_[0])); } sub trim { - my ($str) = @_; - if ($str) { - $str =~ s/^\s+//g; - $str =~ s/\s+$//g; - } - return $str; + my ($str) = @_; + if ($str) { + $str =~ s/^\s+//g; + $str =~ s/\s+$//g; + } + return $str; } __END__ -- cgit v1.2.3-24-g4f1b