]> git.uio.no Git - usit-rt.git/blame - lib/RT/ObjectCustomFieldValue.pm
Upgrade to 4.2.2
[usit-rt.git] / lib / RT / ObjectCustomFieldValue.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
320f0092 5# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
84fb5b46
MKG
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::ObjectCustomFieldValue;
50
51use strict;
52use warnings;
af59614d 53use base 'RT::Record';
84fb5b46
MKG
54
55use RT::Interface::Web;
56use Regexp::Common qw(RE_net_IPv4);
57use Regexp::IPv6 qw($IPv6_re);
58use Regexp::Common::net::CIDR;
59require Net::CIDR;
60
61# Allow the empty IPv6 address
62$IPv6_re = qr/(?:$IPv6_re|::)/;
63
84fb5b46 64use RT::CustomField;
84fb5b46
MKG
65
66sub Table {'ObjectCustomFieldValues'}
67
68
69
70
71sub Create {
72 my $self = shift;
73 my %args = (
74 CustomField => 0,
75 ObjectType => '',
76 ObjectId => 0,
77 Disabled => 0,
78 Content => '',
79 LargeContent => undef,
80 ContentType => '',
81 ContentEncoding => '',
82 @_,
83 );
84
85
86 my $cf_as_sys = RT::CustomField->new(RT->SystemUser);
87 $cf_as_sys->Load($args{'CustomField'});
88
89 if($cf_as_sys->Type eq 'IPAddress') {
90 if ( $args{'Content'} ) {
91 $args{'Content'} = $self->ParseIP( $args{'Content'} );
92 }
93
94 unless ( defined $args{'Content'} ) {
95 return
96 wantarray
97 ? ( 0, $self->loc("Content is an invalid IP address") )
98 : 0;
99 }
100 }
101
102 if($cf_as_sys->Type eq 'IPAddressRange') {
103 if ($args{'Content'}) {
104 ($args{'Content'}, $args{'LargeContent'}) = $self->ParseIPRange( $args{'Content'} );
105 }
106 $args{'ContentType'} = 'text/plain';
107
108 unless ( defined $args{'Content'} ) {
109 return
110 wantarray
111 ? ( 0, $self->loc("Content is an invalid IP address range") )
112 : 0;
113 }
114 }
115
116 if ( defined $args{'Content'} && length( Encode::encode_utf8($args{'Content'}) ) > 255 ) {
117 if ( defined $args{'LargeContent'} && length $args{'LargeContent'} ) {
118 $RT::Logger->error("Content is longer than 255 bytes and LargeContent specified");
119 }
120 else {
121 $args{'LargeContent'} = $args{'Content'};
af59614d 122 $args{'Content'} = undef;
84fb5b46
MKG
123 $args{'ContentType'} ||= 'text/plain';
124 }
125 }
126
127 ( $args{'ContentEncoding'}, $args{'LargeContent'} ) =
128 $self->_EncodeLOB( $args{'LargeContent'}, $args{'ContentType'} )
129 if defined $args{'LargeContent'};
130
131 return $self->SUPER::Create(
132 CustomField => $args{'CustomField'},
133 ObjectType => $args{'ObjectType'},
134 ObjectId => $args{'ObjectId'},
135 Disabled => $args{'Disabled'},
136 Content => $args{'Content'},
137 LargeContent => $args{'LargeContent'},
138 ContentType => $args{'ContentType'},
139 ContentEncoding => $args{'ContentEncoding'},
140 );
141}
142
143
144sub LargeContent {
145 my $self = shift;
146 return $self->_DecodeLOB(
147 $self->ContentType,
148 $self->ContentEncoding,
149 $self->_Value( 'LargeContent', decode_utf8 => 0 )
150 );
151}
152
153
154=head2 LoadByCols
155
156=cut
157
158sub LoadByCols {
159 my $self = shift;
160 my %args = (@_);
161 my $cf;
162 if ( $args{CustomField} ) {
163 $cf = RT::CustomField->new( $self->CurrentUser );
164 $cf->Load( $args{CustomField} );
165 if ( $cf->Type && $cf->Type eq 'IPAddressRange' ) {
166
167 my ( $sIP, $eIP ) = $cf->ParseIPRange( $args{'Content'} );
168 if ( $sIP && $eIP ) {
169 $self->SUPER::LoadByCols( %args,
170 Content => $sIP,
171 LargeContent => $eIP
172 );
173 }
174 }
175 }
176 return $self->SUPER::LoadByCols(%args);
177}
178
179=head2 LoadByTicketContentAndCustomField { Ticket => TICKET, CustomField => CUSTOMFIELD, Content => CONTENT }
180
181Loads a custom field value by Ticket, Content and which CustomField it's tied to
182
183=cut
184
185
186sub LoadByTicketContentAndCustomField {
187 my $self = shift;
188 my %args = (
189 Ticket => undef,
190 CustomField => undef,
191 Content => undef,
192 @_
193 );
194
195 return $self->LoadByCols(
196 Content => $args{'Content'},
197 CustomField => $args{'CustomField'},
198 ObjectType => 'RT::Ticket',
199 ObjectId => $args{'Ticket'},
200 Disabled => 0
201 );
202}
203
204sub LoadByObjectContentAndCustomField {
205 my $self = shift;
206 my %args = (
207 Object => undef,
208 CustomField => undef,
209 Content => undef,
210 @_
211 );
212
213 my $obj = $args{'Object'} or return;
214
215 return $self->LoadByCols(
216 Content => $args{'Content'},
217 CustomField => $args{'CustomField'},
218 ObjectType => ref($obj),
219 ObjectId => $obj->Id,
220 Disabled => 0
221 );
222}
223
224=head2 CustomFieldObj
225
226Returns the CustomField Object which has the id returned by CustomField
227
228=cut
229
230sub CustomFieldObj {
231 my $self = shift;
232 my $CustomField = RT::CustomField->new( $self->CurrentUser );
233 $CustomField->SetContextObject( $self->Object );
234 $CustomField->Load( $self->__Value('CustomField') );
235 return $CustomField;
236}
237
238
239=head2 Content
240
241Return this custom field's content. If there's no "regular"
242content, try "LargeContent"
243
244=cut
245
246my $re_ip_sunit = qr/[0-1][0-9][0-9]|2[0-4][0-9]|25[0-5]/;
247my $re_ip_serialized = qr/$re_ip_sunit(?:\.$re_ip_sunit){3}/;
248
249sub Content {
250 my $self = shift;
251
252 return undef unless $self->CustomFieldObj->CurrentUserHasRight('SeeCustomField');
253
254 my $content = $self->_Value('Content');
255 if ( $self->CustomFieldObj->Type eq 'IPAddress'
256 || $self->CustomFieldObj->Type eq 'IPAddressRange' )
257 {
258
259 if ( $content =~ /^\s*($re_ip_serialized)\s*$/o ) {
260 $content = sprintf "%d.%d.%d.%d", split /\./, $1;
261 }
262
263 return $content if $self->CustomFieldObj->Type eq 'IPAddress';
264
265 my $large_content = $self->__Value('LargeContent');
266 if ( $large_content =~ /^\s*($re_ip_serialized)\s*$/o ) {
267 my $eIP = sprintf "%d.%d.%d.%d", split /\./, $1;
268 if ( $content eq $eIP ) {
269 return $content;
270 }
271 else {
272 return $content . "-" . $eIP;
273 }
274 }
275 elsif ( $large_content =~ /^\s*($IPv6_re)\s*$/o ) {
276 my $eIP = $1;
277 if ( $content eq $eIP ) {
278 return $content;
279 }
280 else {
281 return $content . "-" . $eIP;
282 }
283 }
284 else {
285 return $content;
286 }
287 }
288
289 if ( !(defined $content && length $content) && $self->ContentType && $self->ContentType eq 'text/plain' ) {
290 return $self->LargeContent;
291 } else {
292 return $content;
293 }
294}
295
296=head2 Object
297
298Returns the object this value applies to
299
300=cut
301
302sub Object {
303 my $self = shift;
304 my $Object = $self->__Value('ObjectType')->new( $self->CurrentUser );
305 $Object->LoadById( $self->__Value('ObjectId') );
306 return $Object;
307}
308
309
310=head2 Delete
311
312Disable this value. Used to remove "current" values from records while leaving them in the history.
313
314=cut
315
316
317sub Delete {
318 my $self = shift;
319 return $self->SetDisabled(1);
320}
321
322=head2 _FillInTemplateURL URL
323
324Takes a URL containing placeholders and returns the URL as filled in for this
325ObjectCustomFieldValue. The values for the placeholders will be URI-escaped.
326
327Available placeholders:
328
329=over
330
331=item __id__
332
333The id of the object in question.
334
335=item __CustomField__
336
337The value of this custom field for the object in question.
338
339=item __WebDomain__, __WebPort__, __WebPath__, __WebBaseURL__ and __WebURL__
340
341The value of the config option.
342
343=back
344
345=cut
346
347{
348my %placeholders = (
349 id => { value => sub { $_[0]->ObjectId }, escape => 1 },
350 CustomField => { value => sub { $_[0]->Content }, escape => 1 },
351 WebDomain => { value => sub { RT->Config->Get('WebDomain') } },
352 WebPort => { value => sub { RT->Config->Get('WebPort') } },
353 WebPath => { value => sub { RT->Config->Get('WebPath') } },
354 WebBaseURL => { value => sub { RT->Config->Get('WebBaseURL') } },
355 WebURL => { value => sub { RT->Config->Get('WebURL') } },
356);
357
358sub _FillInTemplateURL {
359 my $self = shift;
360 my $url = shift;
361
362 return undef unless defined $url && length $url;
363
364 # special case, whole value should be an URL
365 if ( $url =~ /^__CustomField__/ ) {
366 my $value = $self->Content;
367 # protect from potentially malicious URLs
368 if ( $value =~ /^\s*(?:javascript|data):/i ) {
369 my $object = $self->Object;
370 $RT::Logger->error(
371 "Potentially dangerous URL type in custom field '". $self->CustomFieldObj->Name ."'"
372 ." on ". ref($object) ." #". $object->id
373 );
374 return undef;
375 }
376 $url =~ s/^__CustomField__/$value/;
377 }
378
379 # default value, uri-escape
380 for my $key (keys %placeholders) {
381 $url =~ s{__${key}__}{
382 my $value = $placeholders{$key}{'value'}->( $self );
383 $value = '' if !defined($value);
384 RT::Interface::Web::EscapeURI(\$value) if $placeholders{$key}{'escape'};
385 $value
386 }gxe;
387 }
388
389 return $url;
390} }
391
392
393=head2 ValueLinkURL
394
395Returns a filled in URL template for this ObjectCustomFieldValue, suitable for
396constructing a hyperlink in RT's webui. Returns undef if this custom field doesn't have
397a LinkValueTo
398
399=cut
400
401sub LinkValueTo {
402 my $self = shift;
403 return $self->_FillInTemplateURL($self->CustomFieldObj->LinkValueTo);
404}
405
406
407
408=head2 ValueIncludeURL
409
410Returns a filled in URL template for this ObjectCustomFieldValue, suitable for
411constructing a hyperlink in RT's webui. Returns undef if this custom field doesn't have
412a IncludeContentForValue
413
414=cut
415
416sub IncludeContentForValue {
417 my $self = shift;
418 return $self->_FillInTemplateURL($self->CustomFieldObj->IncludeContentForValue);
419}
420
421
422sub ParseIPRange {
423 my $self = shift;
424 my $value = shift or return;
425 $value = lc $value;
426 $value =~ s!^\s+!!;
427 $value =~ s!\s+$!!;
428
429 if ( $value =~ /^$RE{net}{CIDR}{IPv4}{-keep}$/go ) {
430 my $cidr = join( '.', map $_||0, (split /\./, $1)[0..3] ) ."/$2";
431 $value = (Net::CIDR::cidr2range( $cidr ))[0] || $value;
432 }
433 elsif ( $value =~ /^$IPv6_re(?:\/\d+)?$/o ) {
434 $value = (Net::CIDR::cidr2range( $value ))[0] || $value;
435 }
436
437 my ($sIP, $eIP);
438 if ( $value =~ /^($RE{net}{IPv4})$/o ) {
439 $sIP = $eIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $1;
440 }
441 elsif ( $value =~ /^($RE{net}{IPv4})-($RE{net}{IPv4})$/o ) {
442 $sIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $1;
443 $eIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $2;
444 }
445 elsif ( $value =~ /^($IPv6_re)$/o ) {
446 $sIP = $self->ParseIP( $1 );
447 $eIP = $sIP;
448 }
449 elsif ( $value =~ /^($IPv6_re)-($IPv6_re)$/o ) {
450 ($sIP, $eIP) = ( $1, $2 );
451 $sIP = $self->ParseIP( $sIP );
452 $eIP = $self->ParseIP( $eIP );
453 }
454 else {
455 return;
456 }
457
458 ($sIP, $eIP) = ($eIP, $sIP) if $sIP gt $eIP;
459
460 return $sIP, $eIP;
461}
462
463sub ParseIP {
464 my $self = shift;
465 my $value = shift or return;
466 $value = lc $value;
467 $value =~ s!^\s+!!;
468 $value =~ s!\s+$!!;
469
470 if ( $value =~ /^($RE{net}{IPv4})$/o ) {
471 return sprintf "%03d.%03d.%03d.%03d", split /\./, $1;
472 }
473 elsif ( $value =~ /^$IPv6_re$/o ) {
474
475 # up_fields are before '::'
476 # low_fields are after '::' but without v4
477 # v4_fields are the v4
478 my ( @up_fields, @low_fields, @v4_fields );
479 my $v6;
480 if ( $value =~ /(.*:)(\d+\..*)/ ) {
481 ( $v6, my $v4 ) = ( $1, $2 );
482 chop $v6 unless $v6 =~ /::$/;
483 while ( $v4 =~ /(\d+)\.(\d+)/g ) {
484 push @v4_fields, sprintf '%.2x%.2x', $1, $2;
485 }
486 }
487 else {
488 $v6 = $value;
489 }
490
491 my ( $up, $low );
492 if ( $v6 =~ /::/ ) {
493 ( $up, $low ) = split /::/, $v6;
494 }
495 else {
496 $up = $v6;
497 }
498
499 @up_fields = split /:/, $up;
500 @low_fields = split /:/, $low if $low;
501
502 my @zero_fields =
503 ('0000') x ( 8 - @v4_fields - @up_fields - @low_fields );
504 my @fields = ( @up_fields, @zero_fields, @low_fields, @v4_fields );
505
506 return join ':', map { sprintf "%.4x", hex "0x$_" } @fields;
507 }
508 return;
509}
510
511
512=head2 id
513
514Returns the current value of id.
515(In the database, id is stored as int(11).)
516
517
518=cut
519
520
521=head2 CustomField
522
523Returns the current value of CustomField.
524(In the database, CustomField is stored as int(11).)
525
526
527
528=head2 SetCustomField VALUE
529
530
531Set CustomField to VALUE.
532Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
533(In the database, CustomField will be stored as a int(11).)
534
535
536=cut
537
538=head2 ObjectType
539
540Returns the current value of ObjectType.
541(In the database, ObjectType is stored as varchar(255).)
542
543
544
545=head2 SetObjectType VALUE
546
547
548Set ObjectType to VALUE.
549Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
550(In the database, ObjectType will be stored as a varchar(255).)
551
552
553=cut
554
555
556=head2 ObjectId
557
558Returns the current value of ObjectId.
559(In the database, ObjectId is stored as int(11).)
560
561
562
563=head2 SetObjectId VALUE
564
565
566Set ObjectId to VALUE.
567Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
568(In the database, ObjectId will be stored as a int(11).)
569
570
571=cut
572
573
574=head2 SortOrder
575
576Returns the current value of SortOrder.
577(In the database, SortOrder is stored as int(11).)
578
579
580
581=head2 SetSortOrder VALUE
582
583
584Set SortOrder to VALUE.
585Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
586(In the database, SortOrder will be stored as a int(11).)
587
588
589=cut
590
591
592=head2 Content
593
594Returns the current value of Content.
595(In the database, Content is stored as varchar(255).)
596
597
598
599=head2 SetContent VALUE
600
601
602Set Content to VALUE.
603Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
604(In the database, Content will be stored as a varchar(255).)
605
606
607=cut
608
609
610=head2 LargeContent
611
612Returns the current value of LargeContent.
613(In the database, LargeContent is stored as longblob.)
614
615
616
617=head2 SetLargeContent VALUE
618
619
620Set LargeContent to VALUE.
621Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
622(In the database, LargeContent will be stored as a longblob.)
623
624
625=cut
626
627
628=head2 ContentType
629
630Returns the current value of ContentType.
631(In the database, ContentType is stored as varchar(80).)
632
633
634
635=head2 SetContentType VALUE
636
637
638Set ContentType to VALUE.
639Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
640(In the database, ContentType will be stored as a varchar(80).)
641
642
643=cut
644
645
646=head2 ContentEncoding
647
648Returns the current value of ContentEncoding.
649(In the database, ContentEncoding is stored as varchar(80).)
650
651
652
653=head2 SetContentEncoding VALUE
654
655
656Set ContentEncoding to VALUE.
657Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
658(In the database, ContentEncoding will be stored as a varchar(80).)
659
660
661=cut
662
663
664=head2 Creator
665
666Returns the current value of Creator.
667(In the database, Creator is stored as int(11).)
668
669
670=cut
671
672
673=head2 Created
674
675Returns the current value of Created.
676(In the database, Created is stored as datetime.)
677
678
679=cut
680
681
682=head2 LastUpdatedBy
683
684Returns the current value of LastUpdatedBy.
685(In the database, LastUpdatedBy is stored as int(11).)
686
687
688=cut
689
690
691=head2 LastUpdated
692
693Returns the current value of LastUpdated.
694(In the database, LastUpdated is stored as datetime.)
695
696
697=cut
698
699
700=head2 Disabled
701
702Returns the current value of Disabled.
703(In the database, Disabled is stored as smallint(6).)
704
705
706
707=head2 SetDisabled VALUE
708
709
710Set Disabled to VALUE.
711Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
712(In the database, Disabled will be stored as a smallint(6).)
713
714
715=cut
716
717
718
719sub _CoreAccessible {
720 {
721
722 id =>
af59614d 723 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
84fb5b46 724 CustomField =>
af59614d 725 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
84fb5b46 726 ObjectType =>
af59614d 727 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
84fb5b46 728 ObjectId =>
af59614d 729 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
84fb5b46 730 SortOrder =>
af59614d 731 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
84fb5b46 732 Content =>
af59614d 733 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
84fb5b46 734 LargeContent =>
af59614d 735 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longblob', default => ''},
84fb5b46 736 ContentType =>
af59614d 737 {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
84fb5b46 738 ContentEncoding =>
af59614d 739 {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
84fb5b46 740 Creator =>
af59614d 741 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
84fb5b46 742 Created =>
af59614d 743 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
84fb5b46 744 LastUpdatedBy =>
af59614d 745 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
84fb5b46 746 LastUpdated =>
af59614d 747 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
84fb5b46 748 Disabled =>
af59614d 749 {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
84fb5b46
MKG
750
751 }
752};
753
af59614d
MKG
754sub FindDependencies {
755 my $self = shift;
756 my ($walker, $deps) = @_;
757
758 $self->SUPER::FindDependencies($walker, $deps);
759
760 $deps->Add( out => $self->CustomFieldObj );
761 $deps->Add( out => $self->Object );
762}
763
84fb5b46
MKG
764RT::Base->_ImportOverlays();
765
7661;