import rt 3.8.7
[freeside.git] / rt / lib / RT / CustomFieldValues / External.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2
3 # COPYRIGHT:
4
5 # This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
6 #                                          <jesse@bestpractical.com>
7
8 # (Except where explicitly superseded by other copyright notices)
9
10
11 # LICENSE:
12
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
16 # from www.gnu.org.
17
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 # General Public License for more details.
22
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28
29
30 # CONTRIBUTION SUBMISSION POLICY:
31
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
37
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
46
47 # END BPS TAGGED BLOCK }}}
48
49 package RT::CustomFieldValues::External;
50
51 use strict;
52 use warnings;
53
54 use base qw(RT::CustomFieldValues);
55
56 =head1 NAME
57
58 RT::CustomFieldValues::External - Pull possible values for a custom
59 field from an arbitrary external data source.
60
61 =head1 SYNOPSIS
62
63 Custom field value lists can be produced by creating a class that
64 inherits from C<RT::CustomFieldValues::External>, and overloading
65 C<SourceDescription> and C<ExternalValues>.  See
66 L<RT::CustomFieldValues::Groups> for a simple example.
67
68 =head1 DESCRIPTION
69
70 Subclasses should implement the following methods:
71
72 =head2 SourceDescription
73
74 This method should return a string describing the data source; this is
75 the identifier by which the user will see the dropdown.
76
77 =head2 ExternalValues
78
79 This method should return an array reference of hash references.  The
80 hash references should contain keys for C<name>, C<description>, and
81 C<sortorder>.
82
83 =head1 SEE ALSO
84
85 L<docs/creating_external_custom_fields.pod>
86
87 =cut
88
89 sub _Init {
90     my $self = shift;
91     $self->Table( '' );
92     return ( $self->SUPER::_Init(@_) );
93 }
94
95 sub CleanSlate {
96     my $self = shift;
97     delete $self->{ $_ } foreach qw(
98         __external_cf
99         __external_cf_limits
100     );
101     return $self->SUPER::CleanSlate(@_);
102 }
103
104 sub _ClonedAttributes {
105     my $self = shift;
106     return qw(
107         __external_cf
108         __external_cf_limits
109     ), $self->SUPER::_ClonedAttributes;
110 }
111
112 sub Limit {
113     my $self = shift;
114     my %args = (@_);
115     push @{ $self->{'__external_cf_limits'} ||= [] }, {
116         %args,
117         CALLBACK => $self->__BuildLimitCheck( %args ),
118     };
119     return $self->SUPER::Limit( %args );
120 }
121
122 sub __BuildLimitCheck {
123     my ($self, %args) = (@_);
124     return undef unless $args{'FIELD'} =~ /^(?:Name|Description)$/;
125
126     $args{'OPERATOR'} ||= '=';
127     my $quoted_value = $args{'VALUE'};
128     if ( $quoted_value ) {
129         $quoted_value =~ s/'/\\'/g;
130         $quoted_value = "'$quoted_value'";
131     }
132
133     my $code = <<END;
134 my \$record = shift;
135 my \$value = \$record->$args{'FIELD'};
136 my \$condition = $quoted_value;
137 END
138
139     if ( $args{'OPERATOR'} =~ /^(?:=|!=|<>)$/ ) {
140         $code .= 'return 0 unless defined $value;';
141         my %h = ( '=' => ' eq ', '!=' => ' ne ', '<>' => ' ne ' );
142         $code .= 'return 0 unless $value'. $h{ $args{'OPERATOR'} } .'$condition;';
143         $code .= 'return 1;'
144     }
145     elsif ( $args{'OPERATOR'} =~ /^(?:LIKE|NOT LIKE)$/i ) {
146         $code .= 'return 0 unless defined $value;';
147         my %h = ( 'LIKE' => ' =~ ', 'NOT LIKE' => ' !~ ' );
148         $code .= 'return 0 unless $value'. $h{ uc $args{'OPERATOR'} } .'/\Q$condition/i;';
149         $code .= 'return 1;'
150     }
151     else {
152         $code .= 'return 0;'
153     }
154     $code = "sub {$code}";
155     my $cb = eval "$code";
156     $RT::Logger->error( "Couldn't build callback '$code': $@" ) if $@;
157     return $cb;
158 }
159
160 sub __BuildAggregatorsCheck {
161     my $self = shift;
162
163     my %h = ( OR => ' || ', AND => ' && ' );
164     
165     my $code = '';
166     for( my $i = 0; $i < @{ $self->{'__external_cf_limits'} }; $i++ ) {
167         next unless $self->{'__external_cf_limits'}->[$i]->{'CALLBACK'};
168         $code .= $h{ uc($self->{'__external_cf_limits'}->[$i]->{'ENTRYAGGREGATOR'} || 'OR') } if $code;
169         $code .= '$sb->{\'__external_cf_limits\'}->['. $i .']->{\'CALLBACK\'}->($record)';
170     }
171     return unless $code;
172
173     $code = "sub { my (\$sb,\$record) = (\@_); return $code }";
174     my $cb = eval "$code";
175     $RT::Logger->error( "Couldn't build callback '$code': $@" ) if $@;
176     return $cb;
177 }
178
179 sub _DoSearch {
180     my $self = shift;
181
182     delete $self->{'items'};
183
184     my %defaults = (
185             id => 1,
186             name => '',
187             customfield => $self->{'__external_cf'},
188             sortorder => 0,
189             description => '',
190             creator => $RT::SystemUser->id,
191             created => undef,
192             lastupdatedby => $RT::SystemUser->id,
193             lastupdated => undef,
194     );
195
196     my $i = 0;
197
198     my $check = $self->__BuildAggregatorsCheck;
199     foreach( @{ $self->ExternalValues } ) {
200         my $value = $self->NewItem;
201         $value->LoadFromHash( { %defaults, %$_ } );
202         next if $check && !$check->( $self, $value );
203         $self->AddRecord( $value );
204     }
205     $self->{'must_redo_search'} = 0;
206     return $self->_RecordCount;
207 }
208
209 sub _DoCount {
210     my $self = shift;
211
212     my $count;
213     $count = $self->_DoSearch if $self->{'must_redo_search'};
214     $count = $self->_RecordCount unless defined $count;
215
216     return $self->{'count_all'} = $self->{'raw_rows'} = $count;
217 }
218
219 sub LimitToCustomField {
220     my $self = shift;
221     $self->{'__external_cf'} = $_[0];
222     return $self->SUPER::LimitToCustomField( @_ );
223 }
224
225 eval "require RT::CustomFieldValues::External_Vendor";
226 if ($@ && $@ !~ qr{^Can't locate RT/CustomFieldValues/External_Vendor.pm}) {
227     die $@;
228 };
229
230 eval "require RT::CustomFieldValues::External_Local";
231 if ($@ && $@ !~ qr{^Can't locate RT/CustomFieldValues/External_Local.pm}) {
232     die $@;
233 };
234
235 1;