Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
bugzilla
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
etersoft
bugzilla
Commits
931bd3b6
Commit
931bd3b6
authored
Oct 03, 2008
by
mkanat%bugzilla.org
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Bug 455632: Add Bugzilla::Field::Choice->create and have editvalues.cgi use it
Patch By Max Kanat-Alexander <mkanat@bugzilla.org> r=bbaetz, a=mkanat
parent
7bfd680d
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
231 additions
and
159 deletions
+231
-159
Bug.pm
Bugzilla/Bug.pm
+0
-8
Constants.pm
Bugzilla/Constants.pm
+4
-0
Field.pm
Bugzilla/Field.pm
+2
-2
Choice.pm
Bugzilla/Field/Choice.pm
+148
-89
Status.pm
Bugzilla/Status.pm
+51
-4
editvalues.cgi
editvalues.cgi
+6
-49
code-error.html.tmpl
template/en/default/global/code-error.html.tmpl
+6
-0
user-error.html.tmpl
template/en/default/global/user-error.html.tmpl
+14
-7
No files found.
Bugzilla/Bug.pm
View file @
931bd3b6
...
...
@@ -54,7 +54,6 @@ use base qw(Bugzilla::Object Exporter);
RemoveVotes CheckIfVotedConfirmed
LogActivityEntry
editable_bug_fields
SPECIAL_STATUS_WORKFLOW_ACTIONS
)
;
#####################################################################
...
...
@@ -234,13 +233,6 @@ use constant MAX_LINE_LENGTH => 254;
# Used in _check_comment(). Gives the max length allowed for a comment.
use
constant
MAX_COMMENT_LENGTH
=>
65535
;
use
constant
SPECIAL_STATUS_WORKFLOW_ACTIONS
=>
qw(
none
duplicate
change_resolution
clearresolution
)
;
#####################################################################
sub
new
{
...
...
Bugzilla/Constants.pm
View file @
931bd3b6
...
...
@@ -151,6 +151,7 @@ use File::Basename;
MAX_PRODUCT_SIZE
MAX_MILESTONE_SIZE
MAX_COMPONENT_SIZE
MAX_FIELD_VALUE_SIZE
MAX_FREETEXT_LENGTH
)
;
...
...
@@ -427,6 +428,9 @@ use constant MAX_MILESTONE_SIZE => 20;
# The longest component name allowed.
use
constant
MAX_COMPONENT_SIZE
=>
64
;
# The maximum length for values of <select> fields.
use
constant
MAX_FIELD_VALUE_SIZE
=>
64
;
# Maximum length allowed for free text fields.
use
constant
MAX_FREETEXT_LENGTH
=>
255
;
...
...
Bugzilla/Field.pm
View file @
931bd3b6
...
...
@@ -75,7 +75,6 @@ use base qw(Exporter Bugzilla::Object);
use
Bugzilla::
Constants
;
use
Bugzilla::
Error
;
use
Bugzilla::Field::
Choice
;
use
Bugzilla::
Util
;
###############################
...
...
@@ -376,7 +375,8 @@ sub legal_values {
my
$self
=
shift
;
if
(
!
defined
$self
->
{
'legal_values'
})
{
my
@values
=
Bugzilla::Field::
Choice
->
get_all
({
field
=>
$self
});
require
Bugzilla::Field::
Choice
;
my
@values
=
Bugzilla::Field::
Choice
->
type
(
$self
)
->
get_all
();
$self
->
{
'legal_values'
}
=
\
@values
;
}
return
$self
->
{
'legal_values'
};
...
...
Bugzilla/Field/Choice.pm
View file @
931bd3b6
...
...
@@ -26,6 +26,8 @@ use base qw(Bugzilla::Object);
use
Bugzilla::
Constants
;
use
Bugzilla::
Error
;
use
Bugzilla::
Field
;
use
Bugzilla::
Util
qw(trim detaint_natural)
;
use
Scalar::
Util
qw(blessed)
;
...
...
@@ -42,104 +44,141 @@ use constant DB_COLUMNS => qw(
use
constant
NAME_FIELD
=>
'value'
;
use
constant
LIST_ORDER
=>
'sortkey, value'
;
##########################################
# Constructors and Database Manipulation #
##########################################
use
constant
REQUIRED_CREATE_FIELDS
=>
qw(value)
;
# When calling class methods, we aren't based on just one table,
# so we need some slightly hacky way to do DB_TABLE. We do it by overriding
# class methods and making them specify $_new_table. This is possible
# because we're always called from inside Bugzilla::Field, so it always
# has a $self to pass us which contains info about which field we're
# attached to.
#
# This isn't thread safe, but Bugzilla isn't a threaded application.
our
$_new_table
;
our
$_current_field
;
sub
DB_TABLE
{
my
$invocant
=
shift
;
if
(
blessed
$invocant
)
{
return
$invocant
->
field
->
name
;
use
constant
VALIDATORS
=>
{
value
=>
\&
_check_value
,
sortkey
=>
\&
_check_sortkey
,
};
use
constant
CLASS_MAP
=>
{
bug_status
=>
'Bugzilla::Status'
,
};
#################
# Class Factory #
#################
# Bugzilla::Field::Choice is actually an abstract base class. Every field
# type has its own dynamically-generated class for its values. This allows
# certain fields to have special types, like how bug_status's values
# are Bugzilla::Status objects.
sub
type
{
my
(
$class
,
$field
)
=
@_
;
my
$field_obj
=
blessed
$field
?
$field
:
Bugzilla::
Field
->
check
(
$field
);
my
$field_name
=
$field_obj
->
name
;
if
(
$class
->
CLASS_MAP
->
{
$field_name
})
{
return
$class
->
CLASS_MAP
->
{
$field_name
};
}
return
$_new_table
;
# For generic classes, we use a lowercase class name, so as
# not to interfere with any real subclasses we might make some day.
my
$package
=
"Bugzilla::Field::Choice::$field_name"
;
# We check defined so that the package and the stored field are only
# created once globally (at least per request). We prefix it with
# field_ (instead of suffixing it) so that we don't somehow conflict
# with the names of custom fields.
if
(
!
defined
Bugzilla
->
request_cache
->
{
"field_$package"
})
{
eval
<<EOC;
package $package;
use base qw(Bugzilla::Field::Choice);
use constant DB_TABLE => '$field_name';
EOC
Bugzilla
->
request_cache
->
{
"field_$package"
}
=
$field_obj
;
}
return
$package
;
}
################
# Constructors #
################
# We just make new() enforce this, which should give developers
# the understanding that you can't use Bugzilla::Field::Choice
# without calling type().
sub
new
{
my
$class
=
shift
;
my
(
$params
)
=
@_
;
_check_field_arg
(
$params
);
my
$self
=
$class
->
SUPER::
new
(
$params
);
_fix_return
(
$self
);
return
$self
;
if
(
$class
eq
'Bugzilla::Field::Choice'
)
{
ThrowCodeError
(
'field_choice_must_use_type'
);
}
$class
->
SUPER::
new
(
@_
);
}
sub
new_from_list
{
my
$class
=
shift
;
my
(
$ids
,
$params
)
=
@_
;
_check_field_arg
(
$params
);
my
$list
=
$class
->
SUPER::
new_from_list
(
@_
);
_fix_return
(
$list
);
return
$list
;
}
#########################
# Database Manipulation #
#########################
sub
match
{
# Our subclasses can take more arguments than we normally accept.
# So, we override create() to remove arguments that aren't valid
# columns. (Normally Bugzilla::Object dies if you pass arguments
# that aren't valid columns.)
sub
create
{
my
$class
=
shift
;
my
(
$params
)
=
@_
;
_check_field_arg
(
$params
);
my
$results
=
$class
->
SUPER::
match
(
@_
);
_fix_return
(
$results
);
return
$results
;
foreach
my
$key
(
keys
%
$params
)
{
if
(
!
grep
{
$_
eq
$key
}
$class
->
DB_COLUMNS
)
{
delete
$params
->
{
$key
};
}
}
return
$class
->
SUPER::
create
(
@_
);
}
sub
get_all
{
my
$class
=
shift
;
_check_field_arg
(
@_
);
my
@list
=
$class
->
SUPER::
get_all
(
@_
);
_fix_return
(
\
@list
);
return
@list
;
}
#############
# Accessors #
#############
sub
_check_field_arg
{
my
(
$params
)
=
@_
;
my
(
$class
,
undef
,
undef
,
$func
)
=
caller
(
1
);
if
(
!
defined
$params
->
{
field
})
{
ThrowCodeError
(
'param_required'
,
{
function
=>
"${class}::$func"
,
param
=>
'field'
});
}
elsif
(
!
blessed
$params
->
{
field
})
{
ThrowCodeError
(
'bad_arg'
,
{
function
=>
"${class}::$func"
,
argument
=>
'field'
});
}
$_new_table
=
$params
->
{
field
}
->
name
;
$_current_field
=
$params
->
{
field
};
delete
$params
->
{
field
};
sub
sortkey
{
return
$_
[
0
]
->
{
'sortkey'
};
}
sub
field
{
my
$invocant
=
shift
;
my
$class
=
ref
$invocant
||
$invocant
;
my
$cache
=
Bugzilla
->
request_cache
;
# This is just to make life easier for subclasses. Our auto-generated
# subclasses from type() already have this set.
$cache
->
{
"field_$class"
}
||=
new
Bugzilla::
Field
({
name
=>
$class
->
DB_TABLE
});
return
$cache
->
{
"field_$class"
};
}
sub
_fix_return
{
my
$retval
=
shift
;
if
(
ref
$retval
eq
'ARRAY'
)
{
foreach
my
$obj
(
@$retval
)
{
$obj
->
{
field
}
=
$_current_field
;
}
}
elsif
(
defined
$retval
)
{
$retval
->
{
field
}
=
$_current_field
;
##############
# Validators #
##############
sub
_check_value
{
my
(
$invocant
,
$value
)
=
@_
;
my
$field
=
$invocant
->
field
;
$value
=
trim
(
$value
);
ThrowUserError
(
'fieldvalue_undefined'
)
if
!
defined
$value
||
$value
eq
""
;
ThrowUserError
(
'fieldvalue_name_too_long'
,
{
value
=>
$value
})
if
length
(
$value
)
>
MAX_FIELD_VALUE_SIZE
;
my
$exists
=
$invocant
->
type
(
$field
)
->
new
({
name
=>
$value
});
if
(
$exists
)
{
ThrowUserError
(
'fieldvalue_already_exists'
,
{
field
=>
$field
,
value
=>
$value
});
}
# We do this just to avoid any possible bugs where $_new_table or
# $_current_field are set from a previous call. It also might save
# a little memory under mod_perl by releasing $_current_field explicitly.
undef
$_new_table
;
undef
$_current_field
;
return
$value
;
}
#############
# Accessors #
#############
sub
_check_sortkey
{
my
(
$invocant
,
$value
)
=
@_
;
$value
=
trim
(
$value
);
return
0
if
!
$value
;
# Store for the error message in case detaint_natural clears it.
my
$orig_value
=
$value
;
detaint_natural
(
$value
)
||
ThrowUserError
(
'fieldvalue_sortkey_invalid'
,
{
sortkey
=>
$orig_value
,
field
=>
$invocant
->
field
});
return
$value
;
}
sub
sortkey
{
return
$_
[
0
]
->
{
'sortkey'
};
}
sub
field
{
return
$_
[
0
]
->
{
'field'
};
}
1
;
...
...
@@ -153,27 +192,47 @@ Bugzilla::Field::Choice - A legal value for a <select>-type field.
my $field = new Bugzilla::Field({name => 'bug_status'});
my $choice = new Bugzilla::Field::Choice({ field => $field, id => 1 });
my $choice = new Bugzilla::Field::Choice({ field => $field, name => 'NEW' });
my $choice = new Bugzilla::Field::Choice->type($field)->new(1);
my $choices = Bugzilla::Field::Choice->new_from_list([1,2,3],
{ field => $field});
my $choices = Bugzilla::Field::Choice->get_all({ field => $field });
my $choices = Bugzilla::Field::Choice->match({ sortkey => 10,
field => $field });
my $choices = Bugzilla::Field::Choice->type($field)->new_from_list([1,2,3]);
my $choices = Bugzilla::Field::Choice->type($field)->get_all();
my $choices = Bugzilla::Field::Choice->type($field->match({ sortkey => 10 });
=head1 DESCRIPTION
This is an implementation of L<Bugzilla::Object>, but with a twist.
All the class methods require that you specify an additional C<field>
argument, which is a L<Bugzilla::Field> object that represents the
field whose value this is.
You can't call any class methods (such as C<new>, C<create>, etc.)
directly on C<Bugzilla::Field::Choice> itself. Instead, you have to
call C<Bugzilla::Field::Choice-E<gt>type($field)> to get the class
you're going to instantiate, and then you call the methods on that.
We do that because each field has its own database table for its values, so
each value type needs its own class.
You can look at the L</SYNOPSIS> to see where this extra C<field>
argument goes in each function.
See the L</SYNOPSIS> for examples of how this works.
=head1 METHODS
=head2 Class Factory
In object-oriented design, a "class factory" is a method that picks
and returns the right class for you, based on an argument that you pass.
=over
=item C<type>
Takes a single argument, which is either the name of a field from the
C<fielddefs> table, or a L<Bugzilla::Field> object representing a field.
Returns an appropriate subclass of C<Bugzilla::Field::Choice> that you
can now call class methods on (like C<new>, C<create>, C<match>, etc.)
B<NOTE>: YOU CANNOT CALL CLASS METHODS ON C<Bugzilla::Field::Choice>. You
must call C<type> to get a class you can call methods on.
=back
=head2 Accessors
These are in addition to the standard L<Bugzilla::Object> accessors.
...
...
Bugzilla/Status.pm
View file @
931bd3b6
...
...
@@ -22,13 +22,28 @@ use strict;
package
Bugzilla::
Status
;
use
base
qw(Bugzilla::Object Exporter)
;
@
Bugzilla::Status::
EXPORT
=
qw(BUG_STATE_OPEN is_open_state closed_bug_statuses)
;
use
Bugzilla::
Error
;
use
base
qw(Bugzilla::Field::Choice Exporter)
;
@
Bugzilla::Status::
EXPORT
=
qw(
BUG_STATE_OPEN
SPECIAL_STATUS_WORKFLOW_ACTIONS
is_open_state
closed_bug_statuses
)
;
################################
##### Initialization #####
################################
use
constant
SPECIAL_STATUS_WORKFLOW_ACTIONS
=>
qw(
none
duplicate
change_resolution
clearresolution
)
;
use
constant
DB_TABLE
=>
'bug_status'
;
use
constant
DB_COLUMNS
=>
qw(
...
...
@@ -39,8 +54,24 @@ use constant DB_COLUMNS => qw(
is_open
)
;
use
constant
NAME_FIELD
=>
'value'
;
use
constant
LIST_ORDER
=>
'sortkey, value'
;
sub
VALIDATORS
{
my
$invocant
=
shift
;
my
$validators
=
$invocant
->
SUPER::
VALIDATORS
;
$validators
->
{
is_open
}
=
\&
Bugzilla::Object::
check_boolean
;
$validators
->
{
value
}
=
\&
_check_value
;
return
$validators
;
}
#########################
# Database Manipulation #
#########################
sub
create
{
my
$class
=
shift
;
my
$self
=
$class
->
SUPER::
create
(
@_
);
add_missing_bug_status_transitions
();
return
$self
;
}
###############################
##### Accessors ####
...
...
@@ -51,6 +82,22 @@ sub sortkey { return $_[0]->{'sortkey'}; }
sub
is_active
{
return
$_
[
0
]
->
{
'isactive'
};
}
sub
is_open
{
return
$_
[
0
]
->
{
'is_open'
};
}
##############
# Validators #
##############
sub
_check_value
{
my
$invocant
=
shift
;
my
$value
=
$invocant
->
SUPER::
_check_value
(
@_
);
if
(
grep
{
lc
(
$value
)
eq
lc
(
$_
)
}
SPECIAL_STATUS_WORKFLOW_ACTIONS
)
{
ThrowUserError
(
'fieldvalue_reserved_word'
,
{
field
=>
$invocant
->
field
,
value
=>
$value
});
}
return
$value
;
}
###############################
##### Methods ####
###############################
...
...
editvalues.cgi
View file @
931bd3b6
...
...
@@ -29,7 +29,7 @@ use Bugzilla::Config qw(:admin);
use
Bugzilla::
Token
;
use
Bugzilla::
Field
;
use
Bugzilla::
Bug
;
use
Bugzilla::
Status
;
use
Bugzilla::
Status
qw(SPECIAL_STATUS_WORKFLOW_ACTIONS)
;
# List of different tables that contain the changeable field values
# (the old "enums.") Keep them in alphabetical order by their
...
...
@@ -172,12 +172,8 @@ trick_taint($field);
sub
display_field_values
{
my
$template
=
Bugzilla
->
template
;
my
$field
=
$vars
->
{
'field'
}
->
name
;
my
$fieldvalues
=
Bugzilla
->
dbh
->
selectall_arrayref
(
"SELECT value AS name, sortkey"
.
" FROM $field ORDER BY sortkey, value"
,
{
Slice
=>
{}});
$vars
->
{
'values'
}
=
$
field
values
;
$vars
->
{
'values'
}
=
$
vars
->
{
'field'
}
->
legal_
values
;
$vars
->
{
'default'
}
=
Bugzilla
->
params
->
{
$defaults
{
$field
}}
if
defined
$defaults
{
$field
};
$vars
->
{
'static'
}
=
$static
{
$field
}
if
exists
$static
{
$field
};
...
...
@@ -212,53 +208,14 @@ if ($action eq 'add') {
if
(
$action
eq
'new'
)
{
check_token_data
(
$token
,
'add_field_value'
);
# Cleanups and validity checks
$value
||
ThrowUserError
(
'fieldvalue_undefined'
);
if
(
length
(
$value
)
>
60
)
{
ThrowUserError
(
'fieldvalue_name_too_long'
,
{
'value'
=>
$value
});
}
# Need to store in case detaint_natural() clears the sortkey
my
$stored_sortkey
=
$sortkey
;
if
(
!
detaint_natural
(
$sortkey
))
{
ThrowUserError
(
'fieldvalue_sortkey_invalid'
,
{
'name'
=>
$field
,
'sortkey'
=>
$stored_sortkey
});
}
if
(
ValueExists
(
$field
,
$value
))
{
ThrowUserError
(
'fieldvalue_already_exists'
,
{
'field'
=>
$field_obj
,
'value'
=>
$value
});
}
if
(
$field
eq
'bug_status'
&&
(
grep
{
lc
(
$value
)
eq
$_
}
SPECIAL_STATUS_WORKFLOW_ACTIONS
))
{
$vars
->
{
'value'
}
=
$value
;
ThrowUserError
(
'fieldvalue_reserved_word'
,
$vars
);
}
# Value is only used in a SELECT placeholder and through the HTML filter.
trick_taint
(
$value
);
# Add the new field value.
$dbh
->
do
(
"INSERT INTO $field (value, sortkey) VALUES (?, ?)"
,
undef
,
(
$value
,
$sortkey
));
if
(
$field
eq
'bug_status'
)
{
unless
(
$cgi
->
param
(
'is_open'
))
{
# The bug status is a closed state, but they are open by default.
$dbh
->
do
(
'UPDATE bug_status SET is_open = 0 WHERE value = ?'
,
undef
,
$value
);
}
# Allow the transition from this new bug status to the one used
# by the 'duplicate_or_move_bug_status' parameter.
Bugzilla::Status::
add_missing_bug_status_transitions
();
}
my
$created_value
=
Bugzilla::Field::
Choice
->
type
(
$field_obj
)
->
create
(
{
value
=>
$value
,
sortkey
=>
$sortkey
,
is_open
=>
scalar
$cgi
->
param
(
'is_open'
)
});
delete_token
(
$token
);
$vars
->
{
'message'
}
=
'field_value_created'
;
$vars
->
{
'value'
}
=
$
valu
e
;
$vars
->
{
'value'
}
=
$
created_value
->
nam
e
;
display_field_values
();
}
...
...
template/en/default/global/code-error.html.tmpl
View file @
931bd3b6
...
...
@@ -145,6 +145,12 @@
in the database for '[% username FILTER html %]', but your
account source says that '[% extern_user FILTER html %]' has that ID.
[% ELSIF error == "field_choice_must_use_type" %]
When you call a class method on <code>Bugzilla::Field::Choice</code>,
you must call <code>Bugzilla::Field::Choice->type('some_field')</code>
to generate the right class (you can't call class methods directly
on Bugzilla::Field::Choice).
[% ELSIF error == "field_type_mismatch" %]
Cannot seem to handle <code>[% field FILTER html %]</code>
and <code>[% type FILTER html %]</code> together.
...
...
template/en/default/global/user-error.html.tmpl
View file @
931bd3b6
...
...
@@ -432,7 +432,7 @@
[% ELSIF error == "fieldvalue_already_exists" %]
[% title = "Field Value Already Exists" %]
The value '[% value FILTER html %]' already exists for the
'[%- field.description FILTER html %]'
field.
[%+ field.description FILTER html %]
field.
[% ELSIF error == "fieldvalue_doesnt_exist" %]
[% title = "Specified Field Value Does Not Exist" %]
...
...
@@ -450,8 +450,9 @@
[% ELSIF error == "fieldvalue_name_too_long" %]
[% title = "Field Value Is Too Long" %]
The value of a field is limited to 60 characters.
'[% value FILTER html %]' is too long ([% value.length %] characters).
The value of a field is limited to [% constants.FIELD_VALUE_MAX_SIZE %]
characters. '[% value FILTER html %]' is too long ([% value.length %]
characters).
[% ELSIF error == "fieldvalue_not_editable" %]
[% title = "Field Value Not Editable" %]
...
...
@@ -475,8 +476,9 @@
[% ELSIF error == "fieldvalue_sortkey_invalid" %]
[% title = "Invalid Field Value Sortkey" %]
The sortkey '[% sortkey FILTER html %]' for the '[% name FILTER html %]'
field is not a valid (positive) number.
The sortkey '[% sortkey FILTER html %]' for the
[%+ field.description FILTER html %] field is not a valid
(positive) number.
[% ELSIF error == "fieldvalue_still_has_bugs" %]
[% title = "You Cannot Delete This Field Value" %]
...
...
@@ -1178,13 +1180,13 @@
is less than the minimum allowable value of '[% min_num FILTER html %]'.
[% ELSIF error == "object_name_not_specified" %]
[% type = BLOCK %][%
PROCESS object_name
%][% END %]
[% type = BLOCK %][%
INCLUDE object_name class = class
%][% END %]
[% title = BLOCK %][% type FILTER ucfirst FILTER html %] Not
Specified[% END %]
You must select/enter a [% type FILTER html %].
[% ELSIF error == "object_does_not_exist" %]
[% type = BLOCK %][%
PROCESS object_name
%][% END %]
[% type = BLOCK %][%
INCLUDE object_name class = class
%][% END %]
[% title = BLOCK %]Invalid [% type FILTER ucfirst FILTER html %][% END %]
There is no [% type FILTER html %] named '[% name FILTER html %]'
[% IF product.defined %]
...
...
@@ -1672,5 +1674,10 @@
milestone
[% ELSIF class == "Bugzilla::Status" %]
status
[% ELSIF class == "Bugzilla::Field" %]
field
[% ELSIF ( matches = class.match('^Bugzilla::Field::Choice::(.+)') ) %]
[% SET field_name = matches.0 %]
[% field_descs.$field_name %]
[% END %]
[% END %]
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment