1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
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
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.
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.
30 # CONTRIBUTION SUBMISSION POLICY:
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.)
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.
47 # END BPS TAGGED BLOCK }}}
49 package RT::ObjectCustomFieldValue;
53 use base 'RT::Record';
55 use RT::Interface::Web;
56 use Regexp::Common qw(RE_net_IPv4);
57 use Regexp::IPv6 qw($IPv6_re);
58 use Regexp::Common::net::CIDR;
61 # Allow the empty IPv6 address
62 $IPv6_re = qr/(?:$IPv6_re|::)/;
66 sub Table {'ObjectCustomFieldValues'}
79 LargeContent => undef,
81 ContentEncoding => '',
86 my $cf_as_sys = RT::CustomField->new(RT->SystemUser);
87 $cf_as_sys->Load($args{'CustomField'});
89 if($cf_as_sys->Type eq 'IPAddress') {
90 if ( $args{'Content'} ) {
91 $args{'Content'} = $self->ParseIP( $args{'Content'} );
94 unless ( defined $args{'Content'} ) {
97 ? ( 0, $self->loc("Content is an invalid IP address") )
102 if($cf_as_sys->Type eq 'IPAddressRange') {
103 if ($args{'Content'}) {
104 ($args{'Content'}, $args{'LargeContent'}) = $self->ParseIPRange( $args{'Content'} );
106 $args{'ContentType'} = 'text/plain';
108 unless ( defined $args{'Content'} ) {
111 ? ( 0, $self->loc("Content is an invalid IP address range") )
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");
121 $args{'LargeContent'} = $args{'Content'};
122 $args{'Content'} = undef;
123 $args{'ContentType'} ||= 'text/plain';
127 ( $args{'ContentEncoding'}, $args{'LargeContent'} ) =
128 $self->_EncodeLOB( $args{'LargeContent'}, $args{'ContentType'} )
129 if defined $args{'LargeContent'};
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'},
146 return $self->_DecodeLOB(
148 $self->ContentEncoding,
149 $self->_Value( 'LargeContent', decode_utf8 => 0 )
162 if ( $args{CustomField} ) {
163 $cf = RT::CustomField->new( $self->CurrentUser );
164 $cf->Load( $args{CustomField} );
165 if ( $cf->Type && $cf->Type eq 'IPAddressRange' ) {
167 my ( $sIP, $eIP ) = $cf->ParseIPRange( $args{'Content'} );
168 if ( $sIP && $eIP ) {
169 $self->SUPER::LoadByCols( %args,
176 return $self->SUPER::LoadByCols(%args);
179 =head2 LoadByTicketContentAndCustomField { Ticket => TICKET, CustomField => CUSTOMFIELD, Content => CONTENT }
181 Loads a custom field value by Ticket, Content and which CustomField it's tied to
186 sub LoadByTicketContentAndCustomField {
190 CustomField => undef,
195 return $self->LoadByCols(
196 Content => $args{'Content'},
197 CustomField => $args{'CustomField'},
198 ObjectType => 'RT::Ticket',
199 ObjectId => $args{'Ticket'},
204 sub LoadByObjectContentAndCustomField {
208 CustomField => undef,
213 my $obj = $args{'Object'} or return;
215 return $self->LoadByCols(
216 Content => $args{'Content'},
217 CustomField => $args{'CustomField'},
218 ObjectType => ref($obj),
219 ObjectId => $obj->Id,
224 =head2 CustomFieldObj
226 Returns the CustomField Object which has the id returned by CustomField
232 my $CustomField = RT::CustomField->new( $self->CurrentUser );
233 $CustomField->SetContextObject( $self->Object );
234 $CustomField->Load( $self->__Value('CustomField') );
241 Return this custom field's content. If there's no "regular"
242 content, try "LargeContent"
246 my $re_ip_sunit = qr/[0-1][0-9][0-9]|2[0-4][0-9]|25[0-5]/;
247 my $re_ip_serialized = qr/$re_ip_sunit(?:\.$re_ip_sunit){3}/;
252 return undef unless $self->CustomFieldObj->CurrentUserHasRight('SeeCustomField');
254 my $content = $self->_Value('Content');
255 if ( $self->CustomFieldObj->Type eq 'IPAddress'
256 || $self->CustomFieldObj->Type eq 'IPAddressRange' )
259 if ( $content =~ /^\s*($re_ip_serialized)\s*$/o ) {
260 $content = sprintf "%d.%d.%d.%d", split /\./, $1;
263 return $content if $self->CustomFieldObj->Type eq 'IPAddress';
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 ) {
272 return $content . "-" . $eIP;
275 elsif ( $large_content =~ /^\s*($IPv6_re)\s*$/o ) {
277 if ( $content eq $eIP ) {
281 return $content . "-" . $eIP;
289 if ( !(defined $content && length $content) && $self->ContentType && $self->ContentType eq 'text/plain' ) {
290 return $self->LargeContent;
298 Returns the object this value applies to
304 my $Object = $self->__Value('ObjectType')->new( $self->CurrentUser );
305 $Object->LoadById( $self->__Value('ObjectId') );
312 Disable this value. Used to remove "current" values from records while leaving them in the history.
319 return $self->SetDisabled(1);
322 =head2 _FillInTemplateURL URL
324 Takes a URL containing placeholders and returns the URL as filled in for this
325 ObjectCustomFieldValue. The values for the placeholders will be URI-escaped.
327 Available placeholders:
333 The id of the object in question.
335 =item __CustomField__
337 The value of this custom field for the object in question.
339 =item __WebDomain__, __WebPort__, __WebPath__, __WebBaseURL__ and __WebURL__
341 The value of the config option.
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') } },
358 sub _FillInTemplateURL {
362 return undef unless defined $url && length $url;
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;
371 "Potentially dangerous URL type in custom field '". $self->CustomFieldObj->Name ."'"
372 ." on ". ref($object) ." #". $object->id
376 $url =~ s/^__CustomField__/$value/;
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'};
395 Returns a filled in URL template for this ObjectCustomFieldValue, suitable for
396 constructing a hyperlink in RT's webui. Returns undef if this custom field doesn't have
403 return $self->_FillInTemplateURL($self->CustomFieldObj->LinkValueTo);
408 =head2 ValueIncludeURL
410 Returns a filled in URL template for this ObjectCustomFieldValue, suitable for
411 constructing a hyperlink in RT's webui. Returns undef if this custom field doesn't have
412 a IncludeContentForValue
416 sub IncludeContentForValue {
418 return $self->_FillInTemplateURL($self->CustomFieldObj->IncludeContentForValue);
424 my $value = shift or return;
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;
433 elsif ( $value =~ /^$IPv6_re(?:\/\d+)?$/o ) {
434 $value = (Net::CIDR::cidr2range( $value ))[0] || $value;
438 if ( $value =~ /^($RE{net}{IPv4})$/o ) {
439 $sIP = $eIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $1;
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;
445 elsif ( $value =~ /^($IPv6_re)$/o ) {
446 $sIP = $self->ParseIP( $1 );
449 elsif ( $value =~ /^($IPv6_re)-($IPv6_re)$/o ) {
450 ($sIP, $eIP) = ( $1, $2 );
451 $sIP = $self->ParseIP( $sIP );
452 $eIP = $self->ParseIP( $eIP );
458 ($sIP, $eIP) = ($eIP, $sIP) if $sIP gt $eIP;
465 my $value = shift or return;
470 if ( $value =~ /^($RE{net}{IPv4})$/o ) {
471 return sprintf "%03d.%03d.%03d.%03d", split /\./, $1;
473 elsif ( $value =~ /^$IPv6_re$/o ) {
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 );
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;
493 ( $up, $low ) = split /::/, $v6;
499 @up_fields = split /:/, $up;
500 @low_fields = split /:/, $low if $low;
503 ('0000') x ( 8 - @v4_fields - @up_fields - @low_fields );
504 my @fields = ( @up_fields, @zero_fields, @low_fields, @v4_fields );
506 return join ':', map { sprintf "%.4x", hex "0x$_" } @fields;
514 Returns the current value of id.
515 (In the database, id is stored as int(11).)
523 Returns the current value of CustomField.
524 (In the database, CustomField is stored as int(11).)
528 =head2 SetCustomField VALUE
531 Set CustomField to VALUE.
532 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
533 (In the database, CustomField will be stored as a int(11).)
540 Returns the current value of ObjectType.
541 (In the database, ObjectType is stored as varchar(255).)
545 =head2 SetObjectType VALUE
548 Set ObjectType to VALUE.
549 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
550 (In the database, ObjectType will be stored as a varchar(255).)
558 Returns the current value of ObjectId.
559 (In the database, ObjectId is stored as int(11).)
563 =head2 SetObjectId VALUE
566 Set ObjectId to VALUE.
567 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
568 (In the database, ObjectId will be stored as a int(11).)
576 Returns the current value of SortOrder.
577 (In the database, SortOrder is stored as int(11).)
581 =head2 SetSortOrder VALUE
584 Set SortOrder to VALUE.
585 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
586 (In the database, SortOrder will be stored as a int(11).)
594 Returns the current value of Content.
595 (In the database, Content is stored as varchar(255).)
599 =head2 SetContent VALUE
602 Set Content to VALUE.
603 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
604 (In the database, Content will be stored as a varchar(255).)
612 Returns the current value of LargeContent.
613 (In the database, LargeContent is stored as longblob.)
617 =head2 SetLargeContent VALUE
620 Set LargeContent to VALUE.
621 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
622 (In the database, LargeContent will be stored as a longblob.)
630 Returns the current value of ContentType.
631 (In the database, ContentType is stored as varchar(80).)
635 =head2 SetContentType VALUE
638 Set ContentType to VALUE.
639 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
640 (In the database, ContentType will be stored as a varchar(80).)
646 =head2 ContentEncoding
648 Returns the current value of ContentEncoding.
649 (In the database, ContentEncoding is stored as varchar(80).)
653 =head2 SetContentEncoding VALUE
656 Set ContentEncoding to VALUE.
657 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
658 (In the database, ContentEncoding will be stored as a varchar(80).)
666 Returns the current value of Creator.
667 (In the database, Creator is stored as int(11).)
675 Returns the current value of Created.
676 (In the database, Created is stored as datetime.)
684 Returns the current value of LastUpdatedBy.
685 (In the database, LastUpdatedBy is stored as int(11).)
693 Returns the current value of LastUpdated.
694 (In the database, LastUpdated is stored as datetime.)
702 Returns the current value of Disabled.
703 (In the database, Disabled is stored as smallint(6).)
707 =head2 SetDisabled VALUE
710 Set Disabled to VALUE.
711 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
712 (In the database, Disabled will be stored as a smallint(6).)
719 sub _CoreAccessible {
723 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
725 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
727 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
729 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
731 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
733 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
735 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longblob', default => ''},
737 {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
739 {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
741 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
743 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
745 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
747 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
749 {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
754 sub FindDependencies {
756 my ($walker, $deps) = @_;
758 $self->SUPER::FindDependencies($walker, $deps);
760 $deps->Add( out => $self->CustomFieldObj );
761 $deps->Add( out => $self->Object );
764 RT::Base->_ImportOverlays();