--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::App::Command::move_flag_types; ## no critic (Capitalization)
+use Mojo::Base 'Mojolicious::Command';
+
+use Bugzilla::Constants;
+use Mojo::File 'path';
+use Mojo::Util 'getopt';
+use JSON::MaybeXS;
+
+has description => 'Move flag types';
+has usage => sub { shift->extract_usage };
+
+sub run {
+ my ($self, @args) = @_;
+ my $doit = 0;
+ my ($oldid, $newid, $product, $component, $debug);
+
+ Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
+ getopt \@args,
+ 'old-id|o=s' => \$oldid,
+ 'new-id|n=s' => \$newid,
+ 'product|p=s' => \$product,
+ 'component|c=s' => \$component,
+ 'doit|d' => \$doit,
+ 'debug|D' => \$debug;
+
+ die $self->usage unless $oldid && $newid && $product;
+
+ my $model = Bugzilla->dbh->model;
+
+ my $old_flagtype = $model->resultset('FlagType')->find({id => $oldid})
+ or die "No flagtype $oldid";
+ my $new_flagtype = $model->resultset('FlagType')->find({id => $newid})
+ or die "No flagtype $newid";
+
+ my $bugs = $model->resultset('Bug')->search(_bug_query($product, $component));
+ my $flags = $bugs->search_related('flags', {'flags.type_id' => $oldid});
+ my $count = $flags->count;
+
+ if ($debug) {
+ my $query = ${ $flags->as_query };
+ say STDERR "SQL query:\n", JSON::MaybeXS->new(pretty => 1, canonical => 1)->encode($query);
+ }
+ if ($count) {
+ my $old_name = $old_flagtype->name;
+ my $new_name = $new_flagtype->name;
+ say "Moving '$count' flags from $old_name ($oldid) to $new_name ($newid)...";
+
+ if (!$doit) {
+ say
+ "Pass the argument --doit or -d to permanently make changes to the database.";
+ }
+ else {
+ while (my $flag = $flags->next) {
+ $model->txn_do(sub {
+ $flag->type_id($new_flagtype->id);
+ $flag->update();
+ });
+ say "Bug: ", $flag->bug_id, " Flag: ", $flag->id;
+ }
+ }
+
+ # It's complex to determine which items now need to be flushed from memcached.
+ # As this is expected to be a rare event, we just flush the entire cache.
+ Bugzilla->memcached->clear_all();
+ }
+ else {
+ say "No flags to move";
+ }
+}
+
+sub _bug_query {
+ my ($product, $component) = @_;
+
+ # if we have a component name, search on product and component name
+ if ($component) {
+ return ({'product.name' => $product, 'component.name' => $component},
+ {join => {component => 'product'}});
+ }
+ else {
+ return ({'product.name' => $product}, {join => 'product'});
+ }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::App::Command::move_flag_types - Move currently set flags from one type id to another based
+on product and optionally component.
+
+=head1 SYNOPSIS
+
+ Usage: APPLICATION move_flag_types
+
+ ./bugzilla.pl move_flag_types --old-id 4 --new-id 720 --product Firefox --component Installer
+
+ Options:
+ -h, --help Print a brief help message and exits.
+ -o, --oldid type_id Old flag type id. Use editflagtypes.cgi to determine the type id from the URL.
+ -n, --newid type_id New flag type id. Use editflagtypes.cgi to determine the type id from the URL.
+ -p, --product name The product that the bugs most be assigned to.
+ -c, --component name (Optional) The component of the given product that the bugs must be assigned to.
+ -d, --doit Without this argument, changes are not actually committed to the database.
+ -D, --debug Show the SQL query
+
+=head1 DESCRIPTION
+
+This command will move bugs matching a specific product (and optionally a component)
+from one flag type id to another if the bug has the flag set to either +, -, or ?.
+
+=head1 ATTRIBUTES
+
+L<Bugzilla::App::Command::move_flag_types> inherits all attributes from
+L<Mojolicious::Command> and implements the following new ones.
+
+=head2 description
+
+ my $description = $move_flag_types->description;
+ $move_flag_types = $move_flag_types->description('Foo');
+
+Short description of this command, used for the command list.
+
+=head2 usage
+
+ my $usage = $move_flag_types->usage;
+ $move_flag_types = $move_flag_types->usage('Foo');
+
+Usage information for this command, used for the help screen.
+
+=head1 METHODS
+
+L<Bugzilla::App::Command::move_flag_types> inherits all methods from
+L<Mojolicious::Command> and implements the following new ones.
+
+=head2 run
+
+ $move_flag_types->run(@ARGV);
+
+Run this command.
+++ /dev/null
-#!/usr/bin/env perl
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Initial Developer of the Original Code is Mozilla Foundation
-# Portions created by the Initial Developer are Copyright (C) 2011 the
-# Initial Developer. All Rights Reserved.
-#
-#===============================================================================
-#
-# FILE: move_flag_types.pl
-#
-# USAGE: ./move_flag_types.pl
-#
-# DESCRIPTION: Move current set flag from one type_id to another
-# based on product and optionally component.
-#
-# OPTIONS: ---
-# REQUIREMENTS: ---
-# BUGS: ---
-# NOTES: ---
-# AUTHOR: David Lawrence (:dkl), dkl@mozilla.com
-# COMPANY: Mozilla Foundation
-# VERSION: 1.0
-# CREATED: 08/22/2011 05:18:06 PM
-# REVISION: ---
-#===============================================================================
-
-=head1 NAME
-
-move_flag_types.pl - Move currently set flags from one type id to another based
-on product and optionally component.
-
-=head1 SYNOPSIS
-
-This script will move bugs matching a specific product (and optionally a component)
-from one flag type id to another if the bug has the flag set to either +, -, or ?.
-
-./move_flag_types.pl --old-id 4 --new-id 720 --product Firefox --component Installer
-
-=head1 OPTIONS
-
-=over
-
-=item B<--help|-h|?>
-
-Print a brief help message and exits.
-
-=item B<--oldid|-o>
-
-Old flag type id. Use editflagtypes.cgi to determine the type id from the URL.
-
-=item B<--newid|-n>
-
-New flag type id. Use editflagtypes.cgi to determine the type id from the URL.
-
-=item B<--product|-p>
-
-The product that the bugs most be assigned to.
-
-=item B<--component|-c>
-
-Optional: The component of the given product that the bugs must be assigned to.
-
-=item B<--doit|-d>
-
-Without this argument, changes are not actually committed to the database.
-
-=back
-
-=cut
-
-use strict;
-use warnings;
-use lib qw(. lib local/lib/perl5);
-
-use Bugzilla;
-use Getopt::Long;
-use Pod::Usage;
-
-my %params;
-GetOptions(\%params, 'help|h|?', 'oldid|o=s', 'newid|n=s', 'product|p=s',
- 'component|c:s', 'doit|d')
- or pod2usage(1);
-
-if ( $params{'help'}
- || !$params{'oldid'}
- || !$params{'newid'}
- || !$params{'product'})
-{
- pod2usage({-message => "Missing required argument", -exitval => 1});
-}
-
-# Set defaults
-$params{'doit'} ||= 0;
-$params{'component'} ||= '';
-
-my $dbh = Bugzilla->dbh;
-
-# Get the flag names
-my $old_flag_name
- = $dbh->selectrow_array("SELECT name FROM flagtypes WHERE id = ?",
- undef, $params{'oldid'});
-my $new_flag_name
- = $dbh->selectrow_array("SELECT name FROM flagtypes WHERE id = ?",
- undef, $params{'newid'});
-
-# Find the product id
-my $product_id = $dbh->selectrow_array("SELECT id FROM products WHERE name = ?",
- undef, $params{'product'});
-
-# Find the component id if not __ANY__
-my $component_id;
-if ($params{'component'}) {
- $component_id
- = $dbh->selectrow_array(
- "SELECT id FROM components WHERE name = ? AND product_id = ?",
- undef, $params{'component'}, $product_id);
-}
-
-my @query_args = ($params{'oldid'});
-
-my $flag_query = "SELECT flags.id AS flag_id, flags.bug_id AS bug_id
- FROM flags JOIN bugs ON flags.bug_id = bugs.bug_id
- WHERE flags.type_id = ? ";
-
-if ($component_id) {
-
- # No need to compare against product_id as component_id is already
- # tied to a specific product
- $flag_query .= "AND bugs.component_id = ?";
- push(@query_args, $component_id);
-}
-else {
- # All bugs for a product regardless of component
- $flag_query .= "AND bugs.product_id = ?";
- push(@query_args, $product_id);
-}
-
-my $flags = $dbh->selectall_arrayref($flag_query, undef, @query_args);
-
-if (@$flags) {
- print "Moving '"
- . scalar @$flags
- . "' flags "
- . "from $old_flag_name ("
- . $params{'oldid'} . ") "
- . "to $new_flag_name ("
- . $params{'newid'}
- . ")...\n";
-
- if (!$params{'doit'}) {
- print
- "Pass the argument --doit or -d to permanently make changes to the database.\n";
- }
- else {
- my $flag_update_sth
- = $dbh->prepare("UPDATE flags SET type_id = ? WHERE id = ?");
-
- foreach my $flag (@$flags) {
- my ($flag_id, $bug_id) = @$flag;
- print "Bug: $bug_id Flag: $flag_id\n";
- $flag_update_sth->execute($params{'newid'}, $flag_id);
- }
- }
-
- # It's complex to determine which items now need to be flushed from memcached.
- # As this is expected to be a rare event, we just flush the entire cache.
- Bugzilla->memcached->clear_all();
-}
-else {
- print "No flags to move\n";
-}