e9466d585c0020f8391ec6c266033764c789c8db
[freeside.git] / rt / lib / RT / ObjectCustomFieldValues.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::ObjectCustomFieldValues;
50
51 use strict;
52 use warnings;
53 use 5.010;
54
55 use base 'RT::SearchBuilder';
56
57 use RT::ObjectCustomFieldValue;
58
59 # Set up the OCFV cache for faster comparison on add/update
60 our $_OCFV_CACHE;
61 ClearOCFVCache();
62
63 sub Table { 'ObjectCustomFieldValues'}
64
65 sub _Init {
66     my $self = shift;
67
68   # By default, order by SortOrder
69   $self->OrderByCols(
70          { ALIAS => 'main',
71            FIELD => 'SortOrder',
72            ORDER => 'ASC' },
73          { ALIAS => 'main',
74            FIELD => 'id',
75            ORDER => 'ASC' },
76      );
77
78     return ( $self->SUPER::_Init(@_) );
79 }
80
81 =head2 ClearOCFVCache
82
83 Cleans out and reinitializes the OCFV cache
84
85 =cut
86
87 sub ClearOCFVCache {
88     $_OCFV_CACHE = {}
89 }
90
91 # {{{ sub LimitToCustomField
92
93 =head2 LimitToCustomField FIELD
94
95 Limits the returned set to values for the custom field with Id FIELD
96
97 =cut
98   
99 sub LimitToCustomField {
100     my $self = shift;
101     my $cf = shift;
102     return $self->Limit(
103         FIELD => 'CustomField',
104         VALUE => $cf,
105     );
106 }
107
108
109
110 =head2 LimitToObject OBJECT
111
112 Limits the returned set to values for the given OBJECT
113
114 =cut
115
116 sub LimitToObject {
117     my $self = shift;
118     my $object = shift;
119     $self->Limit(
120         FIELD => 'ObjectType',
121         VALUE => ref($object),
122     );
123     return $self->Limit(
124         FIELD => 'ObjectId',
125         VALUE => $object->Id,
126     );
127
128 }
129
130
131 =head2 HasEntry CONTENT LARGE_CONTENT
132
133 If this collection has an entry with content that eq CONTENT and large content
134 that eq LARGE_CONTENT then returns the entry, otherwise returns undef.
135
136 =cut
137
138
139 sub HasEntry {
140     my $self = shift;
141     my $value = shift;
142     my $large_content = shift;
143     return undef unless defined $value && length $value;
144
145     my $first = $self->First;
146     return undef unless $first;  # No entries to check
147
148     # Key should be the same for all values of the same ocfv
149     my $ocfv_key = $first->GetOCFVCacheKey;
150
151     # This cache relieves performance issues when adding large numbers of values
152     # to a CF since each add compares against the full list each time.
153
154     unless ( $_OCFV_CACHE->{$ocfv_key} ) {
155         # Load the cache with existing values
156         foreach my $item ( @{$self->ItemsArrayRef} ) {
157             push @{$_OCFV_CACHE->{$ocfv_key}}, {
158                 'ObjectId'       => $item->Id,
159                 'CustomFieldObj' => $item->CustomFieldObj,
160                 'Content'        => $item->_Value('Content'),
161                 'LargeContent'   => $item->LargeContent };
162         }
163     }
164
165     my %canon_value;
166     my $item_id;
167     foreach my $item ( @{$_OCFV_CACHE->{$ocfv_key}} ) {
168         my $cf = $item->{'CustomFieldObj'};
169         my $args = $canon_value{ $cf->Type };
170         if ( !$args ) {
171             $args = { Content => $value, LargeContent => $large_content };
172             my ($ok, $msg) = $cf->_CanonicalizeValue( $args );
173             next unless $ok;
174             $canon_value{ $cf->Type } = $args;
175         }
176
177         if ( $cf->Type eq 'Select' ) {
178             # select is case insensitive
179             $item_id = $item->{'ObjectId'} if lc $item->{'Content'} eq lc $args->{Content};
180         }
181         else {
182             if ( ($item->{'Content'} // '') eq $args->{Content} ) {
183                 if ( defined $item->{'LargeContent'} ) {
184                     $item_id = $item->{'ObjectId'}
185                       if defined $args->{LargeContent}
186                       && $item->{'LargeContent'} eq $args->{LargeContent};
187                 }
188                 else {
189                     $item_id = $item->{'ObjectId'} unless defined $args->{LargeContent};
190                 }
191             } elsif ( $item->{'LargeContent'} && $args->{Content} ) {
192                 $item_id = $item->{'ObjectId'} if ($item->{'LargeContent'} eq $args->{Content});
193             }
194         }
195         last if $item_id;
196     }
197
198     if ( $item_id ) {
199         my $ocfv = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
200         my ($ret, $msg) = $ocfv->Load($item_id);
201         RT::Logger->error("Unable to load object custom field value from id: $item_id $msg")
202             unless $ret;
203         return $ocfv;
204     }
205     else {
206         return undef;
207     }
208 }
209
210 sub _DoSearch {
211     my $self = shift;
212     
213     # unless we really want to find disabled rows,
214     # make sure we're only finding enabled ones.
215     unless ( $self->{'find_expired_rows'} ) {
216         $self->LimitToEnabled();
217     }
218     
219     return $self->SUPER::_DoSearch(@_);
220 }
221
222 sub _DoCount {
223     my $self = shift;
224     
225     # unless we really want to find disabled rows,
226     # make sure we're only finding enabled ones.
227     unless ( $self->{'find_expired_rows'} ) {
228         $self->LimitToEnabled();
229     }
230     
231     return $self->SUPER::_DoCount(@_);
232 }
233
234 RT::Base->_ImportOverlays();
235
236 # Clear the OCVF cache on exit to release connected RT::Ticket objects.
237 #
238 # Without this, there could be warnings generated like "Too late to safely run
239 # transaction-batch scrips...". You can test this by commenting it out and running
240 # some cf tests, e.g. perl -Ilib t/customfields/enter_one.t
241 END { ClearOCFVCache(); }
242
243
244 1;