1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2014 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;
54 use RT::Interface::Web;
55 use Regexp::Common qw(RE_net_IPv4);
56 use Regexp::IPv6 qw($IPv6_re);
57 use Regexp::Common::net::CIDR;
60 # Allow the empty IPv6 address
61 $IPv6_re = qr/(?:$IPv6_re|::)/;
66 use base 'RT::Record';
68 sub Table {'ObjectCustomFieldValues'}
81 LargeContent => undef,
83 ContentEncoding => '',
87 my $cf = RT::CustomField->new( $self->CurrentUser );
88 $cf->Load( $args{CustomField} );
90 my ($val, $msg) = $cf->_CanonicalizeValue(\%args);
91 return ($val, $msg) unless $val;
93 if ( defined $args{'Content'} && length( Encode::encode_utf8($args{'Content'}) ) > 255 ) {
94 if ( defined $args{'LargeContent'} && length $args{'LargeContent'} ) {
95 $RT::Logger->error("Content is longer than 255 bytes and LargeContent specified");
98 $args{'LargeContent'} = $args{'Content'};
99 $args{'Content'} = '';
100 $args{'ContentType'} ||= 'text/plain';
104 ( $args{'ContentEncoding'}, $args{'LargeContent'} ) =
105 $self->_EncodeLOB( $args{'LargeContent'}, $args{'ContentType'} )
106 if defined $args{'LargeContent'};
108 return $self->SUPER::Create(
109 CustomField => $args{'CustomField'},
110 ObjectType => $args{'ObjectType'},
111 ObjectId => $args{'ObjectId'},
112 Disabled => $args{'Disabled'},
113 Content => $args{'Content'},
114 LargeContent => $args{'LargeContent'},
115 ContentType => $args{'ContentType'},
116 ContentEncoding => $args{'ContentEncoding'},
123 return $self->_DecodeLOB(
125 $self->ContentEncoding,
126 $self->_Value( 'LargeContent', decode_utf8 => 0 )
139 if ( $args{CustomField} ) {
140 $cf = RT::CustomField->new( $self->CurrentUser );
141 $cf->Load( $args{CustomField} );
143 my ($ok, $msg) = $cf->_CanonicalizeValue(\%args);
144 return ($ok, $msg) unless $ok;
146 return $self->SUPER::LoadByCols(%args);
149 =head2 LoadByTicketContentAndCustomField { Ticket => TICKET, CustomField => CUSTOMFIELD, Content => CONTENT }
151 Loads a custom field value by Ticket, Content and which CustomField it's tied to
156 sub LoadByTicketContentAndCustomField {
160 CustomField => undef,
165 return $self->LoadByCols(
166 Content => $args{'Content'},
167 CustomField => $args{'CustomField'},
168 ObjectType => 'RT::Ticket',
169 ObjectId => $args{'Ticket'},
174 sub LoadByObjectContentAndCustomField {
178 CustomField => undef,
183 my $obj = $args{'Object'} or return;
185 return $self->LoadByCols(
186 Content => $args{'Content'},
187 CustomField => $args{'CustomField'},
188 ObjectType => ref($obj),
189 ObjectId => $obj->Id,
194 =head2 CustomFieldObj
196 Returns the CustomField Object which has the id returned by CustomField
202 my $CustomField = RT::CustomField->new( $self->CurrentUser );
203 $CustomField->SetContextObject( $self->Object );
204 $CustomField->Load( $self->__Value('CustomField') );
211 Return this custom field's content. If there's no "regular"
212 content, try "LargeContent"
216 my $re_ip_sunit = qr/[0-1][0-9][0-9]|2[0-4][0-9]|25[0-5]/;
217 my $re_ip_serialized = qr/$re_ip_sunit(?:\.$re_ip_sunit){3}/;
222 return undef unless $self->CustomFieldObj->CurrentUserHasRight('SeeCustomField');
224 my $content = $self->_Value('Content');
225 if ( $self->CustomFieldObj->Type eq 'IPAddress'
226 || $self->CustomFieldObj->Type eq 'IPAddressRange' )
229 if ( $content =~ /^\s*($re_ip_serialized)\s*$/o ) {
230 $content = sprintf "%d.%d.%d.%d", split /\./, $1;
233 return $content if $self->CustomFieldObj->Type eq 'IPAddress';
235 my $large_content = $self->__Value('LargeContent');
236 if ( $large_content =~ /^\s*($re_ip_serialized)\s*$/o ) {
237 my $eIP = sprintf "%d.%d.%d.%d", split /\./, $1;
238 if ( $content eq $eIP ) {
242 return $content . "-" . $eIP;
245 elsif ( $large_content =~ /^\s*($IPv6_re)\s*$/o ) {
247 if ( $content eq $eIP ) {
251 return $content . "-" . $eIP;
259 if ( !(defined $content && length $content) && $self->ContentType && $self->ContentType eq 'text/plain' ) {
260 return $self->LargeContent;
268 Returns the object this value applies to
274 my $Object = $self->__Value('ObjectType')->new( $self->CurrentUser );
275 $Object->LoadById( $self->__Value('ObjectId') );
282 Disable this value. Used to remove "current" values from records while leaving them in the history.
289 return $self->SetDisabled(1);
292 =head2 _FillInTemplateURL URL
294 Takes a URL containing placeholders and returns the URL as filled in for this
295 ObjectCustomFieldValue. The values for the placeholders will be URI-escaped.
297 Available placeholders:
303 The id of the object in question.
305 =item __CustomField__
307 The value of this custom field for the object in question.
309 =item __WebDomain__, __WebPort__, __WebPath__, __WebBaseURL__ and __WebURL__
311 The value of the config option.
319 id => { value => sub { $_[0]->ObjectId }, escape => 1 },
320 CustomField => { value => sub { $_[0]->Content }, escape => 1 },
321 WebDomain => { value => sub { RT->Config->Get('WebDomain') } },
322 WebPort => { value => sub { RT->Config->Get('WebPort') } },
323 WebPath => { value => sub { RT->Config->Get('WebPath') } },
324 WebBaseURL => { value => sub { RT->Config->Get('WebBaseURL') } },
325 WebURL => { value => sub { RT->Config->Get('WebURL') } },
328 sub _FillInTemplateURL {
332 return undef unless defined $url && length $url;
334 # special case, whole value should be an URL
335 if ( $url =~ /^__CustomField__/ ) {
336 my $value = $self->Content;
337 # protect from potentially malicious URLs
338 if ( $value =~ /^\s*(?:javascript|data):/i ) {
339 my $object = $self->Object;
341 "Potentially dangerous URL type in custom field '". $self->CustomFieldObj->Name ."'"
342 ." on ". ref($object) ." #". $object->id
346 $url =~ s/^__CustomField__/$value/;
349 # default value, uri-escape
350 for my $key (keys %placeholders) {
351 $url =~ s{__${key}__}{
352 my $value = $placeholders{$key}{'value'}->( $self );
353 $value = '' if !defined($value);
354 RT::Interface::Web::EscapeURI(\$value) if $placeholders{$key}{'escape'};
365 Returns a filled in URL template for this ObjectCustomFieldValue, suitable for
366 constructing a hyperlink in RT's webui. Returns undef if this custom field doesn't have
373 return $self->_FillInTemplateURL($self->CustomFieldObj->LinkValueTo);
378 =head2 ValueIncludeURL
380 Returns a filled in URL template for this ObjectCustomFieldValue, suitable for
381 constructing a hyperlink in RT's webui. Returns undef if this custom field doesn't have
382 a IncludeContentForValue
386 sub IncludeContentForValue {
388 return $self->_FillInTemplateURL($self->CustomFieldObj->IncludeContentForValue);
394 my $value = shift or return;
399 if ( $value =~ /^$RE{net}{CIDR}{IPv4}{-keep}$/go ) {
400 my $cidr = join( '.', map $_||0, (split /\./, $1)[0..3] ) ."/$2";
401 $value = (Net::CIDR::cidr2range( $cidr ))[0] || $value;
403 elsif ( $value =~ /^$IPv6_re(?:\/\d+)?$/o ) {
404 $value = (Net::CIDR::cidr2range( $value ))[0] || $value;
408 if ( $value =~ /^($RE{net}{IPv4})$/o ) {
409 $sIP = $eIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $1;
411 elsif ( $value =~ /^($RE{net}{IPv4})-($RE{net}{IPv4})$/o ) {
412 $sIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $1;
413 $eIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $2;
415 elsif ( $value =~ /^($IPv6_re)$/o ) {
416 $sIP = $self->ParseIP( $1 );
419 elsif ( $value =~ /^($IPv6_re)-($IPv6_re)$/o ) {
420 ($sIP, $eIP) = ( $1, $2 );
421 $sIP = $self->ParseIP( $sIP );
422 $eIP = $self->ParseIP( $eIP );
428 ($sIP, $eIP) = ($eIP, $sIP) if $sIP gt $eIP;
435 my $value = shift or return;
440 if ( $value =~ /^($RE{net}{IPv4})$/o ) {
441 return sprintf "%03d.%03d.%03d.%03d", split /\./, $1;
443 elsif ( $value =~ /^$IPv6_re$/o ) {
445 # up_fields are before '::'
446 # low_fields are after '::' but without v4
447 # v4_fields are the v4
448 my ( @up_fields, @low_fields, @v4_fields );
450 if ( $value =~ /(.*:)(\d+\..*)/ ) {
451 ( $v6, my $v4 ) = ( $1, $2 );
452 chop $v6 unless $v6 =~ /::$/;
453 while ( $v4 =~ /(\d+)\.(\d+)/g ) {
454 push @v4_fields, sprintf '%.2x%.2x', $1, $2;
463 ( $up, $low ) = split /::/, $v6;
469 @up_fields = split /:/, $up;
470 @low_fields = split /:/, $low if $low;
473 ('0000') x ( 8 - @v4_fields - @up_fields - @low_fields );
474 my @fields = ( @up_fields, @zero_fields, @low_fields, @v4_fields );
476 return join ':', map { sprintf "%.4x", hex "0x$_" } @fields;
484 Returns the current value of id.
485 (In the database, id is stored as int(11).)
493 Returns the current value of CustomField.
494 (In the database, CustomField is stored as int(11).)
498 =head2 SetCustomField VALUE
501 Set CustomField to VALUE.
502 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
503 (In the database, CustomField will be stored as a int(11).)
510 Returns the current value of ObjectType.
511 (In the database, ObjectType is stored as varchar(255).)
515 =head2 SetObjectType VALUE
518 Set ObjectType to VALUE.
519 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
520 (In the database, ObjectType will be stored as a varchar(255).)
528 Returns the current value of ObjectId.
529 (In the database, ObjectId is stored as int(11).)
533 =head2 SetObjectId VALUE
536 Set ObjectId to VALUE.
537 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
538 (In the database, ObjectId will be stored as a int(11).)
546 Returns the current value of SortOrder.
547 (In the database, SortOrder is stored as int(11).)
551 =head2 SetSortOrder VALUE
554 Set SortOrder to VALUE.
555 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
556 (In the database, SortOrder will be stored as a int(11).)
564 Returns the current value of Content.
565 (In the database, Content is stored as varchar(255).)
569 =head2 SetContent VALUE
572 Set Content to VALUE.
573 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
574 (In the database, Content will be stored as a varchar(255).)
582 Returns the current value of LargeContent.
583 (In the database, LargeContent is stored as longblob.)
587 =head2 SetLargeContent VALUE
590 Set LargeContent to VALUE.
591 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
592 (In the database, LargeContent will be stored as a longblob.)
600 Returns the current value of ContentType.
601 (In the database, ContentType is stored as varchar(80).)
605 =head2 SetContentType VALUE
608 Set ContentType to VALUE.
609 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
610 (In the database, ContentType will be stored as a varchar(80).)
616 =head2 ContentEncoding
618 Returns the current value of ContentEncoding.
619 (In the database, ContentEncoding is stored as varchar(80).)
623 =head2 SetContentEncoding VALUE
626 Set ContentEncoding to VALUE.
627 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
628 (In the database, ContentEncoding will be stored as a varchar(80).)
636 Returns the current value of Creator.
637 (In the database, Creator is stored as int(11).)
645 Returns the current value of Created.
646 (In the database, Created is stored as datetime.)
654 Returns the current value of LastUpdatedBy.
655 (In the database, LastUpdatedBy is stored as int(11).)
663 Returns the current value of LastUpdated.
664 (In the database, LastUpdated is stored as datetime.)
672 Returns the current value of Disabled.
673 (In the database, Disabled is stored as smallint(6).)
677 =head2 SetDisabled VALUE
680 Set Disabled to VALUE.
681 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
682 (In the database, Disabled will be stored as a smallint(6).)
689 sub _CoreAccessible {
693 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
695 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
697 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
699 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
701 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
703 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
705 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longblob', default => ''},
707 {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
709 {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
711 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
713 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
715 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
717 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
719 {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
724 RT::Base->_ImportOverlays();