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;
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 => '',
88 my $cf_as_sys = RT::CustomField->new(RT->SystemUser);
89 $cf_as_sys->Load($args{'CustomField'});
91 if($cf_as_sys->Type eq 'IPAddress') {
92 if ( $args{'Content'} ) {
93 $args{'Content'} = $self->ParseIP( $args{'Content'} );
96 unless ( defined $args{'Content'} ) {
99 ? ( 0, $self->loc("Content is an invalid IP address") )
104 if($cf_as_sys->Type eq 'IPAddressRange') {
105 if ($args{'Content'}) {
106 ($args{'Content'}, $args{'LargeContent'}) = $self->ParseIPRange( $args{'Content'} );
108 $args{'ContentType'} = 'text/plain';
110 unless ( defined $args{'Content'} ) {
113 ? ( 0, $self->loc("Content is an invalid IP address range") )
118 if ( defined $args{'Content'} && length( Encode::encode_utf8($args{'Content'}) ) > 255 ) {
119 if ( defined $args{'LargeContent'} && length $args{'LargeContent'} ) {
120 $RT::Logger->error("Content is longer than 255 bytes and LargeContent specified");
123 $args{'LargeContent'} = $args{'Content'};
124 $args{'Content'} = '';
125 $args{'ContentType'} ||= 'text/plain';
129 ( $args{'ContentEncoding'}, $args{'LargeContent'} ) =
130 $self->_EncodeLOB( $args{'LargeContent'}, $args{'ContentType'} )
131 if defined $args{'LargeContent'};
133 return $self->SUPER::Create(
134 CustomField => $args{'CustomField'},
135 ObjectType => $args{'ObjectType'},
136 ObjectId => $args{'ObjectId'},
137 Disabled => $args{'Disabled'},
138 Content => $args{'Content'},
139 LargeContent => $args{'LargeContent'},
140 ContentType => $args{'ContentType'},
141 ContentEncoding => $args{'ContentEncoding'},
148 return $self->_DecodeLOB(
150 $self->ContentEncoding,
151 $self->_Value( 'LargeContent', decode_utf8 => 0 )
164 if ( $args{CustomField} ) {
165 $cf = RT::CustomField->new( $self->CurrentUser );
166 $cf->Load( $args{CustomField} );
167 if ( $cf->Type && $cf->Type eq 'IPAddressRange' ) {
169 my ( $sIP, $eIP ) = $cf->ParseIPRange( $args{'Content'} );
170 if ( $sIP && $eIP ) {
171 $self->SUPER::LoadByCols( %args,
178 return $self->SUPER::LoadByCols(%args);
181 =head2 LoadByTicketContentAndCustomField { Ticket => TICKET, CustomField => CUSTOMFIELD, Content => CONTENT }
183 Loads a custom field value by Ticket, Content and which CustomField it's tied to
188 sub LoadByTicketContentAndCustomField {
192 CustomField => undef,
197 return $self->LoadByCols(
198 Content => $args{'Content'},
199 CustomField => $args{'CustomField'},
200 ObjectType => 'RT::Ticket',
201 ObjectId => $args{'Ticket'},
206 sub LoadByObjectContentAndCustomField {
210 CustomField => undef,
215 my $obj = $args{'Object'} or return;
217 return $self->LoadByCols(
218 Content => $args{'Content'},
219 CustomField => $args{'CustomField'},
220 ObjectType => ref($obj),
221 ObjectId => $obj->Id,
226 =head2 CustomFieldObj
228 Returns the CustomField Object which has the id returned by CustomField
234 my $CustomField = RT::CustomField->new( $self->CurrentUser );
235 $CustomField->SetContextObject( $self->Object );
236 $CustomField->Load( $self->__Value('CustomField') );
243 Return this custom field's content. If there's no "regular"
244 content, try "LargeContent"
248 my $re_ip_sunit = qr/[0-1][0-9][0-9]|2[0-4][0-9]|25[0-5]/;
249 my $re_ip_serialized = qr/$re_ip_sunit(?:\.$re_ip_sunit){3}/;
254 return undef unless $self->CustomFieldObj->CurrentUserHasRight('SeeCustomField');
256 my $content = $self->_Value('Content');
257 if ( $self->CustomFieldObj->Type eq 'IPAddress'
258 || $self->CustomFieldObj->Type eq 'IPAddressRange' )
261 if ( $content =~ /^\s*($re_ip_serialized)\s*$/o ) {
262 $content = sprintf "%d.%d.%d.%d", split /\./, $1;
265 return $content if $self->CustomFieldObj->Type eq 'IPAddress';
267 my $large_content = $self->__Value('LargeContent');
268 if ( $large_content =~ /^\s*($re_ip_serialized)\s*$/o ) {
269 my $eIP = sprintf "%d.%d.%d.%d", split /\./, $1;
270 if ( $content eq $eIP ) {
274 return $content . "-" . $eIP;
277 elsif ( $large_content =~ /^\s*($IPv6_re)\s*$/o ) {
279 if ( $content eq $eIP ) {
283 return $content . "-" . $eIP;
291 if ( !(defined $content && length $content) && $self->ContentType && $self->ContentType eq 'text/plain' ) {
292 return $self->LargeContent;
300 Returns the object this value applies to
306 my $Object = $self->__Value('ObjectType')->new( $self->CurrentUser );
307 $Object->LoadById( $self->__Value('ObjectId') );
314 Disable this value. Used to remove "current" values from records while leaving them in the history.
321 return $self->SetDisabled(1);
324 =head2 _FillInTemplateURL URL
326 Takes a URL containing placeholders and returns the URL as filled in for this
327 ObjectCustomFieldValue. The values for the placeholders will be URI-escaped.
329 Available placeholders:
335 The id of the object in question.
337 =item __CustomField__
339 The value of this custom field for the object in question.
341 =item __WebDomain__, __WebPort__, __WebPath__, __WebBaseURL__ and __WebURL__
343 The value of the config option.
351 id => { value => sub { $_[0]->ObjectId }, escape => 1 },
352 CustomField => { value => sub { $_[0]->Content }, escape => 1 },
353 WebDomain => { value => sub { RT->Config->Get('WebDomain') } },
354 WebPort => { value => sub { RT->Config->Get('WebPort') } },
355 WebPath => { value => sub { RT->Config->Get('WebPath') } },
356 WebBaseURL => { value => sub { RT->Config->Get('WebBaseURL') } },
357 WebURL => { value => sub { RT->Config->Get('WebURL') } },
360 sub _FillInTemplateURL {
364 return undef unless defined $url && length $url;
366 # special case, whole value should be an URL
367 if ( $url =~ /^__CustomField__/ ) {
368 my $value = $self->Content;
369 # protect from potentially malicious URLs
370 if ( $value =~ /^\s*(?:javascript|data):/i ) {
371 my $object = $self->Object;
373 "Potentially dangerous URL type in custom field '". $self->CustomFieldObj->Name ."'"
374 ." on ". ref($object) ." #". $object->id
378 $url =~ s/^__CustomField__/$value/;
381 # default value, uri-escape
382 for my $key (keys %placeholders) {
383 $url =~ s{__${key}__}{
384 my $value = $placeholders{$key}{'value'}->( $self );
385 $value = '' if !defined($value);
386 RT::Interface::Web::EscapeURI(\$value) if $placeholders{$key}{'escape'};
397 Returns a filled in URL template for this ObjectCustomFieldValue, suitable for
398 constructing a hyperlink in RT's webui. Returns undef if this custom field doesn't have
405 return $self->_FillInTemplateURL($self->CustomFieldObj->LinkValueTo);
410 =head2 ValueIncludeURL
412 Returns a filled in URL template for this ObjectCustomFieldValue, suitable for
413 constructing a hyperlink in RT's webui. Returns undef if this custom field doesn't have
414 a IncludeContentForValue
418 sub IncludeContentForValue {
420 return $self->_FillInTemplateURL($self->CustomFieldObj->IncludeContentForValue);
426 my $value = shift or return;
431 if ( $value =~ /^$RE{net}{CIDR}{IPv4}{-keep}$/go ) {
432 my $cidr = join( '.', map $_||0, (split /\./, $1)[0..3] ) ."/$2";
433 $value = (Net::CIDR::cidr2range( $cidr ))[0] || $value;
435 elsif ( $value =~ /^$IPv6_re(?:\/\d+)?$/o ) {
436 $value = (Net::CIDR::cidr2range( $value ))[0] || $value;
440 if ( $value =~ /^($RE{net}{IPv4})$/o ) {
441 $sIP = $eIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $1;
443 elsif ( $value =~ /^($RE{net}{IPv4})-($RE{net}{IPv4})$/o ) {
444 $sIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $1;
445 $eIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $2;
447 elsif ( $value =~ /^($IPv6_re)$/o ) {
448 $sIP = $self->ParseIP( $1 );
451 elsif ( $value =~ /^($IPv6_re)-($IPv6_re)$/o ) {
452 ($sIP, $eIP) = ( $1, $2 );
453 $sIP = $self->ParseIP( $sIP );
454 $eIP = $self->ParseIP( $eIP );
460 ($sIP, $eIP) = ($eIP, $sIP) if $sIP gt $eIP;
467 my $value = shift or return;
472 if ( $value =~ /^($RE{net}{IPv4})$/o ) {
473 return sprintf "%03d.%03d.%03d.%03d", split /\./, $1;
475 elsif ( $value =~ /^$IPv6_re$/o ) {
477 # up_fields are before '::'
478 # low_fields are after '::' but without v4
479 # v4_fields are the v4
480 my ( @up_fields, @low_fields, @v4_fields );
482 if ( $value =~ /(.*:)(\d+\..*)/ ) {
483 ( $v6, my $v4 ) = ( $1, $2 );
484 chop $v6 unless $v6 =~ /::$/;
485 while ( $v4 =~ /(\d+)\.(\d+)/g ) {
486 push @v4_fields, sprintf '%.2x%.2x', $1, $2;
495 ( $up, $low ) = split /::/, $v6;
501 @up_fields = split /:/, $up;
502 @low_fields = split /:/, $low if $low;
505 ('0000') x ( 8 - @v4_fields - @up_fields - @low_fields );
506 my @fields = ( @up_fields, @zero_fields, @low_fields, @v4_fields );
508 return join ':', map { sprintf "%.4x", hex "0x$_" } @fields;
516 Returns the current value of id.
517 (In the database, id is stored as int(11).)
525 Returns the current value of CustomField.
526 (In the database, CustomField is stored as int(11).)
530 =head2 SetCustomField VALUE
533 Set CustomField to VALUE.
534 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
535 (In the database, CustomField will be stored as a int(11).)
542 Returns the current value of ObjectType.
543 (In the database, ObjectType is stored as varchar(255).)
547 =head2 SetObjectType VALUE
550 Set ObjectType to VALUE.
551 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
552 (In the database, ObjectType will be stored as a varchar(255).)
560 Returns the current value of ObjectId.
561 (In the database, ObjectId is stored as int(11).)
565 =head2 SetObjectId VALUE
568 Set ObjectId to VALUE.
569 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
570 (In the database, ObjectId will be stored as a int(11).)
578 Returns the current value of SortOrder.
579 (In the database, SortOrder is stored as int(11).)
583 =head2 SetSortOrder VALUE
586 Set SortOrder to VALUE.
587 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
588 (In the database, SortOrder will be stored as a int(11).)
596 Returns the current value of Content.
597 (In the database, Content is stored as varchar(255).)
601 =head2 SetContent VALUE
604 Set Content to VALUE.
605 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
606 (In the database, Content will be stored as a varchar(255).)
614 Returns the current value of LargeContent.
615 (In the database, LargeContent is stored as longblob.)
619 =head2 SetLargeContent VALUE
622 Set LargeContent to VALUE.
623 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
624 (In the database, LargeContent will be stored as a longblob.)
632 Returns the current value of ContentType.
633 (In the database, ContentType is stored as varchar(80).)
637 =head2 SetContentType VALUE
640 Set ContentType to VALUE.
641 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
642 (In the database, ContentType will be stored as a varchar(80).)
648 =head2 ContentEncoding
650 Returns the current value of ContentEncoding.
651 (In the database, ContentEncoding is stored as varchar(80).)
655 =head2 SetContentEncoding VALUE
658 Set ContentEncoding to VALUE.
659 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
660 (In the database, ContentEncoding will be stored as a varchar(80).)
668 Returns the current value of Creator.
669 (In the database, Creator is stored as int(11).)
677 Returns the current value of Created.
678 (In the database, Created is stored as datetime.)
686 Returns the current value of LastUpdatedBy.
687 (In the database, LastUpdatedBy is stored as int(11).)
695 Returns the current value of LastUpdated.
696 (In the database, LastUpdated is stored as datetime.)
704 Returns the current value of Disabled.
705 (In the database, Disabled is stored as smallint(6).)
709 =head2 SetDisabled VALUE
712 Set Disabled to VALUE.
713 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
714 (In the database, Disabled will be stored as a smallint(6).)
721 sub _CoreAccessible {
725 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
727 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
729 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
731 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
733 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
735 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
737 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longblob', default => ''},
739 {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
741 {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
743 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
745 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
747 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
749 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
751 {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
756 RT::Base->_ImportOverlays();