375f5c5384f49af3afd2b2c2580d1fd9e804892f
[freeside.git] / rt / lib / RT / CustomFieldValues / External.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
6 #                                          <sales@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 F<docs/extending/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     my $condition = $args{VALUE};
127     my $op = $args{'OPERATOR'} || '=';
128     my $field = $args{FIELD};
129
130     return sub {
131         my $record = shift;
132         my $value = $record->$field;
133         return 0 unless defined $value;
134         if ($op eq "=") {
135             return 0 unless $value eq $condition;
136         } elsif ($op eq "!=" or $op eq "<>") {
137             return 0 unless $value ne $condition;
138         } elsif (uc($op) eq "LIKE") {
139             return 0 unless $value =~ /\Q$condition\E/i;
140         } elsif (uc($op) eq "NOT LIKE") {
141             return 0 unless $value !~ /\Q$condition\E/i;
142         } else {
143             return 0;
144         }
145         return 1;
146     };
147 }
148
149 sub __BuildAggregatorsCheck {
150     my $self = shift;
151     my @cbs = grep {$_->{CALLBACK}} @{ $self->{'__external_cf_limits'} };
152     return undef unless @cbs;
153
154     my %h = (
155         OR  => sub { defined $_[0] ? ($_[0] || $_[1]) : $_[1] },
156         AND => sub { defined $_[0] ? ($_[0] && $_[1]) : $_[1] },
157     );
158
159     return sub {
160         my ($sb, $record) = @_;
161         my $ok;
162         for my $limit ( @cbs ) {
163             $ok = $h{$limit->{ENTRYAGGREGATOR} || 'OR'}->(
164                 $ok, $limit->{CALLBACK}->($record),
165             );
166         }
167         return $ok;
168     };
169 }
170
171 sub _DoSearch {
172     my $self = shift;
173
174     delete $self->{'items'};
175
176     my %defaults = (
177             id => 1,
178             name => '',
179             customfield => $self->{'__external_cf'},
180             sortorder => 0,
181             description => '',
182             creator => RT->SystemUser->id,
183             created => undef,
184             lastupdatedby => RT->SystemUser->id,
185             lastupdated => undef,
186     );
187
188     my $i = 0;
189
190     my $check = $self->__BuildAggregatorsCheck;
191     foreach( @{ $self->ExternalValues } ) {
192         my $value = $self->NewItem;
193         $value->LoadFromHash( { %defaults, %$_ } );
194         next if $check && !$check->( $self, $value );
195         $self->AddRecord( $value );
196     }
197     $self->{'must_redo_search'} = 0;
198     return $self->_RecordCount;
199 }
200
201 sub _DoCount {
202     my $self = shift;
203
204     my $count;
205     $count = $self->_DoSearch if $self->{'must_redo_search'};
206     $count = $self->_RecordCount unless defined $count;
207
208     return $self->{'count_all'} = $self->{'raw_rows'} = $count;
209 }
210
211 sub LimitToCustomField {
212     my $self = shift;
213     $self->{'__external_cf'} = $_[0];
214     return $self->SUPER::LimitToCustomField( @_ );
215 }
216
217 RT::Base->_ImportOverlays();
218
219 1;