4995e1a18b01d4d29333b9b2d284be5782d2b554
[freeside.git] / rt / lib / RT / CustomFieldValues / External.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2018 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 must contain a key for C<name> and can optionally contain
81 keys for C<description>, C<sortorder>, and C<category>. If supplying a
82 category, you must also set the category the custom field is based on in
83 the custom field configuration page.
84
85 =head1 SEE ALSO
86
87 F<docs/extending/external_custom_fields.pod>
88
89 =cut
90
91 sub _Init {
92     my $self = shift;
93     $self->Table( '' );
94     return ( $self->SUPER::_Init(@_) );
95 }
96
97 sub CleanSlate {
98     my $self = shift;
99     delete $self->{ $_ } foreach qw(
100         __external_cf
101         __external_cf_limits
102     );
103     return $self->SUPER::CleanSlate(@_);
104 }
105
106 sub _ClonedAttributes {
107     my $self = shift;
108     return qw(
109         __external_cf
110         __external_cf_limits
111     ), $self->SUPER::_ClonedAttributes;
112 }
113
114 sub Limit {
115     my $self = shift;
116     my %args = (@_);
117     push @{ $self->{'__external_cf_limits'} ||= [] }, {
118         %args,
119         CALLBACK => $self->__BuildLimitCheck( %args ),
120     };
121     return $self->SUPER::Limit( %args );
122 }
123
124 sub __BuildLimitCheck {
125     my ($self, %args) = (@_);
126     return undef unless $args{'FIELD'} =~ /^(?:Name|Description)$/;
127
128     my $condition = $args{VALUE};
129     my $op = $args{'OPERATOR'} || '=';
130     my $field = $args{FIELD};
131
132     return sub {
133         my $record = shift;
134         my $value = $record->$field;
135         return 0 unless defined $value;
136         if ($op eq "=") {
137             return 0 unless $value eq $condition;
138         } elsif ($op eq "!=" or $op eq "<>") {
139             return 0 unless $value ne $condition;
140         } elsif (uc($op) eq "LIKE") {
141             return 0 unless $value =~ /\Q$condition\E/i;
142         } elsif (uc($op) eq "NOT LIKE") {
143             return 0 unless $value !~ /\Q$condition\E/i;
144         } else {
145             return 0;
146         }
147         return 1;
148     };
149 }
150
151 sub __BuildAggregatorsCheck {
152     my $self = shift;
153     my @cbs = grep {$_->{CALLBACK}} @{ $self->{'__external_cf_limits'} };
154     return undef unless @cbs;
155
156     my %h = (
157         OR  => sub { defined $_[0] ? ($_[0] || $_[1]) : $_[1] },
158         AND => sub { defined $_[0] ? ($_[0] && $_[1]) : $_[1] },
159     );
160
161     return sub {
162         my ($sb, $record) = @_;
163         my $ok;
164         for my $limit ( @cbs ) {
165             $ok = $h{$limit->{ENTRYAGGREGATOR} || 'OR'}->(
166                 $ok, $limit->{CALLBACK}->($record),
167             );
168         }
169         return $ok;
170     };
171 }
172
173 sub _DoSearch {
174     my $self = shift;
175
176     delete $self->{'items'};
177
178     my %defaults = (
179             id => 1,
180             name => '',
181             customfield => $self->{'__external_cf'},
182             sortorder => 0,
183             description => '',
184             category => undef,
185             creator => RT->SystemUser->id,
186             created => undef,
187             lastupdatedby => RT->SystemUser->id,
188             lastupdated => undef,
189     );
190
191     my $i = 0;
192
193     my $check = $self->__BuildAggregatorsCheck;
194     foreach( @{ $self->ExternalValues } ) {
195         my $value = $self->NewItem;
196         $value->LoadFromHash( { %defaults, %$_ } );
197         next if $check && !$check->( $self, $value );
198         $self->AddRecord( $value );
199         last if $self->RowsPerPage and ++$i >= $self->RowsPerPage;
200     }
201     $self->{'must_redo_search'} = 0;
202     return $self->_RecordCount;
203 }
204
205 sub _DoCount {
206     my $self = shift;
207
208     my $count;
209     $count = $self->_DoSearch if $self->{'must_redo_search'};
210     $count = $self->_RecordCount unless defined $count;
211
212     return $self->{'count_all'} = $self->{'raw_rows'} = $count;
213 }
214
215 sub LimitToCustomField {
216     my $self = shift;
217     $self->{'__external_cf'} = $_[0];
218     return $self->SUPER::LimitToCustomField( @_ );
219 }
220
221 sub _SingularClass {
222     "RT::CustomFieldValue"
223 }
224
225 RT::Base->_ImportOverlays();
226
227 1;