line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package SPOPSx::Ginsu; |
2
|
|
|
|
|
|
|
|
3
|
7
|
|
|
7
|
|
736180
|
use strict; |
|
7
|
|
|
|
|
16
|
|
|
7
|
|
|
|
|
336
|
|
4
|
7
|
|
|
7
|
|
42
|
use vars qw($VERSION $Revision); |
|
7
|
|
|
|
|
14
|
|
|
7
|
|
|
|
|
642
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
BEGIN { |
7
|
7
|
|
|
7
|
|
68
|
$Revision = sprintf "%d.%03d", q$Revision: 1.60 $ =~ /: (\d+)\.(\d+)/; |
8
|
7
|
|
|
|
|
176
|
$VERSION = '0.58'; |
9
|
|
|
|
|
|
|
} |
10
|
|
|
|
|
|
|
|
11
|
7
|
|
|
7
|
|
39
|
use base qw( SPOPSx::Ginsu::DBI ); |
|
7
|
|
|
|
|
16
|
|
|
7
|
|
|
|
|
4763
|
|
12
|
|
|
|
|
|
|
use SPOPSx::Ginsu::DBI; |
13
|
|
|
|
|
|
|
use SPOPS::ClassFactory; |
14
|
|
|
|
|
|
|
use SPOPS::DBI; |
15
|
|
|
|
|
|
|
use DBI qw( :sql_types ); |
16
|
|
|
|
|
|
|
use Log::Log4perl qw( get_logger ); |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
my $log = get_logger(); |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
sub ROOT_OBJ_CLASS { die "Must be overridden by a root base class."; } |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
sub e_has_a { return { }; } |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
##----- Public Class Methods ----- |
25
|
|
|
|
|
|
|
sub new { |
26
|
|
|
|
|
|
|
my $class = shift; |
27
|
|
|
|
|
|
|
my $p = shift; |
28
|
|
|
|
|
|
|
# my $self = $class->SUPER::new($p); |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
## Since SUPER::new($p) ignores keys in $p that are defined as fields |
31
|
|
|
|
|
|
|
## in the CONFIG of a parent object, we have to do the assigning of |
32
|
|
|
|
|
|
|
## these parameters manually (or fix SPOPS to handle this internally). |
33
|
|
|
|
|
|
|
my $self = $class->SUPER::new; |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
foreach my $field ( @{$class->all_fields} ) { |
36
|
|
|
|
|
|
|
$self->{$field} = defined $p->{$field} ? $p->{$field} : undef; |
37
|
|
|
|
|
|
|
} |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
$self->{class} = ref($self); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
return $self; |
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
sub isa_classes { |
45
|
|
|
|
|
|
|
my $self = shift; |
46
|
|
|
|
|
|
|
my $isa = $self->_isa_classes; |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
return [ sort { $isa->{$a} <=> $isa->{$b} } keys %$isa ]; |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
sub inherited_fields { |
52
|
|
|
|
|
|
|
my $class = shift; |
53
|
|
|
|
|
|
|
$class = ref($class) if ref($class); ## get class if passed an object |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
my $fields = []; |
56
|
|
|
|
|
|
|
foreach my $c ( @{$class->isa_classes} ) { |
57
|
|
|
|
|
|
|
next if $c eq $class; |
58
|
|
|
|
|
|
|
foreach my $field ( @{$c->field_list} ) { |
59
|
|
|
|
|
|
|
push @$fields, $field unless $field eq $c->id_field; |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
return $fields; |
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub all_fields { return [ @{$_[0]->field_list}, @{$_[0]->inherited_fields} ]; } |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
sub all_field_types { |
69
|
|
|
|
|
|
|
my $class = shift; |
70
|
|
|
|
|
|
|
my $p = shift; |
71
|
|
|
|
|
|
|
$class = ref($class) if ref($class); ## get class if passed an object |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
my $type_info = {}; |
74
|
|
|
|
|
|
|
foreach my $c ( @{$class->isa_classes} ) { |
75
|
|
|
|
|
|
|
my $c_types = { $c->db_discover_types( $c->base_table, $p )->as_hash }; |
76
|
|
|
|
|
|
|
foreach my $field ( @{$c->field_list} ) { |
77
|
|
|
|
|
|
|
$type_info->{$field} = $c_types->{$field} |
78
|
|
|
|
|
|
|
unless $field eq $c->id_field && $c ne $class; ## skip parent table ids |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
return $type_info; |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub config_and_init { |
86
|
|
|
|
|
|
|
my $class = shift; |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
SPOPS::ClassFactory->create( $class->_build_conf ) |
89
|
|
|
|
|
|
|
unless $class->_config_processed; |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
$class->class_initialize; |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
## copied straight from SPOPS::DBI, with the modifications as noted |
95
|
|
|
|
|
|
|
sub fetch { |
96
|
|
|
|
|
|
|
my ( $class, $id, $p ) = @_; |
97
|
|
|
|
|
|
|
$p ||= {}; |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
$log->is_debug && |
100
|
|
|
|
|
|
|
$log->debug( "Trying to fetch an item of $class with ID $id and params ", |
101
|
|
|
|
|
|
|
join( " // ", map { sprintf( "%s -> %s", $_, defined $p->{$_} ? $p->{$_} : '' ) } |
102
|
|
|
|
|
|
|
grep { defined $_ } keys %{ $p } ) ); |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
# No ID, no object |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
return undef unless ( defined( $id ) and $id ne '' and $id !~ /^tmp/ ); |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
# Security violations bubble up to caller |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
my $level = $p->{security_level}; |
111
|
|
|
|
|
|
|
unless ( $p->{skip_security} ) { |
112
|
|
|
|
|
|
|
$level ||= $class->check_action_security({ id => $id, |
113
|
|
|
|
|
|
|
required => SPOPS::Secure::SEC_LEVEL_READ }); |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# Do any actions the class wants before fetching -- note that if |
117
|
|
|
|
|
|
|
# any of the actions returns undef (false), we bail. |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
return undef unless ( $class->pre_fetch_action( { %{ $p }, id => $id } ) ); |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
my $obj = undef; |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# If we were passed the data for an object, go ahead and create |
124
|
|
|
|
|
|
|
# it; if not, check to see if we can whip up a cached object |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
if ( ref $p->{data} eq 'HASH' ) { |
127
|
|
|
|
|
|
|
$obj = $class->new({ %{ $p->{data} }, skip_default_values => 1 }); |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
else { |
130
|
|
|
|
|
|
|
$obj = $class->get_cached_object({ %{ $p }, id => $id }); |
131
|
|
|
|
|
|
|
$p->{skip_cache}++; # Set so we don't re-cache it later |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
unless ( ref $obj eq $class ) { |
135
|
|
|
|
|
|
|
##----- REPLACE THIS ORIGINAL CODE ----- |
136
|
|
|
|
|
|
|
# my ( $raw_fields, $select_fields ) = $class->_fetch_select_fields( $p ); |
137
|
|
|
|
|
|
|
##----- WITH THIS OVERRIDING CODE ----- |
138
|
|
|
|
|
|
|
## Note: this code skips the column group and alter field stuff |
139
|
|
|
|
|
|
|
my $table_name = $class->base_table; |
140
|
|
|
|
|
|
|
my $my_id_field = $class->id_field; |
141
|
|
|
|
|
|
|
my $raw_fields = []; |
142
|
|
|
|
|
|
|
my $select_fields = []; |
143
|
|
|
|
|
|
|
my $sqltables = []; |
144
|
|
|
|
|
|
|
my $sqlwhere = []; |
145
|
|
|
|
|
|
|
foreach my $parent_class ( @{$class->isa_classes} ) { |
146
|
|
|
|
|
|
|
my $table = $parent_class->table_name; |
147
|
|
|
|
|
|
|
my $id_field = $parent_class->id_field; |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
push @$sqltables, $table; ## list of tables for "FROM" clause |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
## join tables by id field (set all equal to id field of this class) |
152
|
|
|
|
|
|
|
push @$sqlwhere, $table . '.' . $id_field . ' = ' . |
153
|
|
|
|
|
|
|
$table_name . '.' . $my_id_field |
154
|
|
|
|
|
|
|
unless($table_name eq $table); |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
## all fields, except id of inherited tables |
157
|
|
|
|
|
|
|
foreach my $field ( keys %{$parent_class->field} ) { |
158
|
|
|
|
|
|
|
next if $parent_class ne $class && $field eq $id_field; |
159
|
|
|
|
|
|
|
push @$select_fields, $table . '.' . $field; |
160
|
|
|
|
|
|
|
push @$raw_fields, $field; |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
push @$sqlwhere, $class->id_clause( $id, undef, $p ); |
164
|
|
|
|
|
|
|
##----- END OVERRIDING CODE ----- |
165
|
|
|
|
|
|
|
$log->is_info && |
166
|
|
|
|
|
|
|
$log->info( "SELECTing: ", join( "//", @{ $select_fields } ) ); |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
# Put all the arguments into a hash (so we can reuse them simply |
169
|
|
|
|
|
|
|
# later) and grab the record |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
##----- REPLACE THIS ORIGINAL CODE ----- |
172
|
|
|
|
|
|
|
# my %args = ( |
173
|
|
|
|
|
|
|
# from => [ $class->table_name ], |
174
|
|
|
|
|
|
|
# select => $select_fields, |
175
|
|
|
|
|
|
|
# where => $class->id_clause( $id, undef, $p ), |
176
|
|
|
|
|
|
|
##----- WITH THIS OVERRIDING CODE ----- |
177
|
|
|
|
|
|
|
my %args = ( |
178
|
|
|
|
|
|
|
from => $sqltables, |
179
|
|
|
|
|
|
|
select => $select_fields, |
180
|
|
|
|
|
|
|
where => join(' AND ', @$sqlwhere), |
181
|
|
|
|
|
|
|
##----- END OVERRIDING CODE ----- |
182
|
|
|
|
|
|
|
db => $p->{db}, |
183
|
|
|
|
|
|
|
return => 'single', |
184
|
|
|
|
|
|
|
); |
185
|
|
|
|
|
|
|
my $row = eval { $class->db_select( \%args ) }; |
186
|
|
|
|
|
|
|
if ( $@ ) { |
187
|
|
|
|
|
|
|
$class->fail_fetch( \%args ); |
188
|
|
|
|
|
|
|
die $@; |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# If the row isn't found, return nothing; just as if an incorrect |
192
|
|
|
|
|
|
|
# (or nonexistent) ID were passed in |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
return undef unless ( $row ); |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# Note that we pass $p along to the ->new() method, in case |
197
|
|
|
|
|
|
|
# other information was passed in needed by it -- however, we |
198
|
|
|
|
|
|
|
# need to be careful that certain parameters used by this |
199
|
|
|
|
|
|
|
# method (e.g., the optional 'field_alter') is not the same as |
200
|
|
|
|
|
|
|
# a parameter of an object -- THAT would be fun to debug... |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
$obj = $class->new({ id => $id, skip_default_values => 1, %{ $p } }); |
203
|
|
|
|
|
|
|
$obj->_fetch_assign_row( $raw_fields, $row, $p ); |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
return $obj->_fetch_post_process( $p, $level ); |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
## copied straight from SPOPS::DBI, with the modifications as noted |
209
|
|
|
|
|
|
|
sub fetch_group { |
210
|
|
|
|
|
|
|
my ( $class, $p ) = @_; |
211
|
|
|
|
|
|
|
##----- REPLACE THIS ORIGINAL CODE ----- |
212
|
|
|
|
|
|
|
# ( $p->{raw_fields}, $p->{select} ) = $class->_construct_group_select( $p ); |
213
|
|
|
|
|
|
|
##----- WITH THIS OVERRIDING CODE ----- |
214
|
|
|
|
|
|
|
## Note: this code skips the column group and alter field stuff |
215
|
|
|
|
|
|
|
my $table_name = $class->table_name; |
216
|
|
|
|
|
|
|
my $p_original = $p ? { %$p } : {}; |
217
|
|
|
|
|
|
|
my $my_id_field = $class->id_field; |
218
|
|
|
|
|
|
|
my $raw_fields = []; |
219
|
|
|
|
|
|
|
my $select_fields = []; |
220
|
|
|
|
|
|
|
my $sqltables = []; |
221
|
|
|
|
|
|
|
my $sqlwhere = []; |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
foreach my $parent_class ( @{$class->isa_classes} ) { |
224
|
|
|
|
|
|
|
my $table = $parent_class->table_name; |
225
|
|
|
|
|
|
|
my $id_field = $parent_class->id_field; |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
push @$sqltables, $table; ## list of tables for "FROM" clause |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
## join tables by id field (set all equal to id field of this class) |
230
|
|
|
|
|
|
|
push @$sqlwhere, $table . '.' . $id_field . ' = ' . |
231
|
|
|
|
|
|
|
$table_name . '.' . $my_id_field |
232
|
|
|
|
|
|
|
unless($table_name eq $table); |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
## all fields, except id of inherited tables |
235
|
|
|
|
|
|
|
foreach my $field ( keys %{$parent_class->field} ) { |
236
|
|
|
|
|
|
|
next if $parent_class ne $class && $field eq $id_field; |
237
|
|
|
|
|
|
|
push @$select_fields, $table . '.' . $field; |
238
|
|
|
|
|
|
|
push @$raw_fields, $field; |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
## original table list and WHERE clause |
243
|
|
|
|
|
|
|
push @$sqltables, @{ $p->{from} } if $p->{from}; |
244
|
|
|
|
|
|
|
push @$sqlwhere, $p->{where} if $p->{where}; |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
$p->{where} = join(' AND ', @$sqlwhere); |
247
|
|
|
|
|
|
|
$p->{from} = $sqltables; |
248
|
|
|
|
|
|
|
$p->{select} = $select_fields; |
249
|
|
|
|
|
|
|
$p->{raw_fields} = $raw_fields; |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
## get indices into rows of class name and object id |
252
|
|
|
|
|
|
|
my ($classname_idx) = grep $raw_fields->[$_] eq 'class', (0..$#{$raw_fields}); |
253
|
|
|
|
|
|
|
my ($id_field_idx) = grep $raw_fields->[$_] eq $class->id_field, (0..$#{$raw_fields}); |
254
|
|
|
|
|
|
|
##----- END OVERRIDING CODE ----- |
255
|
|
|
|
|
|
|
my $sth = $class->_execute_multiple_record_query( $p ); |
256
|
|
|
|
|
|
|
my ( $offset, $max ) = SPOPS::Utility->determine_limit( $p->{limit} ); |
257
|
|
|
|
|
|
|
my @obj_list = (); |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
my $row_count = 0; |
260
|
|
|
|
|
|
|
ROW: |
261
|
|
|
|
|
|
|
while ( my $row = $sth->fetchrow_arrayref ) { |
262
|
|
|
|
|
|
|
##----- BEGIN ADDITIONAL CODE ----- |
263
|
|
|
|
|
|
|
my $newclass = $row->[ $classname_idx ]; |
264
|
|
|
|
|
|
|
if ($newclass eq $class) { |
265
|
|
|
|
|
|
|
##----- END ADDITIONAL CODE ----- |
266
|
|
|
|
|
|
|
my $obj = $class->new({ skip_default_values => 1 }); |
267
|
|
|
|
|
|
|
$obj->_fetch_assign_row( $p->{raw_fields}, $row, $p ); |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
next ROW unless ( $obj ); # How could this ever be true? |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# Check security on the row unless overridden by |
272
|
|
|
|
|
|
|
# 'skip_security'. If the security check fails that's ok, just |
273
|
|
|
|
|
|
|
# skip the row and move on |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
my $sec_level = SPOPS::Secure::SEC_LEVEL_WRITE; |
276
|
|
|
|
|
|
|
unless ( $p->{skip_security} ) { |
277
|
|
|
|
|
|
|
$log->is_debug && |
278
|
|
|
|
|
|
|
$log->debug( "Checking security for [", ref( $obj ), ": ", $obj->id, "]" ); |
279
|
|
|
|
|
|
|
$sec_level = eval { |
280
|
|
|
|
|
|
|
$obj->check_action_security({ required => SPOPS::Secure::SEC_LEVEL_READ }) |
281
|
|
|
|
|
|
|
}; |
282
|
|
|
|
|
|
|
if ( $@ ) { |
283
|
|
|
|
|
|
|
$log->is_info && |
284
|
|
|
|
|
|
|
$log->info( "Security check for object in ", |
285
|
|
|
|
|
|
|
"fetch_group() failed, skipping." ); |
286
|
|
|
|
|
|
|
next ROW; |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
# Not to the offset yet, so go to the next row but still increment |
291
|
|
|
|
|
|
|
# the counter so we calculate limits properly |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
if ( $offset and ( $row_count < $offset ) ) { |
294
|
|
|
|
|
|
|
$row_count++; |
295
|
|
|
|
|
|
|
next ROW; |
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
last ROW if ( $max and ( $row_count >= $max ) ); |
298
|
|
|
|
|
|
|
$row_count++; |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
# If we've made it down to here, we're home free; just call the |
301
|
|
|
|
|
|
|
# post_fetch callback |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
next ROW unless ( $obj->_fetch_post_process( $p, $sec_level ) ); |
304
|
|
|
|
|
|
|
push @obj_list, $obj; |
305
|
|
|
|
|
|
|
##----- BEGIN ADDITIONAL CODE ----- |
306
|
|
|
|
|
|
|
} else { |
307
|
|
|
|
|
|
|
next ROW unless UNIVERSAL::isa($newclass, $class); |
308
|
|
|
|
|
|
|
my $obj = $newclass->fetch( $row->[ $id_field_idx ], $p_original); |
309
|
|
|
|
|
|
|
next ROW unless ( $obj ); |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
## deleted security check (done by fetch) |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# Not to the offset yet, so go to the next row but still increment |
314
|
|
|
|
|
|
|
# the counter so we calculate limits properly |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
if ( $offset and ( $row_count < $offset ) ) { |
317
|
|
|
|
|
|
|
$row_count++; |
318
|
|
|
|
|
|
|
next ROW; |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
last ROW if ( $max and ( $row_count >= $max ) ); |
321
|
|
|
|
|
|
|
$row_count++; |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
## deleted _post_fetch_process (done by fetch) |
324
|
|
|
|
|
|
|
push @obj_list, $obj; |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
##----- END ADDITIONAL CODE ----- |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
$sth->finish; |
329
|
|
|
|
|
|
|
return \@obj_list; |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
## copied straight from SPOPS::DBI, with the modifications as noted |
333
|
|
|
|
|
|
|
sub fetch_count { |
334
|
|
|
|
|
|
|
my ( $class, $p ) = @_; |
335
|
|
|
|
|
|
|
my $row_count = 0; |
336
|
|
|
|
|
|
|
##----- REPLACE THIS ORIGINAL CODE ----- |
337
|
|
|
|
|
|
|
# if ( $p->{skip_security} ) { |
338
|
|
|
|
|
|
|
# $p->{select} = [ 'COUNT(*)' ]; |
339
|
|
|
|
|
|
|
# my $db = $p->{db} |
340
|
|
|
|
|
|
|
# || $class->global_datasource_handle( $p->{connect_key} ); |
341
|
|
|
|
|
|
|
# my $row_count_rec = eval { |
342
|
|
|
|
|
|
|
# $class->db_select({ select => [ 'COUNT(*)' ], |
343
|
|
|
|
|
|
|
# where => $p->{where}, |
344
|
|
|
|
|
|
|
# value => $p->{value}, |
345
|
|
|
|
|
|
|
# from => $class->table_name, |
346
|
|
|
|
|
|
|
# return => 'single', |
347
|
|
|
|
|
|
|
# db => $db }) |
348
|
|
|
|
|
|
|
# }; |
349
|
|
|
|
|
|
|
# $row_count = $row_count_rec->[0]; |
350
|
|
|
|
|
|
|
# if ( $@ ) { |
351
|
|
|
|
|
|
|
# $log->warn( "Caught error running SELECT COUNT(*): $@" ); |
352
|
|
|
|
|
|
|
# } |
353
|
|
|
|
|
|
|
# } |
354
|
|
|
|
|
|
|
# else { |
355
|
|
|
|
|
|
|
# $p->{select} = [ $class->id_field_select( $p ) ]; |
356
|
|
|
|
|
|
|
##----- WITH THIS OVERRIDING CODE ----- |
357
|
|
|
|
|
|
|
## should be fine if the class has a table, |
358
|
|
|
|
|
|
|
## except we can't use inherited fields in WHERE clause |
359
|
|
|
|
|
|
|
## without doing explicit join |
360
|
|
|
|
|
|
|
return $class->SUPER::fetch_count($p) if $class->_config_processed; |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
my $obj_table = $class->ROOT_OBJ_CLASS->table_name; |
363
|
|
|
|
|
|
|
my $my_table = $class->table_name; |
364
|
|
|
|
|
|
|
$p->{select} = [ $class->id_field_select( $p ), "$obj_table.class" ]; |
365
|
|
|
|
|
|
|
if ($my_table ne $obj_table) { |
366
|
|
|
|
|
|
|
push @{$p->{from}}, $obj_table; |
367
|
|
|
|
|
|
|
my @where = $obj_table . '.' . $class->ROOT_OBJ_CLASS->id_field . ' = ' . |
368
|
|
|
|
|
|
|
$my_table . '.' . $class->id_field; |
369
|
|
|
|
|
|
|
push @where, $p->{where} if $p->{where}; |
370
|
|
|
|
|
|
|
$p->{where} = join(' AND ', @where); |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
##----- END OVERRIDING CODE ----- |
373
|
|
|
|
|
|
|
my $sth = $class->_execute_multiple_record_query( $p ); |
374
|
|
|
|
|
|
|
while ( my $row = $sth->fetch ) { |
375
|
|
|
|
|
|
|
eval { |
376
|
|
|
|
|
|
|
$class->check_action_security({ id => $row->[0], |
377
|
|
|
|
|
|
|
required => SPOPS::Secure::SEC_LEVEL_READ }) |
378
|
|
|
|
|
|
|
}; |
379
|
|
|
|
|
|
|
next if ( $@ ); |
380
|
|
|
|
|
|
|
##----- BEGIN ADDITIONAL CODE ----- |
381
|
|
|
|
|
|
|
next unless UNIVERSAL::isa($row->[1], $class); |
382
|
|
|
|
|
|
|
##----- END ADDITIONAL CODE ----- |
383
|
|
|
|
|
|
|
$row_count++; |
384
|
|
|
|
|
|
|
} |
385
|
|
|
|
|
|
|
##----- BEGIN REMOVE CODE ----- |
386
|
|
|
|
|
|
|
# } |
387
|
|
|
|
|
|
|
##----- END REMOVE CODE ----- |
388
|
|
|
|
|
|
|
return $row_count; |
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
sub pm_fetch { |
392
|
|
|
|
|
|
|
my ( $class, $id, $p ) = @_; |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
$p->{where} = $class->id_clause( $id ); |
395
|
|
|
|
|
|
|
my $obj = $class->fetch_group( $p )->[0]; |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
return $obj; |
398
|
|
|
|
|
|
|
} |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
sub fetch_group_by_field { |
401
|
|
|
|
|
|
|
my ( $class, $field, $vals, $p ) = @_; |
402
|
|
|
|
|
|
|
return [] unless @$vals; |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
my $where = $class->base_table . ".$field" . |
405
|
|
|
|
|
|
|
' IN (' . join(',', map('?', @$vals)) . ')'; |
406
|
|
|
|
|
|
|
$where .= ' AND (' . $p->{where} . ')' if $p->{where}; |
407
|
|
|
|
|
|
|
$p->{where} = $where; |
408
|
|
|
|
|
|
|
unshift @{$p->{value}}, @$vals; |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
my $objs = $class->fetch_group( $p ); |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
return $objs; |
413
|
|
|
|
|
|
|
} |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
sub fetch_group_by_ids { |
416
|
|
|
|
|
|
|
my ( $class, $ids, $p ) = @_; |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
my $unordered = $class->fetch_group_by_field( $class->id_field, $ids, $p ); |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
## order by id list |
421
|
|
|
|
|
|
|
my %obj_by_id = map { $_->id => $_ } @$unordered; |
422
|
|
|
|
|
|
|
my @ordered = grep { $_ } map { $obj_by_id{$_} } @$ids; |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
return \@ordered; |
425
|
|
|
|
|
|
|
} |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
##----- Public Object Methods ----- |
428
|
|
|
|
|
|
|
## copied straight from SPOPS::DBI, with the modifications as noted |
429
|
|
|
|
|
|
|
sub save { |
430
|
|
|
|
|
|
|
my ( $self, $p ) = @_; |
431
|
|
|
|
|
|
|
$log->is_info && |
432
|
|
|
|
|
|
|
$log->info( "Trying to save a (", ref $self, ")" ); |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
# We can force save() to be an INSERT by passing in a true value |
435
|
|
|
|
|
|
|
# for the is_add parameter; otherwise, we rely on the flag within |
436
|
|
|
|
|
|
|
# SPOPS::Tie to reflect whether an object has been saved or not. |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
my $is_add = ( $p->{is_add} or ! $self->saved ); |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
# If this is an update and it hasn't changed, we don't need to do |
441
|
|
|
|
|
|
|
# anything. |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
unless ( $is_add or $self->changed ) { |
444
|
|
|
|
|
|
|
$log->is_info && |
445
|
|
|
|
|
|
|
$log->info( "This object exists and has not changed. Exiting." ); |
446
|
|
|
|
|
|
|
return $self; |
447
|
|
|
|
|
|
|
} |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
# Check security for create/update |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
my $action = ( $is_add ) ? 'create' : 'update'; |
452
|
|
|
|
|
|
|
my ( $level ); |
453
|
|
|
|
|
|
|
unless ( $p->{skip_security} ) { |
454
|
|
|
|
|
|
|
$level = $self->check_action_security({ required => SPOPS::Secure::SEC_LEVEL_WRITE, |
455
|
|
|
|
|
|
|
is_add => $is_add }); |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
$log->is_info && |
458
|
|
|
|
|
|
|
$log->info( "Security check passed ok. Continuing." ); |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
# Callback for objects to do something before they're saved |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
return undef unless ( $self->pre_save_action({ %{ $p }, |
463
|
|
|
|
|
|
|
is_add => $is_add }) ); |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
##----- BEGIN ADDITIONAL CODE ----- |
466
|
|
|
|
|
|
|
## get list of classes which need to be saved |
467
|
|
|
|
|
|
|
## (put ROOT_OBJ_CLASS first, and this class last) |
468
|
|
|
|
|
|
|
my $no_insert = $p->{no_insert}; ## this gets converted to an empty hash |
469
|
|
|
|
|
|
|
## which causes problems the 2nd time through |
470
|
|
|
|
|
|
|
my @classes = reverse @{ $self->isa_classes }; |
471
|
|
|
|
|
|
|
foreach my $class (@classes) { |
472
|
|
|
|
|
|
|
bless $self, $class; |
473
|
|
|
|
|
|
|
$p->{field} = []; |
474
|
|
|
|
|
|
|
$p->{value} = []; |
475
|
|
|
|
|
|
|
$p->{no_insert} = $no_insert; |
476
|
|
|
|
|
|
|
##----- END ADDITIONAL CODE ----- |
477
|
|
|
|
|
|
|
# Do not include these fields in the insert/update at all. Allow |
478
|
|
|
|
|
|
|
# user to override even with an empty arrayref. |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
my ( %not_included ); |
481
|
|
|
|
|
|
|
if ( $is_add ) { |
482
|
|
|
|
|
|
|
my ( @no_insert_items ); |
483
|
|
|
|
|
|
|
if ( $p->{no_insert} ) { |
484
|
|
|
|
|
|
|
@no_insert_items = ( ref $p->{no_insert} eq 'ARRAY' ) |
485
|
|
|
|
|
|
|
? @{ $p->{no_insert} } : ( $p->{no_insert} ); |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
elsif ( my $no_insert_config = $self->no_insert ) { |
488
|
|
|
|
|
|
|
@no_insert_items = keys %{ $no_insert_config }; |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
%not_included = map { $_ => 1 } @no_insert_items; |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
else { |
493
|
|
|
|
|
|
|
my ( @no_update_items ); |
494
|
|
|
|
|
|
|
if ( $p->{no_update} ) { |
495
|
|
|
|
|
|
|
@no_update_items = ( ref $p->{no_update} eq 'ARRAY' ) |
496
|
|
|
|
|
|
|
? @{ $p->{no_update} } : ( $p->{no_update} ); |
497
|
|
|
|
|
|
|
} |
498
|
|
|
|
|
|
|
elsif ( my $no_update_config = $self->no_update ) { |
499
|
|
|
|
|
|
|
@no_update_items = keys %{ $no_update_config }; |
500
|
|
|
|
|
|
|
} |
501
|
|
|
|
|
|
|
%not_included = map { $_ => 1 } @no_update_items; |
502
|
|
|
|
|
|
|
} |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
# Do not include these fields in the insert/update if they're not defined |
505
|
|
|
|
|
|
|
# (note that this includes blank/empty) |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
$p->{skip_undef} ||= []; |
508
|
|
|
|
|
|
|
my $skip_undef = $self->skip_undef || {}; |
509
|
|
|
|
|
|
|
$skip_undef->{ $_ }++ for ( @{ $p->{skip_undef} } ); |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
$p->{field} = []; |
512
|
|
|
|
|
|
|
$p->{value} = []; |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
FIELD: |
515
|
|
|
|
|
|
|
foreach my $field ( keys %{ $self->field } ) { |
516
|
|
|
|
|
|
|
next FIELD if ( $not_included{ $field } ); |
517
|
|
|
|
|
|
|
my $value = $self->{ $field }; |
518
|
|
|
|
|
|
|
next FIELD if ( ! defined $value and $skip_undef->{ $field } ); |
519
|
|
|
|
|
|
|
push @{ $p->{field} }, $field; |
520
|
|
|
|
|
|
|
push @{ $p->{value} }, $value; |
521
|
|
|
|
|
|
|
} |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
# Do the insert/update based on whether the object is new; don't |
524
|
|
|
|
|
|
|
# catch the die() that might be thrown -- let that percolate |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
##----- REPLACE THIS ORIGINAL CODE ----- |
527
|
|
|
|
|
|
|
# if ( $is_add ) { $self->_save_insert( $p, \%not_included ) } |
528
|
|
|
|
|
|
|
##----- WITH THIS OVERRIDING CODE ----- |
529
|
|
|
|
|
|
|
if ( $is_add ) { |
530
|
|
|
|
|
|
|
eval { $self->_save_insert( $p, \%not_included ) }; |
531
|
|
|
|
|
|
|
## clean up partial saves if there is a duplicate entry error |
532
|
|
|
|
|
|
|
if (my $error = $@) { ## save $@ from getting overwritten |
533
|
|
|
|
|
|
|
## in remove_from_parent_tables() |
534
|
|
|
|
|
|
|
$self->_remove_from_parent_tables if $error =~ /Duplicate entry/; |
535
|
|
|
|
|
|
|
die $error; |
536
|
|
|
|
|
|
|
} |
537
|
|
|
|
|
|
|
} |
538
|
|
|
|
|
|
|
##----- END OVERRIDING CODE ----- |
539
|
|
|
|
|
|
|
else { $self->_save_update( $p, \%not_included ) } |
540
|
|
|
|
|
|
|
##----- BEGIN ADDITIONAL CODE ----- |
541
|
|
|
|
|
|
|
} |
542
|
|
|
|
|
|
|
##----- END ADDITIONAL CODE ----- |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
# Set the 'has_save' flag so that any saved changes to the object |
545
|
|
|
|
|
|
|
# in the post_save will be an update rather than another insert; |
546
|
|
|
|
|
|
|
# clear the changed fields for the same reason |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
$self->has_save; |
549
|
|
|
|
|
|
|
$self->clear_change; |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
# Do any actions that need to happen after you save the object |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
return undef unless ( $self->post_save_action({ %{ $p }, |
554
|
|
|
|
|
|
|
is_add => $is_add }) ); |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
# Save the newly-created/updated object to the cache |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
$self->set_cached_object( $p ); |
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
# Note the action that we've just taken (opportunity for subclasses) |
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
unless ( $p->{skip_log} ) { |
563
|
|
|
|
|
|
|
$self->log_action( $action, scalar $self->id ); |
564
|
|
|
|
|
|
|
} |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
return $self; |
567
|
|
|
|
|
|
|
} |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
sub compare { |
570
|
|
|
|
|
|
|
my $self = shift; |
571
|
|
|
|
|
|
|
my $twin = shift; |
572
|
|
|
|
|
|
|
my $p = shift; |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
## must be objects of the same type |
575
|
|
|
|
|
|
|
return 0 unless ref($self) eq ref($twin); |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
## and their fields must all have the same values |
578
|
|
|
|
|
|
|
my $type_info = $self->all_field_types($p); |
579
|
|
|
|
|
|
|
foreach my $field ( @{$self->all_fields} ) { |
580
|
|
|
|
|
|
|
next if $field eq $self->id_field; |
581
|
|
|
|
|
|
|
next unless defined $self->{$field} || defined $twin->{$field}; |
582
|
|
|
|
|
|
|
return 0 unless defined $self->{$field} && $twin->{$field}; |
583
|
|
|
|
|
|
|
if ( ref($self->{$field}) ) { |
584
|
|
|
|
|
|
|
return 0 unless $self->{$field}->compare($twin->{$field}); |
585
|
|
|
|
|
|
|
} else { |
586
|
|
|
|
|
|
|
if ( $self->_is_numeric_type($type_info->{$field}) ) { |
587
|
|
|
|
|
|
|
return 0 unless $self->{$field} == $twin->{$field}; |
588
|
|
|
|
|
|
|
} else { |
589
|
|
|
|
|
|
|
return 0 unless $self->{$field} eq $twin->{$field}; |
590
|
|
|
|
|
|
|
} |
591
|
|
|
|
|
|
|
} |
592
|
|
|
|
|
|
|
} |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
return 1; |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
sub as_string { |
598
|
|
|
|
|
|
|
my $self = shift; |
599
|
|
|
|
|
|
|
my $tab = shift || ''; |
600
|
|
|
|
|
|
|
my $fields = $self->CONFIG->{as_string_order} || $self->all_fields; |
601
|
|
|
|
|
|
|
my $msg = ''; |
602
|
|
|
|
|
|
|
foreach my $field (@$fields){ |
603
|
|
|
|
|
|
|
$msg .= $tab.sprintf( "%-20s: %s\n", $field, defined $self->{$field} ? $self->{$field} : ''); |
604
|
|
|
|
|
|
|
my $ref = ref $self->{$field}; |
605
|
|
|
|
|
|
|
next unless ($ref && $ref ne 'HASH' && $ref ne 'ARRAY'); |
606
|
|
|
|
|
|
|
$msg .= $self->{$field}->as_string("\t"); |
607
|
|
|
|
|
|
|
} |
608
|
|
|
|
|
|
|
return $msg; |
609
|
|
|
|
|
|
|
} |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
##----- Private Class Methods ----- |
612
|
|
|
|
|
|
|
## overrides method in SPOPS |
613
|
|
|
|
|
|
|
sub _get_definitive_fields { return $_[0]->all_fields; } |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
sub _build_conf { |
616
|
|
|
|
|
|
|
my $class = shift; |
617
|
|
|
|
|
|
|
my $conf = shift || {}; |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
# get the conf variable for the class. |
620
|
|
|
|
|
|
|
my $class_conf = $class->_get_CONF; |
621
|
|
|
|
|
|
|
# get the alias for the class |
622
|
|
|
|
|
|
|
my $alias = $class->_get_main_alias; |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
unless (exists ($conf->{$alias})) { |
625
|
|
|
|
|
|
|
$conf->{$alias} = $class_conf->{$alias}; |
626
|
|
|
|
|
|
|
} |
627
|
|
|
|
|
|
|
my $class_links = $class->_get_links_to || ''; |
628
|
|
|
|
|
|
|
if ($class_links) { |
629
|
|
|
|
|
|
|
foreach my $key (keys %$class_links) { |
630
|
|
|
|
|
|
|
next if ($key->_config_processed || |
631
|
|
|
|
|
|
|
exists $conf->{$key->_get_main_alias}); |
632
|
|
|
|
|
|
|
$conf = $key->_build_conf($conf); |
633
|
|
|
|
|
|
|
} |
634
|
|
|
|
|
|
|
}#end if |
635
|
|
|
|
|
|
|
return $conf; |
636
|
|
|
|
|
|
|
} |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
sub _get_main_alias { |
639
|
|
|
|
|
|
|
## This method can be used to find the main alias even |
640
|
|
|
|
|
|
|
## before the class's configuration has been processed. |
641
|
|
|
|
|
|
|
my $class = shift; |
642
|
|
|
|
|
|
|
my $conf = eval '$' . $class . '::CONF'; |
643
|
|
|
|
|
|
|
my ($alias) = grep $conf->{$_}->{class} eq $class, keys %$conf; |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
return $alias; |
646
|
|
|
|
|
|
|
} |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
sub _get_CONF { return eval '$' . $_[0] . '::CONF'; } |
649
|
|
|
|
|
|
|
sub _get_links_to { return $_[0]->_get_CONF->{$_[0]->_get_main_alias}->{links_to}; } |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
sub _config_processed { |
652
|
|
|
|
|
|
|
no strict 'refs'; |
653
|
|
|
|
|
|
|
my $CONFIG_method = *{$_[0]."::CONFIG"}{CODE}; |
654
|
|
|
|
|
|
|
return ref($CONFIG_method) eq 'CODE'; |
655
|
|
|
|
|
|
|
} |
656
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
sub _is_numeric_type { |
658
|
|
|
|
|
|
|
my $self = shift; |
659
|
|
|
|
|
|
|
my $type_info = shift; |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
return grep $type_info == $_, ( SQL_NUMERIC, |
662
|
|
|
|
|
|
|
SQL_DECIMAL, |
663
|
|
|
|
|
|
|
SQL_INTEGER, |
664
|
|
|
|
|
|
|
SQL_SMALLINT, |
665
|
|
|
|
|
|
|
SQL_FLOAT, |
666
|
|
|
|
|
|
|
SQL_REAL, |
667
|
|
|
|
|
|
|
SQL_DOUBLE, |
668
|
|
|
|
|
|
|
# (no longer in DBI) SQL_BIGINT, |
669
|
|
|
|
|
|
|
SQL_TINYINT ); |
670
|
|
|
|
|
|
|
} |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
sub _isa_classes { |
673
|
|
|
|
|
|
|
my $class = shift; |
674
|
|
|
|
|
|
|
my $href = shift || {}; |
675
|
|
|
|
|
|
|
my $depth = shift || 1; |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
$class = ref($class) if ref($class); ## get class if passed an object |
678
|
|
|
|
|
|
|
$href->{$class} = $depth; ## stick it as a key in the hash |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
foreach my $parent ( @{$class->CONFIG->{isa}} ) { |
681
|
|
|
|
|
|
|
next unless $parent->isa($class->ROOT_OBJ_CLASS); |
682
|
|
|
|
|
|
|
$href = $parent->_isa_classes($href, $depth+1) |
683
|
|
|
|
|
|
|
unless $href->{$parent} && $href->{$parent} > $depth+1; |
684
|
|
|
|
|
|
|
} |
685
|
|
|
|
|
|
|
return $href; |
686
|
|
|
|
|
|
|
} |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
##----- Private Object Methods ----- |
689
|
|
|
|
|
|
|
sub _remove_from_parent_tables { |
690
|
|
|
|
|
|
|
my $self = shift; |
691
|
|
|
|
|
|
|
my $p = shift || {}; |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
foreach my $class (@{ $self->isa_classes }) { |
694
|
|
|
|
|
|
|
next if $class eq ref($self); |
695
|
|
|
|
|
|
|
eval { |
696
|
|
|
|
|
|
|
$class->db_delete({ |
697
|
|
|
|
|
|
|
table => $class->table_name, |
698
|
|
|
|
|
|
|
where => $class->id_clause($self->id, 'noqualify', {%$p}), |
699
|
|
|
|
|
|
|
db => $p->{db}, |
700
|
|
|
|
|
|
|
}) |
701
|
|
|
|
|
|
|
}; |
702
|
|
|
|
|
|
|
warn "Unable to remove row from ". $class->table_name if $@; |
703
|
|
|
|
|
|
|
} |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
return 1; |
706
|
|
|
|
|
|
|
} |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
##----- Callback Methods ----- |
709
|
|
|
|
|
|
|
sub post_fetch_action { |
710
|
|
|
|
|
|
|
my $self = shift; |
711
|
|
|
|
|
|
|
my $orig_p = shift || {}; |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
## call the overridden post_fetch_action to handle rulesets |
714
|
|
|
|
|
|
|
$self->SUPER::post_fetch_action( $orig_p ); |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
foreach my $field ( keys %{ $self->e_has_a } ) { |
717
|
|
|
|
|
|
|
my $h = $self->e_has_a->{$field}; |
718
|
|
|
|
|
|
|
if ( $h->{fetch} && $h->{fetch}{type} eq 'auto' ) { |
719
|
|
|
|
|
|
|
if ( my $val = $self->{$field} ) { |
720
|
|
|
|
|
|
|
my %p; |
721
|
|
|
|
|
|
|
$p{db} = $orig_p->{db} if defined $orig_p->{db}; |
722
|
|
|
|
|
|
|
$self->{$field} = $h->{class}->pm_fetch($val, \%p ) || |
723
|
|
|
|
|
|
|
die "Could not auto-fetch '$field' ($h->{class}) id: $val"; |
724
|
|
|
|
|
|
|
} |
725
|
|
|
|
|
|
|
} |
726
|
|
|
|
|
|
|
} |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
return $self; |
729
|
|
|
|
|
|
|
} |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
sub pre_save_action { |
732
|
|
|
|
|
|
|
my $self = shift; |
733
|
|
|
|
|
|
|
my $orig_p = shift || {}; |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
## call the overridden pre_save_action to handle rulesets |
736
|
|
|
|
|
|
|
$self->SUPER::pre_save_action( $orig_p ); |
737
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
foreach my $field ( keys %{ $self->e_has_a } ) { |
739
|
|
|
|
|
|
|
my $h = $self->e_has_a->{ $field }; |
740
|
|
|
|
|
|
|
my $val = $self->{$field}; |
741
|
|
|
|
|
|
|
if ( $val && ref $val ) { |
742
|
|
|
|
|
|
|
## save if indicated |
743
|
|
|
|
|
|
|
my %p; |
744
|
|
|
|
|
|
|
$p{db} = $orig_p->{db} if defined $orig_p->{db}; |
745
|
|
|
|
|
|
|
$val->save( \%p ) if $h->{fetch} && !$h->{fetch}{nosave}; |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
## move object to a temp field during save |
748
|
|
|
|
|
|
|
$self->{'tmp_' . $field . '_'} = $val; |
749
|
|
|
|
|
|
|
$self->{$field} = $self->{$field}->id; |
750
|
|
|
|
|
|
|
} |
751
|
|
|
|
|
|
|
} |
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
return $self; |
754
|
|
|
|
|
|
|
} |
755
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
sub post_save_action { |
757
|
|
|
|
|
|
|
my $self = shift; |
758
|
|
|
|
|
|
|
my $orig_p = shift || {}; |
759
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
## call the overridden post_save_action to handle rulesets |
761
|
|
|
|
|
|
|
$self->SUPER::post_save_action( $orig_p );; |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
foreach my $field ( keys %{ $self->e_has_a } ) { |
764
|
|
|
|
|
|
|
my $h = $self->e_has_a->{$field}; |
765
|
|
|
|
|
|
|
my $val = $self->{'tmp_' . $field . '_'}; |
766
|
|
|
|
|
|
|
if ( $val && ref $val ) { |
767
|
|
|
|
|
|
|
$self->{$field} = $val; |
768
|
|
|
|
|
|
|
$self->{'tmp_' . $field . '_'} = undef; |
769
|
|
|
|
|
|
|
} elsif ( $h->{fetch} && $h->{fetch}{type} eq 'auto' ) { |
770
|
|
|
|
|
|
|
if ( my $val = $self->{$field} ) { |
771
|
|
|
|
|
|
|
my %p; |
772
|
|
|
|
|
|
|
$p{db} = $orig_p->{db} if defined $orig_p->{db}; |
773
|
|
|
|
|
|
|
$self->{$field} = $h->{class}->pm_fetch($val, \%p ) || |
774
|
|
|
|
|
|
|
die "Could not auto-fetch '$field' ($h->{class}) id: $val"; |
775
|
|
|
|
|
|
|
} |
776
|
|
|
|
|
|
|
} |
777
|
|
|
|
|
|
|
} |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
return $self; |
780
|
|
|
|
|
|
|
} |
781
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
sub pre_remove_action { |
783
|
|
|
|
|
|
|
my $self = shift; |
784
|
|
|
|
|
|
|
my $orig_p = shift || {}; |
785
|
|
|
|
|
|
|
my $class = ref($self); |
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
## call the overridden pre_remove_action to handle rulesets |
788
|
|
|
|
|
|
|
$self->SUPER::pre_remove_action( $orig_p ); |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
## auto-remove specified secondary objects |
791
|
|
|
|
|
|
|
foreach my $field ( keys %{ $self->e_has_a } ) { |
792
|
|
|
|
|
|
|
my $h = $self->e_has_a->{$field}; |
793
|
|
|
|
|
|
|
if ( $h->{remove} && $h->{remove}{type} eq 'auto' && $self->{$field} ) { |
794
|
|
|
|
|
|
|
my %p; |
795
|
|
|
|
|
|
|
$p{db} = $orig_p->{db} if defined $orig_p->{db}; |
796
|
|
|
|
|
|
|
$self->{$field} = $h->{class}->pm_fetch($self->{$field}, \%p ) |
797
|
|
|
|
|
|
|
unless ref $self->{$field}; |
798
|
|
|
|
|
|
|
$self->{$field}->remove( $orig_p ) if $self->{$field}; |
799
|
|
|
|
|
|
|
} |
800
|
|
|
|
|
|
|
} |
801
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
## remove all corresponding rows in 'links_to' tables |
803
|
|
|
|
|
|
|
my ($table, $where); |
804
|
|
|
|
|
|
|
foreach $table ( values %{$self->CONFIG->{links_to}} ) { |
805
|
|
|
|
|
|
|
$where = $self->id_clause(undef, 'noqualify', $orig_p); |
806
|
|
|
|
|
|
|
eval { $self->db_delete( { table => $table, |
807
|
|
|
|
|
|
|
where => $where, |
808
|
|
|
|
|
|
|
db => $orig_p->{db} }) }; |
809
|
|
|
|
|
|
|
if ( $@ ) { |
810
|
|
|
|
|
|
|
warn "Unable to remove links."; |
811
|
|
|
|
|
|
|
} |
812
|
|
|
|
|
|
|
} |
813
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
## remove corresponding row in each parent table |
815
|
|
|
|
|
|
|
$self->_remove_from_parent_tables; |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
return $self; |
818
|
|
|
|
|
|
|
} |
819
|
|
|
|
|
|
|
|
820
|
|
|
|
|
|
|
1; |
821
|
|
|
|
|
|
|
__END__ |