1# BEGIN BPS TAGGED BLOCK {{{ 2# 3# COPYRIGHT: 4# 5# This software is Copyright (c) 1996-2021 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 49package RT::CustomFieldValues::External; 50 51use strict; 52use warnings; 53 54use base qw(RT::CustomFieldValues); 55 56=head1 NAME 57 58RT::CustomFieldValues::External - Pull possible values for a custom 59field from an arbitrary external data source. 60 61=head1 SYNOPSIS 62 63Custom field value lists can be produced by creating a class that 64inherits from C<RT::CustomFieldValues::External>, and overloading 65C<SourceDescription> and C<ExternalValues>. See 66L<RT::CustomFieldValues::Groups> for a simple example. 67 68=head1 DESCRIPTION 69 70Subclasses should implement the following methods: 71 72=head2 SourceDescription 73 74This method should return a string describing the data source; this is 75the identifier by which the user will see the dropdown. 76 77=head2 ExternalValues 78 79This method should return an array reference of hash references. The 80hash references must contain a key for C<name> and can optionally contain 81keys for C<description>, C<sortorder>, and C<category>. If supplying a 82category, you must also set the category the custom field is based on in 83the custom field configuration page. 84 85=head1 SEE ALSO 86 87F<docs/extending/external_custom_fields.pod> 88 89=cut 90 91sub _Init { 92 my $self = shift; 93 $self->Table( '' ); 94 return ( $self->SUPER::_Init(@_) ); 95} 96 97sub 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 106sub _ClonedAttributes { 107 my $self = shift; 108 return qw( 109 __external_cf 110 __external_cf_limits 111 ), $self->SUPER::_ClonedAttributes; 112} 113 114sub 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 124sub __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 151sub __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 173sub _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 195 foreach( $self->_SortValues( @{ $self->ExternalValues } ) ) { 196 my $value = $self->NewItem; 197 $value->LoadFromHash( { %defaults, %$_ } ); 198 next if $check && !$check->( $self, $value ); 199 $self->AddRecord( $value ); 200 last if $self->RowsPerPage and ++$i >= $self->RowsPerPage; 201 } 202 $self->{'must_redo_search'} = 0; 203 return $self->_RecordCount; 204} 205 206sub _SortValues { 207 my $self = shift; 208 my @items = @_; 209 210 no warnings 'uninitialized'; 211 212 return sort { $a->{sortorder} <=> $b->{sortorder} || lc($a->{name}) cmp lc($b->{name}) } 213 @items; 214} 215 216sub _DoCount { 217 my $self = shift; 218 219 my $count; 220 $count = $self->_DoSearch if $self->{'must_redo_search'}; 221 $count = $self->_RecordCount unless defined $count; 222 223 return $self->{'count_all'} = $self->{'raw_rows'} = $count; 224} 225 226sub LimitToCustomField { 227 my $self = shift; 228 $self->{'__external_cf'} = $_[0]; 229 return $self->SUPER::LimitToCustomField( @_ ); 230} 231 232sub _SingularClass { 233 "RT::CustomFieldValue" 234} 235 236RT::Base->_ImportOverlays(); 237 2381; 239