summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Object.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/Object.pm')
-rw-r--r--Bugzilla/Object.pm1374
1 files changed, 691 insertions, 683 deletions
diff --git a/Bugzilla/Object.pm b/Bugzilla/Object.pm
index eaafca219..11a6a5895 100644
--- a/Bugzilla/Object.pm
+++ b/Bugzilla/Object.pm
@@ -24,16 +24,17 @@ use constant NAME_FIELD => 'name';
use constant ID_FIELD => 'id';
use constant LIST_ORDER => NAME_FIELD;
-use constant UPDATE_VALIDATORS => {};
-use constant NUMERIC_COLUMNS => ();
-use constant DATE_COLUMNS => ();
+use constant UPDATE_VALIDATORS => {};
+use constant NUMERIC_COLUMNS => ();
+use constant DATE_COLUMNS => ();
use constant VALIDATOR_DEPENDENCIES => {};
+
# XXX At some point, this will be joined with FIELD_MAP.
-use constant REQUIRED_FIELD_MAP => {};
+use constant REQUIRED_FIELD_MAP => {};
use constant EXTRA_REQUIRED_FIELDS => ();
-use constant AUDIT_CREATES => 1;
-use constant AUDIT_UPDATES => 1;
-use constant AUDIT_REMOVES => 1;
+use constant AUDIT_CREATES => 1;
+use constant AUDIT_UPDATES => 1;
+use constant AUDIT_REMOVES => 1;
# When USE_MEMCACHED is true, the class is suitable for serialisation to
# Memcached. See documentation in Bugzilla::Memcached for more information.
@@ -50,54 +51,52 @@ use constant DYNAMIC_COLUMNS => 0;
# This allows the JSON-RPC interface to return Bugzilla::Object instances
# as though they were hashes. In the future, this may be modified to return
# less information.
-sub TO_JSON { return { %{ $_[0] } }; }
+sub TO_JSON { return {%{$_[0]}}; }
###############################
#### Initialization ####
###############################
sub new {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my $param = shift;
-
- my $object = $class->_object_cache_get($param);
- return $object if $object;
-
- my ($data, $set_memcached);
- if (Bugzilla->memcached->enabled
- && $class->USE_MEMCACHED
- && ref($param) eq 'HASH' && $param->{cache})
- {
- if (defined $param->{id}) {
- $data = Bugzilla->memcached->get({
- table => $class->DB_TABLE,
- id => $param->{id},
- });
- }
- elsif (defined $param->{name}) {
- $data = Bugzilla->memcached->get({
- table => $class->DB_TABLE,
- name => $param->{name},
- });
- }
- $set_memcached = $data ? 0 : 1;
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $param = shift;
+
+ my $object = $class->_object_cache_get($param);
+ return $object if $object;
+
+ my ($data, $set_memcached);
+ if ( Bugzilla->memcached->enabled
+ && $class->USE_MEMCACHED
+ && ref($param) eq 'HASH'
+ && $param->{cache})
+ {
+ if (defined $param->{id}) {
+ $data
+ = Bugzilla->memcached->get({table => $class->DB_TABLE, id => $param->{id},});
}
- $data ||= $class->_load_from_db($param);
-
- if ($data && $set_memcached) {
- Bugzilla->memcached->set({
- table => $class->DB_TABLE,
- id => $data->{$class->ID_FIELD},
- name => $data->{$class->NAME_FIELD},
- data => $data,
+ elsif (defined $param->{name}) {
+ $data
+ = Bugzilla->memcached->get({table => $class->DB_TABLE, name => $param->{name},
});
}
-
- $object = $class->new_from_hash($data);
- $class->_object_cache_set($param, $object);
-
- return $object;
+ $set_memcached = $data ? 0 : 1;
+ }
+ $data ||= $class->_load_from_db($param);
+
+ if ($data && $set_memcached) {
+ Bugzilla->memcached->set({
+ table => $class->DB_TABLE,
+ id => $data->{$class->ID_FIELD},
+ name => $data->{$class->NAME_FIELD},
+ data => $data,
+ });
+ }
+
+ $object = $class->new_from_hash($data);
+ $class->_object_cache_set($param, $object);
+
+ return $object;
}
@@ -106,349 +105,346 @@ sub new {
# in Bugzilla::DB::Pg appropriately, to add the right LOWER
# index. You can see examples already there.
sub _load_from_db {
- my $class = shift;
- my ($param) = @_;
- my $dbh = Bugzilla->dbh;
- my $columns = join(',', $class->_get_db_columns);
- my $table = $class->DB_TABLE;
- my $name_field = $class->NAME_FIELD;
- my $id_field = $class->ID_FIELD;
-
- my $id = $param;
- if (ref $param eq 'HASH') {
- $id = $param->{id};
+ my $class = shift;
+ my ($param) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $columns = join(',', $class->_get_db_columns);
+ my $table = $class->DB_TABLE;
+ my $name_field = $class->NAME_FIELD;
+ my $id_field = $class->ID_FIELD;
+
+ my $id = $param;
+ if (ref $param eq 'HASH') {
+ $id = $param->{id};
+ }
+
+ my $object_data;
+ if (defined $id) {
+
+ # We special-case if somebody specifies an ID, so that we can
+ # validate it as numeric.
+ detaint_natural($id)
+ || ThrowCodeError('param_must_be_numeric',
+ {function => $class . '::_load_from_db'});
+
+ # Too large integers make PostgreSQL crash.
+ return if $id > MAX_INT_32;
+
+ $object_data = $dbh->selectrow_hashref(
+ qq{
+ SELECT $columns FROM $table
+ WHERE $id_field = ?}, undef, $id
+ );
+ }
+ else {
+ unless (defined $param->{name}
+ || (defined $param->{'condition'} && defined $param->{'values'}))
+ {
+ ThrowCodeError('bad_arg', {argument => 'param', function => $class . '::new'});
}
- my $object_data;
- if (defined $id) {
- # We special-case if somebody specifies an ID, so that we can
- # validate it as numeric.
- detaint_natural($id)
- || ThrowCodeError('param_must_be_numeric',
- {function => $class . '::_load_from_db'});
-
- # Too large integers make PostgreSQL crash.
- return if $id > MAX_INT_32;
-
- $object_data = $dbh->selectrow_hashref(qq{
- SELECT $columns FROM $table
- WHERE $id_field = ?}, undef, $id);
- } else {
- unless (defined $param->{name} || (defined $param->{'condition'}
- && defined $param->{'values'}))
+ my ($condition, @values);
+ if (defined $param->{name}) {
+ $condition = $dbh->sql_istrcmp($name_field, '?');
+ push(@values, $param->{name});
+ }
+ elsif (defined $param->{'condition'} && defined $param->{'values'}) {
+ caller->isa('Bugzilla::Object') || ThrowCodeError(
+ 'protection_violation',
{
- ThrowCodeError('bad_arg', { argument => 'param',
- function => $class . '::new' });
+ caller => caller,
+ function => $class . '::new',
+ argument => 'condition/values'
}
-
- my ($condition, @values);
- if (defined $param->{name}) {
- $condition = $dbh->sql_istrcmp($name_field, '?');
- push(@values, $param->{name});
- }
- elsif (defined $param->{'condition'} && defined $param->{'values'}) {
- caller->isa('Bugzilla::Object')
- || ThrowCodeError('protection_violation',
- { caller => caller,
- function => $class . '::new',
- argument => 'condition/values' });
- $condition = $param->{'condition'};
- push(@values, @{$param->{'values'}});
- }
-
- map { trick_taint($_) } @values;
- $object_data = $dbh->selectrow_hashref(
- "SELECT $columns FROM $table WHERE $condition", undef, @values);
+ );
+ $condition = $param->{'condition'};
+ push(@values, @{$param->{'values'}});
}
- return $object_data;
+
+ map { trick_taint($_) } @values;
+ $object_data
+ = $dbh->selectrow_hashref("SELECT $columns FROM $table WHERE $condition",
+ undef, @values);
+ }
+ return $object_data;
}
sub new_from_list {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my ($id_list) = @_;
- my $id_field = $class->ID_FIELD;
-
- my @detainted_ids;
- foreach my $id (@$id_list) {
- detaint_natural($id) ||
- ThrowCodeError('param_must_be_numeric',
- {function => $class . '::new_from_list'});
- # Too large integers make PostgreSQL crash.
- next if $id > MAX_INT_32;
- push(@detainted_ids, $id);
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my ($id_list) = @_;
+ my $id_field = $class->ID_FIELD;
+
+ my @detainted_ids;
+ foreach my $id (@$id_list) {
+ detaint_natural($id)
+ || ThrowCodeError('param_must_be_numeric',
+ {function => $class . '::new_from_list'});
+
+ # Too large integers make PostgreSQL crash.
+ next if $id > MAX_INT_32;
+ push(@detainted_ids, $id);
+ }
+
+ # We don't do $invocant->match because some classes have
+ # their own implementation of match which is not compatible
+ # with this one. However, match() still needs to have the right $invocant
+ # in order to do $class->DB_TABLE and so on.
+ my $list = match($invocant, {$id_field => \@detainted_ids});
+
+ # BMO: Populate the object cache with bug objects, which helps
+ # inline-history when viewing bugs with dependencies.
+ if ($class eq 'Bugzilla::Bug') {
+ foreach my $object (@$list) {
+ $class->_object_cache_set({id => $object->id, cache => 1}, $object);
}
+ }
- # We don't do $invocant->match because some classes have
- # their own implementation of match which is not compatible
- # with this one. However, match() still needs to have the right $invocant
- # in order to do $class->DB_TABLE and so on.
- my $list = match($invocant, { $id_field => \@detainted_ids });
-
- # BMO: Populate the object cache with bug objects, which helps
- # inline-history when viewing bugs with dependencies.
- if ($class eq 'Bugzilla::Bug') {
- foreach my $object (@$list) {
- $class->_object_cache_set(
- { id => $object->id, cache => 1 },
- $object
- );
- }
- }
-
- return $list;
+ return $list;
}
sub new_from_hash {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my $object_data = shift || return;
- $class->_serialisation_keys($object_data);
- bless($object_data, $class);
- $object_data->initialize();
- return $object_data;
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $object_data = shift || return;
+ $class->_serialisation_keys($object_data);
+ bless($object_data, $class);
+ $object_data->initialize();
+ return $object_data;
}
sub initialize {
- # abstract
+
+ # abstract
}
# Provides a mechanism for objects to be cached in the request_cahce
sub object_cache_get {
- my ($class, $id) = @_;
- return $class->_object_cache_get(
- { id => $id, cache => 1},
- $class
- );
+ my ($class, $id) = @_;
+ return $class->_object_cache_get({id => $id, cache => 1}, $class);
}
sub object_cache_set {
- my $self = shift;
- return $self->_object_cache_set(
- { id => $self->id, cache => 1 },
- $self
- );
+ my $self = shift;
+ return $self->_object_cache_set({id => $self->id, cache => 1}, $self);
}
sub _object_cache_get {
- my $class = shift;
- my ($param) = @_;
- my $cache_key = $class->object_cache_key($param)
- || return;
- return Bugzilla->request_cache->{$cache_key};
+ my $class = shift;
+ my ($param) = @_;
+ my $cache_key = $class->object_cache_key($param) || return;
+ return Bugzilla->request_cache->{$cache_key};
}
sub _object_cache_set {
- my $class = shift;
- my ($param, $object) = @_;
- my $cache_key = $class->object_cache_key($param)
- || return;
- Bugzilla->request_cache->{$cache_key} = $object;
+ my $class = shift;
+ my ($param, $object) = @_;
+ my $cache_key = $class->object_cache_key($param) || return;
+ Bugzilla->request_cache->{$cache_key} = $object;
}
sub _object_cache_remove {
- my $class = shift;
- my ($param, $object) = @_;
- $param->{cache} = 1;
- my $cache_key = $class->object_cache_key($param)
- || return;
- delete Bugzilla->request_cache->{$cache_key};
+ my $class = shift;
+ my ($param, $object) = @_;
+ $param->{cache} = 1;
+ my $cache_key = $class->object_cache_key($param) || return;
+ delete Bugzilla->request_cache->{$cache_key};
}
sub object_cache_key {
- my $class = shift;
- my ($param) = @_;
- if (ref($param) && $param->{cache} && ($param->{id} || $param->{name})) {
- $class = blessed($class) if blessed($class);
- return $class . ',' . ($param->{id} || $param->{name});
- } else {
- return;
- }
+ my $class = shift;
+ my ($param) = @_;
+ if (ref($param) && $param->{cache} && ($param->{id} || $param->{name})) {
+ $class = blessed($class) if blessed($class);
+ return $class . ',' . ($param->{id} || $param->{name});
+ }
+ else {
+ return;
+ }
}
sub object_cache_clearall {
- my $class = shift;
- my $cache = Bugzilla->request_cache;
- $class = blessed($class) if blessed($class);
- my $prefix = $class . ',';
- foreach my $key (grep { substr($_, 0, length($prefix)) eq $prefix } keys %$cache) {
- delete $cache->{$key};
- }
+ my $class = shift;
+ my $cache = Bugzilla->request_cache;
+ $class = blessed($class) if blessed($class);
+ my $prefix = $class . ',';
+ foreach
+ my $key (grep { substr($_, 0, length($prefix)) eq $prefix } keys %$cache)
+ {
+ delete $cache->{$key};
+ }
}
# To support serialisation, we need to capture the keys in an object's default
# hashref.
sub _serialisation_keys {
- my ($class, $object) = @_;
- my $cache = Bugzilla->request_cache->{serialisation_keys} ||= {};
- $cache->{$class} = [ keys %$object ] if $object && !exists $cache->{$class};
- return @{ $cache->{$class} };
+ my ($class, $object) = @_;
+ my $cache = Bugzilla->request_cache->{serialisation_keys} ||= {};
+ $cache->{$class} = [keys %$object] if $object && !exists $cache->{$class};
+ return @{$cache->{$class}};
}
sub check {
- my ($invocant, $param) = @_;
- my $class = ref($invocant) || $invocant;
- # If we were just passed a name, then just use the name.
- if (!ref $param) {
- $param = { name => $param };
- }
-
- # Don't allow empty names or ids.
- my $check_param = exists $param->{id} ? 'id' : 'name';
- $param->{$check_param} = trim($param->{$check_param});
- # If somebody passes us "0", we want to throw an error like
- # "there is no X with the name 0". This is true even for ids. So here,
- # we only check if the parameter is undefined or empty.
- if (!defined $param->{$check_param} or $param->{$check_param} eq '') {
- ThrowUserError('object_not_specified', { class => $class });
+ my ($invocant, $param) = @_;
+ my $class = ref($invocant) || $invocant;
+
+ # If we were just passed a name, then just use the name.
+ if (!ref $param) {
+ $param = {name => $param};
+ }
+
+ # Don't allow empty names or ids.
+ my $check_param = exists $param->{id} ? 'id' : 'name';
+ $param->{$check_param} = trim($param->{$check_param});
+
+ # If somebody passes us "0", we want to throw an error like
+ # "there is no X with the name 0". This is true even for ids. So here,
+ # we only check if the parameter is undefined or empty.
+ if (!defined $param->{$check_param} or $param->{$check_param} eq '') {
+ ThrowUserError('object_not_specified', {class => $class});
+ }
+
+ my $obj = $class->new($param);
+ if (!$obj) {
+
+ # We don't want to override the normal template "user" object if
+ # "user" is one of the params.
+ delete $param->{user};
+ if (my $error = delete $param->{_error}) {
+ ThrowUserError($error, {%$param, class => $class});
}
-
- my $obj = $class->new($param);
- if (!$obj) {
- # We don't want to override the normal template "user" object if
- # "user" is one of the params.
- delete $param->{user};
- if (my $error = delete $param->{_error}) {
- ThrowUserError($error, { %$param, class => $class });
- }
- else {
- ThrowUserError('object_does_not_exist', { %$param, class => $class });
- }
+ else {
+ ThrowUserError('object_does_not_exist', {%$param, class => $class});
}
- return $obj;
+ }
+ return $obj;
}
# Note: Future extensions to this could be:
# * Add a MATCH_JOIN constant so that we can join against
# certain other tables for the WHERE criteria.
sub match {
- my ($invocant, $criteria) = @_;
- my $class = ref($invocant) || $invocant;
- my $dbh = Bugzilla->dbh;
-
- return [$class->get_all] if !$criteria;
-
- my (@terms, @values, $postamble);
- foreach my $field (keys %$criteria) {
- my $value = $criteria->{$field};
-
- # allow for LIMIT and OFFSET expressions via the criteria.
- next if $field eq 'OFFSET';
- if ( $field eq 'LIMIT' ) {
- next unless defined $value;
- detaint_natural($value)
- or ThrowCodeError('param_must_be_numeric',
- { param => 'LIMIT',
- function => "${class}::match" });
- my $offset;
- if (defined $criteria->{OFFSET}) {
- $offset = $criteria->{OFFSET};
- detaint_signed($offset)
- or ThrowCodeError('param_must_be_numeric',
- { param => 'OFFSET',
- function => "${class}::match" });
- }
- $postamble = $dbh->sql_limit($value, $offset);
- next;
- }
- elsif ( $field eq 'WHERE' ) {
- # the WHERE value is a hashref where the keys are
- # "column_name operator ?" and values are the placeholder's
- # value (either a scalar or an array of values).
- foreach my $k (keys %$value) {
- push(@terms, $k);
- my @this_value = ref($value->{$k}) ? @{ $value->{$k} }
- : ($value->{$k});
- push(@values, @this_value);
- }
- next;
- }
+ my ($invocant, $criteria) = @_;
+ my $class = ref($invocant) || $invocant;
+ my $dbh = Bugzilla->dbh;
+
+ return [$class->get_all] if !$criteria;
+
+ my (@terms, @values, $postamble);
+ foreach my $field (keys %$criteria) {
+ my $value = $criteria->{$field};
+
+ # allow for LIMIT and OFFSET expressions via the criteria.
+ next if $field eq 'OFFSET';
+ if ($field eq 'LIMIT') {
+ next unless defined $value;
+ detaint_natural($value)
+ or ThrowCodeError('param_must_be_numeric',
+ {param => 'LIMIT', function => "${class}::match"});
+ my $offset;
+ if (defined $criteria->{OFFSET}) {
+ $offset = $criteria->{OFFSET};
+ detaint_signed($offset)
+ or ThrowCodeError('param_must_be_numeric',
+ {param => 'OFFSET', function => "${class}::match"});
+ }
+ $postamble = $dbh->sql_limit($value, $offset);
+ next;
+ }
+ elsif ($field eq 'WHERE') {
+
+ # the WHERE value is a hashref where the keys are
+ # "column_name operator ?" and values are the placeholder's
+ # value (either a scalar or an array of values).
+ foreach my $k (keys %$value) {
+ push(@terms, $k);
+ my @this_value = ref($value->{$k}) ? @{$value->{$k}} : ($value->{$k});
+ push(@values, @this_value);
+ }
+ next;
+ }
- # It's always safe to use the field defined by classes as being
- # their ID field. In particular, this means that new_from_list()
- # is exempted from this check.
- $class->_check_field($field, 'match') unless $field eq $class->ID_FIELD;
+ # It's always safe to use the field defined by classes as being
+ # their ID field. In particular, this means that new_from_list()
+ # is exempted from this check.
+ $class->_check_field($field, 'match') unless $field eq $class->ID_FIELD;
- if (ref $value eq 'ARRAY') {
- # IN () is invalid SQL, and if we have an empty list
- # to match against, we're just returning an empty
- # array anyhow.
- return [] if !scalar @$value;
+ if (ref $value eq 'ARRAY') {
- my @qmarks = ("?") x @$value;
- push(@terms, $dbh->sql_in($field, \@qmarks));
- push(@values, @$value);
- }
- elsif ($value eq NOT_NULL) {
- push(@terms, "$field IS NOT NULL");
- }
- elsif ($value eq IS_NULL) {
- push(@terms, "$field IS NULL");
- }
- else {
- push(@terms, "$field = ?");
- push(@values, $value);
- }
+ # IN () is invalid SQL, and if we have an empty list
+ # to match against, we're just returning an empty
+ # array anyhow.
+ return [] if !scalar @$value;
+
+ my @qmarks = ("?") x @$value;
+ push(@terms, $dbh->sql_in($field, \@qmarks));
+ push(@values, @$value);
+ }
+ elsif ($value eq NOT_NULL) {
+ push(@terms, "$field IS NOT NULL");
+ }
+ elsif ($value eq IS_NULL) {
+ push(@terms, "$field IS NULL");
+ }
+ else {
+ push(@terms, "$field = ?");
+ push(@values, $value);
}
+ }
- my $where = join(' AND ', @terms) if scalar @terms;
- return $class->_do_list_select($where, \@values, $postamble);
+ my $where = join(' AND ', @terms) if scalar @terms;
+ return $class->_do_list_select($where, \@values, $postamble);
}
sub _do_list_select {
- my ($class, $where, $values, $postamble) = @_;
- my $table = $class->DB_TABLE;
- my $cols = join(',', $class->_get_db_columns);
- my $order = $class->LIST_ORDER;
-
- # Unconditional requests for configuration data are cacheable.
- my ($objects, $set_memcached, $memcached_key);
- if (!defined $where
- && Bugzilla->memcached->enabled
- && $class->IS_CONFIG)
- {
- $memcached_key = "$class:get_all";
- $objects = Bugzilla->memcached->get_config({ key => $memcached_key });
- $set_memcached = $objects ? 0 : 1;
- }
-
- if (!$objects) {
- my $sql = "SELECT $cols FROM $table";
- if (defined $where) {
- $sql .= " WHERE $where ";
- }
- $sql .= " ORDER BY $order";
- $sql .= " $postamble" if $postamble;
-
- my $dbh = Bugzilla->dbh;
- # Sometimes the values are tainted, but we don't want to untaint them
- # for the caller. So we copy the array. It's safe to untaint because
- # they're only used in placeholders here.
- my @untainted = @{ $values || [] };
- trick_taint($_) foreach @untainted;
- $objects = $dbh->selectall_arrayref($sql, {Slice=>{}}, @untainted);
- $class->_serialisation_keys($objects->[0]) if @$objects;
+ my ($class, $where, $values, $postamble) = @_;
+ my $table = $class->DB_TABLE;
+ my $cols = join(',', $class->_get_db_columns);
+ my $order = $class->LIST_ORDER;
+
+ # Unconditional requests for configuration data are cacheable.
+ my ($objects, $set_memcached, $memcached_key);
+ if (!defined $where && Bugzilla->memcached->enabled && $class->IS_CONFIG) {
+ $memcached_key = "$class:get_all";
+ $objects = Bugzilla->memcached->get_config({key => $memcached_key});
+ $set_memcached = $objects ? 0 : 1;
+ }
+
+ if (!$objects) {
+ my $sql = "SELECT $cols FROM $table";
+ if (defined $where) {
+ $sql .= " WHERE $where ";
}
+ $sql .= " ORDER BY $order";
+ $sql .= " $postamble" if $postamble;
- if ($objects && $set_memcached) {
- Bugzilla->memcached->set_config({
- key => $memcached_key,
- data => $objects
- });
- }
+ my $dbh = Bugzilla->dbh;
- foreach my $object (@$objects) {
- $object = $class->new_from_hash($object);
- }
- return $objects;
+ # Sometimes the values are tainted, but we don't want to untaint them
+ # for the caller. So we copy the array. It's safe to untaint because
+ # they're only used in placeholders here.
+ my @untainted = @{$values || []};
+ trick_taint($_) foreach @untainted;
+ $objects = $dbh->selectall_arrayref($sql, {Slice => {}}, @untainted);
+ $class->_serialisation_keys($objects->[0]) if @$objects;
+ }
+
+ if ($objects && $set_memcached) {
+ Bugzilla->memcached->set_config({key => $memcached_key, data => $objects});
+ }
+
+ foreach my $object (@$objects) {
+ $object = $class->new_from_hash($object);
+ }
+ return $objects;
}
###############################
#### Accessors ######
###############################
-sub id { return $_[0]->{$_[0]->ID_FIELD}; }
+sub id { return $_[0]->{$_[0]->ID_FIELD}; }
sub name { return $_[0]->{$_[0]->NAME_FIELD}; }
###############################
@@ -456,190 +452,200 @@ sub name { return $_[0]->{$_[0]->NAME_FIELD}; }
###############################
sub set {
- my ($self, $field, $value) = @_;
-
- # This method is protected. It's used to help implement set_ functions.
- my $caller = caller;
- $caller->isa('Bugzilla::Object') || $caller->isa('Bugzilla::Extension')
- || ThrowCodeError('protection_violation',
- { caller => caller,
- superclass => __PACKAGE__,
- function => 'Bugzilla::Object->set' });
-
- Bugzilla::Hook::process('object_before_set',
- { object => $self, field => $field,
- value => $value });
-
- my %validators = (%{$self->_get_validators}, %{$self->UPDATE_VALIDATORS});
- if (exists $validators{$field}) {
- my $validator = $validators{$field};
- $value = $self->$validator($value, $field);
- trick_taint($value) if (defined $value && !ref($value));
-
- if ($self->can('_set_global_validator')) {
- $self->_set_global_validator($value, $field);
- }
+ my ($self, $field, $value) = @_;
+
+ # This method is protected. It's used to help implement set_ functions.
+ my $caller = caller;
+ $caller->isa('Bugzilla::Object')
+ || $caller->isa('Bugzilla::Extension')
+ || ThrowCodeError(
+ 'protection_violation',
+ {
+ caller => caller,
+ superclass => __PACKAGE__,
+ function => 'Bugzilla::Object->set'
+ }
+ );
+
+ Bugzilla::Hook::process('object_before_set',
+ {object => $self, field => $field, value => $value});
+
+ my %validators = (%{$self->_get_validators}, %{$self->UPDATE_VALIDATORS});
+ if (exists $validators{$field}) {
+ my $validator = $validators{$field};
+ $value = $self->$validator($value, $field);
+ trick_taint($value) if (defined $value && !ref($value));
+
+ if ($self->can('_set_global_validator')) {
+ $self->_set_global_validator($value, $field);
}
+ }
- $self->{$field} = $value;
+ $self->{$field} = $value;
- Bugzilla::Hook::process('object_end_of_set',
- { object => $self, field => $field });
+ Bugzilla::Hook::process('object_end_of_set',
+ {object => $self, field => $field});
}
sub set_all {
- my ($self, $params) = @_;
-
- # Don't let setters modify the values in $params for the caller.
- my %field_values = %$params;
-
- my @sorted_names = $self->_sort_by_dep(keys %field_values);
-
- foreach my $key (@sorted_names) {
- # It's possible for one set_ method to delete a key from $params
- # for another set method, so if that's happened, we don't call the
- # other set method.
- next if !exists $field_values{$key};
- my $method = "set_$key";
- if (!$self->can($method)) {
- my $class = ref($self) || $self;
- ThrowCodeError("unknown_method", { method => "${class}::${method}" });
- }
- $self->$method($field_values{$key}, \%field_values);
+ my ($self, $params) = @_;
+
+ # Don't let setters modify the values in $params for the caller.
+ my %field_values = %$params;
+
+ my @sorted_names = $self->_sort_by_dep(keys %field_values);
+
+ foreach my $key (@sorted_names) {
+
+ # It's possible for one set_ method to delete a key from $params
+ # for another set method, so if that's happened, we don't call the
+ # other set method.
+ next if !exists $field_values{$key};
+ my $method = "set_$key";
+ if (!$self->can($method)) {
+ my $class = ref($self) || $self;
+ ThrowCodeError("unknown_method", {method => "${class}::${method}"});
}
- Bugzilla::Hook::process('object_end_of_set_all',
- { object => $self, params => \%field_values });
+ $self->$method($field_values{$key}, \%field_values);
+ }
+ Bugzilla::Hook::process('object_end_of_set_all',
+ {object => $self, params => \%field_values});
}
sub update {
- my $self = shift;
-
- my $dbh = Bugzilla->dbh;
- my $table = $self->DB_TABLE;
- my $id_field = $self->ID_FIELD;
-
- $dbh->bz_start_transaction();
-
- my $old_self = $self->new($self->id);
-
- # BMO - allow altering values in a sane way
- Bugzilla::Hook::process('object_start_of_update',
- { object => $self, old_object => $old_self });
-
- my @all_columns = $self->UPDATE_COLUMNS;
- my @hook_columns;
- Bugzilla::Hook::process('object_update_columns',
- { object => $self, columns => \@hook_columns });
- push(@all_columns, @hook_columns);
-
- my %numeric = map { $_ => 1 } $self->NUMERIC_COLUMNS;
- my %date = map { $_ => 1 } $self->DATE_COLUMNS;
- my (@update_columns, @values, %changes);
- foreach my $column (@all_columns) {
- my ($old, $new) = ($old_self->{$column}, $self->{$column});
- # This has to be written this way in order to allow us to set a field
- # from undef or to undef, and avoid warnings about comparing an undef
- # with the "eq" operator.
- if (!defined $new || !defined $old) {
- next if !defined $new && !defined $old;
- }
- elsif ( ($numeric{$column} && $old == $new)
- || ($date{$column} && str2time($old) == str2time($new))
- || $old eq $new ) {
- next;
- }
+ my $self = shift;
- trick_taint($new) if defined $new;
- push(@values, $new);
- push(@update_columns, $column);
- # We don't use $new because we don't want to detaint this for
- # the caller.
- $changes{$column} = [$old, $self->{$column}];
- }
+ my $dbh = Bugzilla->dbh;
+ my $table = $self->DB_TABLE;
+ my $id_field = $self->ID_FIELD;
- my $columns = join(', ', map {"$_ = ?"} @update_columns);
+ $dbh->bz_start_transaction();
- $dbh->do("UPDATE $table SET $columns WHERE $id_field = ?", undef,
- @values, $self->id) if @values;
+ my $old_self = $self->new($self->id);
- Bugzilla::Hook::process('object_end_of_update',
- { object => $self, old_object => $old_self,
- changes => \%changes });
+ # BMO - allow altering values in a sane way
+ Bugzilla::Hook::process('object_start_of_update',
+ {object => $self, old_object => $old_self});
- $self->audit_log(\%changes) if $self->AUDIT_UPDATES;
+ my @all_columns = $self->UPDATE_COLUMNS;
+ my @hook_columns;
+ Bugzilla::Hook::process('object_update_columns',
+ {object => $self, columns => \@hook_columns});
+ push(@all_columns, @hook_columns);
- $dbh->bz_commit_transaction();
- if ($self->USE_MEMCACHED && @values) {
- Bugzilla->memcached->clear({ table => $table, id => $self->id });
- Bugzilla->memcached->clear_config()
- if $self->IS_CONFIG;
- }
- $self->_object_cache_remove({ id => $self->id });
- $self->_object_cache_remove({ name => $self->name }) if $self->name;
+ my %numeric = map { $_ => 1 } $self->NUMERIC_COLUMNS;
+ my %date = map { $_ => 1 } $self->DATE_COLUMNS;
+ my (@update_columns, @values, %changes);
+ foreach my $column (@all_columns) {
+ my ($old, $new) = ($old_self->{$column}, $self->{$column});
- if (wantarray) {
- return (\%changes, $old_self);
+ # This has to be written this way in order to allow us to set a field
+ # from undef or to undef, and avoid warnings about comparing an undef
+ # with the "eq" operator.
+ if (!defined $new || !defined $old) {
+ next if !defined $new && !defined $old;
+ }
+ elsif (($numeric{$column} && $old == $new)
+ || ($date{$column} && str2time($old) == str2time($new))
+ || $old eq $new)
+ {
+ next;
}
- return \%changes;
+ trick_taint($new) if defined $new;
+ push(@values, $new);
+ push(@update_columns, $column);
+
+ # We don't use $new because we don't want to detaint this for
+ # the caller.
+ $changes{$column} = [$old, $self->{$column}];
+ }
+
+ my $columns = join(', ', map {"$_ = ?"} @update_columns);
+
+ $dbh->do("UPDATE $table SET $columns WHERE $id_field = ?",
+ undef, @values, $self->id)
+ if @values;
+
+ Bugzilla::Hook::process('object_end_of_update',
+ {object => $self, old_object => $old_self, changes => \%changes});
+
+ $self->audit_log(\%changes) if $self->AUDIT_UPDATES;
+
+ $dbh->bz_commit_transaction();
+ if ($self->USE_MEMCACHED && @values) {
+ Bugzilla->memcached->clear({table => $table, id => $self->id});
+ Bugzilla->memcached->clear_config() if $self->IS_CONFIG;
+ }
+ $self->_object_cache_remove({id => $self->id});
+ $self->_object_cache_remove({name => $self->name}) if $self->name;
+
+ if (wantarray) {
+ return (\%changes, $old_self);
+ }
+
+ return \%changes;
}
sub remove_from_db {
- my $self = shift;
- Bugzilla::Hook::process('object_before_delete', { object => $self });
- my $table = $self->DB_TABLE;
- my $id_field = $self->ID_FIELD;
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
- $self->audit_log(AUDIT_REMOVE) if $self->AUDIT_REMOVES;
- $dbh->do("DELETE FROM $table WHERE $id_field = ?", undef, $self->id);
- $dbh->bz_commit_transaction();
- if ($self->USE_MEMCACHED) {
- Bugzilla->memcached->clear({ table => $table, id => $self->id });
- Bugzilla->memcached->clear_config()
- if $self->IS_CONFIG;
- }
- $self->_object_cache_remove({ id => $self->id });
- $self->_object_cache_remove({ name => $self->name }) if $self->name;
- undef $self;
+ my $self = shift;
+ Bugzilla::Hook::process('object_before_delete', {object => $self});
+ my $table = $self->DB_TABLE;
+ my $id_field = $self->ID_FIELD;
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ $self->audit_log(AUDIT_REMOVE) if $self->AUDIT_REMOVES;
+ $dbh->do("DELETE FROM $table WHERE $id_field = ?", undef, $self->id);
+ $dbh->bz_commit_transaction();
+
+ if ($self->USE_MEMCACHED) {
+ Bugzilla->memcached->clear({table => $table, id => $self->id});
+ Bugzilla->memcached->clear_config() if $self->IS_CONFIG;
+ }
+ $self->_object_cache_remove({id => $self->id});
+ $self->_object_cache_remove({name => $self->name}) if $self->name;
+ undef $self;
}
sub audit_log {
- my ($self, $changes) = @_;
- my $class = ref $self;
- my $dbh = Bugzilla->dbh;
- my $user_id = Bugzilla->user->id || undef;
- my $sth = $dbh->prepare(
- 'INSERT INTO audit_log (user_id, class, object_id, field,
+ my ($self, $changes) = @_;
+ my $class = ref $self;
+ my $dbh = Bugzilla->dbh;
+ my $user_id = Bugzilla->user->id || undef;
+ my $sth = $dbh->prepare(
+ 'INSERT INTO audit_log (user_id, class, object_id, field,
removed, added, at_time)
- VALUES (?,?,?,?,?,?,LOCALTIMESTAMP(0))');
- # During creation or removal, $changes is actually just a string
- # indicating whether we're creating or removing the object.
- if ($changes eq AUDIT_CREATE or $changes eq AUDIT_REMOVE) {
- # We put the object's name in the "added" or "removed" field.
- # We do this thing with NAME_FIELD because $self->name returns
- # the wrong thing for Bugzilla::User.
- my $name = $self->{$self->NAME_FIELD};
- my @added_removed = $changes eq AUDIT_CREATE ? (undef, $name)
- : ($name, undef);
- $sth->execute($user_id, $class, $self->id, $changes, @added_removed);
- return;
- }
-
- # During update, it's the actual %changes hash produced by update().
- foreach my $field (keys %$changes) {
- # Skip private changes.
- next if $field =~ /^_/;
- my ($from, $to) = @{ $changes->{$field} };
- $sth->execute($user_id, $class, $self->id, $field, $from, $to);
- }
+ VALUES (?,?,?,?,?,?,LOCALTIMESTAMP(0))'
+ );
+
+ # During creation or removal, $changes is actually just a string
+ # indicating whether we're creating or removing the object.
+ if ($changes eq AUDIT_CREATE or $changes eq AUDIT_REMOVE) {
+
+ # We put the object's name in the "added" or "removed" field.
+ # We do this thing with NAME_FIELD because $self->name returns
+ # the wrong thing for Bugzilla::User.
+ my $name = $self->{$self->NAME_FIELD};
+ my @added_removed = $changes eq AUDIT_CREATE ? (undef, $name) : ($name, undef);
+ $sth->execute($user_id, $class, $self->id, $changes, @added_removed);
+ return;
+ }
+
+ # During update, it's the actual %changes hash produced by update().
+ foreach my $field (keys %$changes) {
+
+ # Skip private changes.
+ next if $field =~ /^_/;
+ my ($from, $to) = @{$changes->{$field}};
+ $sth->execute($user_id, $class, $self->id, $field, $from, $to);
+ }
}
sub flatten_to_hash {
- my $self = shift;
- my $class = blessed($self);
- my %hash = map { $_ => $self->{$_} } $class->_serialisation_keys;
- return \%hash;
+ my $self = shift;
+ my $class = blessed($self);
+ my %hash = map { $_ => $self->{$_} } $class->_serialisation_keys;
+ return \%hash;
}
###############################
@@ -647,127 +653,125 @@ sub flatten_to_hash {
###############################
sub any_exist {
- my $class = shift;
- my $table = $class->DB_TABLE;
- my $dbh = Bugzilla->dbh;
- my $any_exist = $dbh->selectrow_array(
- "SELECT 1 FROM $table " . $dbh->sql_limit(1));
- return $any_exist ? 1 : 0;
+ my $class = shift;
+ my $table = $class->DB_TABLE;
+ my $dbh = Bugzilla->dbh;
+ my $any_exist
+ = $dbh->selectrow_array("SELECT 1 FROM $table " . $dbh->sql_limit(1));
+ return $any_exist ? 1 : 0;
}
sub create {
- my ($class, $params) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($class, $params) = @_;
+ my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
- $class->check_required_create_fields($params);
- my $field_values = $class->run_create_validators($params);
- my $object = $class->insert_create_data($field_values);
- $dbh->bz_commit_transaction();
+ $dbh->bz_start_transaction();
+ $class->check_required_create_fields($params);
+ my $field_values = $class->run_create_validators($params);
+ my $object = $class->insert_create_data($field_values);
+ $dbh->bz_commit_transaction();
- if (Bugzilla->memcached->enabled
- && $class->USE_MEMCACHED
- && $class->IS_CONFIG)
- {
- Bugzilla->memcached->clear_config();
- }
+ if (Bugzilla->memcached->enabled && $class->USE_MEMCACHED && $class->IS_CONFIG)
+ {
+ Bugzilla->memcached->clear_config();
+ }
- return $object;
+ return $object;
}
# Used to validate that a field name is in fact a valid column in the
# current table before inserting it into SQL.
sub _check_field {
- my ($invocant, $field, $function) = @_;
- my $class = ref($invocant) || $invocant;
- if (!Bugzilla->dbh->bz_column_info($class->DB_TABLE, $field)) {
- ThrowCodeError('param_invalid', { param => $field,
- function => "${class}::$function" });
- }
+ my ($invocant, $field, $function) = @_;
+ my $class = ref($invocant) || $invocant;
+ if (!Bugzilla->dbh->bz_column_info($class->DB_TABLE, $field)) {
+ ThrowCodeError('param_invalid',
+ {param => $field, function => "${class}::$function"});
+ }
}
sub check_required_create_fields {
- my ($class, $params) = @_;
+ my ($class, $params) = @_;
- # This hook happens here so that even subclasses that don't call
- # SUPER::create are still affected by the hook.
- Bugzilla::Hook::process('object_before_create', { class => $class,
- params => $params });
+ # This hook happens here so that even subclasses that don't call
+ # SUPER::create are still affected by the hook.
+ Bugzilla::Hook::process('object_before_create',
+ {class => $class, params => $params});
- my @check_fields = $class->_required_create_fields();
- foreach my $field (@check_fields) {
- $params->{$field} = undef if !exists $params->{$field};
- }
+ my @check_fields = $class->_required_create_fields();
+ foreach my $field (@check_fields) {
+ $params->{$field} = undef if !exists $params->{$field};
+ }
}
sub run_create_validators {
- my ($class, $params, $options) = @_;
-
- my $validators = $class->_get_validators;
- my %field_values = %$params;
+ my ($class, $params, $options) = @_;
- # Make a hash skiplist for easier searching later
- my %skip_list = map { $_ => 1 } @{ $options->{skip} || [] };
+ my $validators = $class->_get_validators;
+ my %field_values = %$params;
- # Get the sorted field names
- my @sorted_names = $class->_sort_by_dep(keys %field_values);
+ # Make a hash skiplist for easier searching later
+ my %skip_list = map { $_ => 1 } @{$options->{skip} || []};
- # Remove the skipped names
- my @unskipped = grep { !$skip_list{$_} } @sorted_names;
+ # Get the sorted field names
+ my @sorted_names = $class->_sort_by_dep(keys %field_values);
- foreach my $field (@unskipped) {
- my $value;
- if (exists $validators->{$field}) {
- my $validator = $validators->{$field};
- $value = $class->$validator($field_values{$field}, $field,
- \%field_values);
- }
- else {
- $value = $field_values{$field};
- }
+ # Remove the skipped names
+ my @unskipped = grep { !$skip_list{$_} } @sorted_names;
- # We want people to be able to explicitly set fields to NULL,
- # and that means they can be set to undef.
- trick_taint($value) if defined $value && !ref($value);
- $field_values{$field} = $value;
+ foreach my $field (@unskipped) {
+ my $value;
+ if (exists $validators->{$field}) {
+ my $validator = $validators->{$field};
+ $value = $class->$validator($field_values{$field}, $field, \%field_values);
}
-
- Bugzilla::Hook::process('object_end_of_create_validators',
- { class => $class, params => \%field_values });
-
- return \%field_values;
-}
-
-sub insert_create_data {
- my ($class, $field_values) = @_;
- my $dbh = Bugzilla->dbh;
-
- my (@field_names, @values);
- while (my ($field, $value) = each %$field_values) {
- $class->_check_field($field, 'create');
- push(@field_names, $field);
- push(@values, $value);
+ else {
+ $value = $field_values{$field};
}
- my $qmarks = '?,' x @field_names;
- chop($qmarks);
- my $table = $class->DB_TABLE;
- $dbh->do("INSERT INTO $table (" . join(', ', @field_names)
- . ") VALUES ($qmarks)", undef, @values);
- my $id = $dbh->bz_last_key($table, $class->ID_FIELD);
+ # We want people to be able to explicitly set fields to NULL,
+ # and that means they can be set to undef.
+ trick_taint($value) if defined $value && !ref($value);
+ $field_values{$field} = $value;
+ }
- my $object = $class->new($id);
+ Bugzilla::Hook::process('object_end_of_create_validators',
+ {class => $class, params => \%field_values});
- Bugzilla::Hook::process('object_end_of_create', { class => $class,
- object => $object });
- $object->audit_log(AUDIT_CREATE) if $object->AUDIT_CREATES;
+ return \%field_values;
+}
- return $object;
+sub insert_create_data {
+ my ($class, $field_values) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my (@field_names, @values);
+ while (my ($field, $value) = each %$field_values) {
+ $class->_check_field($field, 'create');
+ push(@field_names, $field);
+ push(@values, $value);
+ }
+
+ my $qmarks = '?,' x @field_names;
+ chop($qmarks);
+ my $table = $class->DB_TABLE;
+ $dbh->do(
+ "INSERT INTO $table (" . join(', ', @field_names) . ") VALUES ($qmarks)",
+ undef, @values);
+ my $id = $dbh->bz_last_key($table, $class->ID_FIELD);
+
+ my $object = $class->new($id);
+
+ Bugzilla::Hook::process('object_end_of_create',
+ {class => $class, object => $object});
+ $object->audit_log(AUDIT_CREATE) if $object->AUDIT_CREATES;
+
+ return $object;
}
sub get_all {
- my $class = shift;
- return @{ $class->_do_list_select() };
+ my $class = shift;
+ return @{$class->_do_list_select()};
}
###############################
@@ -777,20 +781,19 @@ sub get_all {
sub check_boolean { return $_[1] ? 1 : 0 }
sub check_time {
- my ($invocant, $value, $field, $params, $allow_negative) = @_;
+ my ($invocant, $value, $field, $params, $allow_negative) = @_;
- # If we don't have a current value default to zero
- my $current = blessed($invocant) ? $invocant->{$field}
- : 0;
- $current ||= 0;
+ # If we don't have a current value default to zero
+ my $current = blessed($invocant) ? $invocant->{$field} : 0;
+ $current ||= 0;
- # Get the new value or zero if it isn't defined
- $value = trim($value) || 0;
+ # Get the new value or zero if it isn't defined
+ $value = trim($value) || 0;
- # Make sure the new value is well formed
- _validate_time($value, $field, $allow_negative);
+ # Make sure the new value is well formed
+ _validate_time($value, $field, $allow_negative);
- return $value;
+ return $value;
}
@@ -799,26 +802,25 @@ sub check_time {
###################
sub _validate_time {
- my ($time, $field, $allow_negative) = @_;
-
- # regexp verifies one or more digits, optionally followed by a period and
- # zero or more digits, OR we have a period followed by one or more digits
- # (allow negatives, though, so people can back out errors in time reporting)
- if ($time !~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/) {
- ThrowUserError("number_not_numeric",
- {field => $field, num => "$time"});
- }
-
- # Callers can optionally allow negative times
- if ( ($time < 0) && !$allow_negative ) {
- ThrowUserError("number_too_small",
- {field => $field, num => "$time", min_num => "0"});
- }
-
- if ($time > 99999.99) {
- ThrowUserError("number_too_large",
- {field => $field, num => "$time", max_num => "99999.99"});
- }
+ my ($time, $field, $allow_negative) = @_;
+
+ # regexp verifies one or more digits, optionally followed by a period and
+ # zero or more digits, OR we have a period followed by one or more digits
+ # (allow negatives, though, so people can back out errors in time reporting)
+ if ($time !~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/) {
+ ThrowUserError("number_not_numeric", {field => $field, num => "$time"});
+ }
+
+ # Callers can optionally allow negative times
+ if (($time < 0) && !$allow_negative) {
+ ThrowUserError("number_too_small",
+ {field => $field, num => "$time", min_num => "0"});
+ }
+
+ if ($time > 99999.99) {
+ ThrowUserError("number_too_large",
+ {field => $field, num => "$time", max_num => "99999.99"});
+ }
}
# Sorts fields according to VALIDATOR_DEPENDENCIES. This is not a
@@ -826,54 +828,55 @@ sub _validate_time {
# *have* to be in the list--it just has to be earlier than its dependent
# if it *is* in the list.
sub _sort_by_dep {
- my ($invocant, @fields) = @_;
-
- my $dependencies = $invocant->VALIDATOR_DEPENDENCIES;
- my ($has_deps, $no_deps) = part { $dependencies->{$_} ? 0 : 1 } @fields;
-
- # For fields with no dependencies, we sort them alphabetically,
- # so that validation always happens in a consistent order.
- # Fields with no dependencies come at the start of the list.
- my @result = sort @{ $no_deps || [] };
-
- # Fields with dependencies all go at the end of the list, and if
- # they have dependencies on *each other*, then they have to be
- # sorted properly. We go through $has_deps in sorted order to be
- # sure that fields always validate in a consistent order.
- foreach my $field (sort @{ $has_deps || [] }) {
- if (!grep { $_ eq $field } @result) {
- _insert_dep_field($field, $has_deps, $dependencies, \@result);
- }
+ my ($invocant, @fields) = @_;
+
+ my $dependencies = $invocant->VALIDATOR_DEPENDENCIES;
+ my ($has_deps, $no_deps) = part { $dependencies->{$_} ? 0 : 1 } @fields;
+
+ # For fields with no dependencies, we sort them alphabetically,
+ # so that validation always happens in a consistent order.
+ # Fields with no dependencies come at the start of the list.
+ my @result = sort @{$no_deps || []};
+
+ # Fields with dependencies all go at the end of the list, and if
+ # they have dependencies on *each other*, then they have to be
+ # sorted properly. We go through $has_deps in sorted order to be
+ # sure that fields always validate in a consistent order.
+ foreach my $field (sort @{$has_deps || []}) {
+ if (!grep { $_ eq $field } @result) {
+ _insert_dep_field($field, $has_deps, $dependencies, \@result);
}
- return @result;
+ }
+ return @result;
}
sub _insert_dep_field {
- my ($field, $insert_me, $dependencies, $result, $loop_tracking) = @_;
+ my ($field, $insert_me, $dependencies, $result, $loop_tracking) = @_;
- if ($loop_tracking->{$field}) {
- ThrowCodeError('object_dep_sort_loop',
- { field => $field,
- considered => [keys %$loop_tracking] });
- }
- $loop_tracking->{$field} = 1;
-
- my $required_fields = $dependencies->{$field};
- # Imagine Field A requires field B...
- foreach my $required_field (@$required_fields) {
- # If our dependency is already satisfied, we're good.
- next if grep { $_ eq $required_field } @$result;
-
- # If our dependency is not in the remaining fields to insert,
- # then we're also OK.
- next if !grep { $_ eq $required_field } @$insert_me;
-
- # So, at this point, we know that Field B is in $insert_me.
- # So let's put the required field into the result.
- _insert_dep_field($required_field, $insert_me, $dependencies,
- $result, $loop_tracking);
- }
- push(@$result, $field);
+ if ($loop_tracking->{$field}) {
+ ThrowCodeError('object_dep_sort_loop',
+ {field => $field, considered => [keys %$loop_tracking]});
+ }
+ $loop_tracking->{$field} = 1;
+
+ my $required_fields = $dependencies->{$field};
+
+ # Imagine Field A requires field B...
+ foreach my $required_field (@$required_fields) {
+
+ # If our dependency is already satisfied, we're good.
+ next if grep { $_ eq $required_field } @$result;
+
+ # If our dependency is not in the remaining fields to insert,
+ # then we're also OK.
+ next if !grep { $_ eq $required_field } @$insert_me;
+
+ # So, at this point, we know that Field B is in $insert_me.
+ # So let's put the required field into the result.
+ _insert_dep_field($required_field, $insert_me, $dependencies, $result,
+ $loop_tracking);
+ }
+ push(@$result, $field);
}
####################
@@ -886,67 +889,72 @@ sub _insert_dep_field {
# page.
sub _get_db_columns {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my $cache = Bugzilla->request_cache;
- my $cache_key = "object_${class}_db_columns";
- return @{ $cache->{$cache_key} } if $cache->{$cache_key};
- my @columns;
- if ($class->DYNAMIC_COLUMNS) {
- @columns = Bugzilla->dbh->bz_table_columns_real($class->DB_TABLE);
- }
- else {
- # Currently you can only add new columns using object_columns, not
- # remove or modify existing columns, because removing columns would
- # almost certainly cause Bugzilla to function improperly.
- my @add_columns;
- Bugzilla::Hook::process('object_columns',
- { class => $class, columns => \@add_columns });
- @columns = ($invocant->DB_COLUMNS, @add_columns);
- }
- $cache->{$cache_key} = \@columns;
- return @{ $cache->{$cache_key} };
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $cache = Bugzilla->request_cache;
+ my $cache_key = "object_${class}_db_columns";
+ return @{$cache->{$cache_key}} if $cache->{$cache_key};
+ my @columns;
+ if ($class->DYNAMIC_COLUMNS) {
+ @columns = Bugzilla->dbh->bz_table_columns_real($class->DB_TABLE);
+ }
+ else {
+ # Currently you can only add new columns using object_columns, not
+ # remove or modify existing columns, because removing columns would
+ # almost certainly cause Bugzilla to function improperly.
+ my @add_columns;
+ Bugzilla::Hook::process('object_columns',
+ {class => $class, columns => \@add_columns});
+ @columns = ($invocant->DB_COLUMNS, @add_columns);
+ }
+ $cache->{$cache_key} = \@columns;
+ return @{$cache->{$cache_key}};
}
# This method is private and should only be called by Bugzilla::Object.
sub _get_validators {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my $cache = Bugzilla->request_cache;
- my $cache_key = "object_${class}_validators";
- return $cache->{$cache_key} if $cache->{$cache_key};
- # We copy this into a hash so that the hook doesn't modify the constant.
- # (That could be bad in mod_perl.)
- my %validators = %{ $invocant->VALIDATORS };
- Bugzilla::Hook::process('object_validators',
- { class => $class, validators => \%validators });
- $cache->{$cache_key} = \%validators;
- return $cache->{$cache_key};
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $cache = Bugzilla->request_cache;
+ my $cache_key = "object_${class}_validators";
+ return $cache->{$cache_key} if $cache->{$cache_key};
+
+ # We copy this into a hash so that the hook doesn't modify the constant.
+ # (That could be bad in mod_perl.)
+ my %validators = %{$invocant->VALIDATORS};
+ Bugzilla::Hook::process('object_validators',
+ {class => $class, validators => \%validators});
+ $cache->{$cache_key} = \%validators;
+ return $cache->{$cache_key};
}
# These are all the fields that need to be checked, always, when
# calling create(), because they have no DEFAULT and they are marked
# NOT NULL.
sub _required_create_fields {
- my $class = shift;
- my $dbh = Bugzilla->dbh;
- my $table = $class->DB_TABLE;
-
- my @columns = $dbh->bz_table_columns($table);
- my @required;
- foreach my $column (@columns) {
- my $def = $dbh->bz_column_info($table, $column);
- if ($def->{NOTNULL} and !defined $def->{DEFAULT}
- # SERIAL fields effectively have a DEFAULT, but they're not
- # listed as having a DEFAULT in DB::Schema.
- and $def->{TYPE} !~ /serial/i)
- {
- my $field = $class->REQUIRED_FIELD_MAP->{$column} || $column;
- push(@required, $field);
- }
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
+ my $table = $class->DB_TABLE;
+
+ my @columns = $dbh->bz_table_columns($table);
+ my @required;
+ foreach my $column (@columns) {
+ my $def = $dbh->bz_column_info($table, $column);
+ if (
+ $def->{NOTNULL}
+ and !defined $def->{DEFAULT}
+
+ # SERIAL fields effectively have a DEFAULT, but they're not
+ # listed as having a DEFAULT in DB::Schema.
+ and $def->{TYPE} !~ /serial/i
+ )
+ {
+ my $field = $class->REQUIRED_FIELD_MAP->{$column} || $column;
+ push(@required, $field);
}
- push(@required, $class->EXTRA_REQUIRED_FIELDS);
- return @required;
+ }
+ push(@required, $class->EXTRA_REQUIRED_FIELDS);
+ return @required;
}
1;