X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FObjectCustomFieldValue.pm;h=de4bc748dd9d6423cdae0b85547c222bf8087d48;hb=e9e0cf0989259b94d9758eceff448666a2e5a5cc;hp=5f51ee6108d38c681ca4ee2ebe4d100c883deae7;hpb=b5c4237a34aef94976bc343c8d9e138664fc3984;p=freeside.git diff --git a/rt/lib/RT/ObjectCustomFieldValue.pm b/rt/lib/RT/ObjectCustomFieldValue.pm index 5f51ee610..de4bc748d 100644 --- a/rt/lib/RT/ObjectCustomFieldValue.pm +++ b/rt/lib/RT/ObjectCustomFieldValue.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -46,99 +46,474 @@ # # END BPS TAGGED BLOCK }}} -# Autogenerated by DBIx::SearchBuilder factory (by ) -# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST. -# -# !! DO NOT EDIT THIS FILE !! -# +package RT::ObjectCustomFieldValue; use strict; +use warnings; + +use RT::Interface::Web; +use Regexp::Common qw(RE_net_IPv4); +use Regexp::IPv6 qw($IPv6_re); +use Regexp::Common::net::CIDR; +require Net::CIDR; + +# Allow the empty IPv6 address +$IPv6_re = qr/(?:$IPv6_re|::)/; + + + +use RT::CustomField; +use base 'RT::Record'; + +sub Table {'ObjectCustomFieldValues'} + + + +sub Create { + my $self = shift; + my %args = ( + CustomField => 0, + ObjectType => '', + ObjectId => 0, + Disabled => 0, + Content => '', + LargeContent => undef, + ContentType => '', + ContentEncoding => '', + @_, + ); + + + my $cf_as_sys = RT::CustomField->new(RT->SystemUser); + $cf_as_sys->Load($args{'CustomField'}); + + if($cf_as_sys->Type eq 'IPAddress') { + if ( $args{'Content'} ) { + $args{'Content'} = $self->ParseIP( $args{'Content'} ); + } + + unless ( defined $args{'Content'} ) { + return + wantarray + ? ( 0, $self->loc("Content is an invalid IP address") ) + : 0; + } + } + + if($cf_as_sys->Type eq 'IPAddressRange') { + if ($args{'Content'}) { + ($args{'Content'}, $args{'LargeContent'}) = $self->ParseIPRange( $args{'Content'} ); + } + $args{'ContentType'} = 'text/plain'; + + unless ( defined $args{'Content'} ) { + return + wantarray + ? ( 0, $self->loc("Content is an invalid IP address range") ) + : 0; + } + } + + if ( defined $args{'Content'} && length( Encode::encode_utf8($args{'Content'}) ) > 255 ) { + if ( defined $args{'LargeContent'} && length $args{'LargeContent'} ) { + $RT::Logger->error("Content is longer than 255 bytes and LargeContent specified"); + } + else { + $args{'LargeContent'} = $args{'Content'}; + $args{'Content'} = ''; + $args{'ContentType'} ||= 'text/plain'; + } + } + + ( $args{'ContentEncoding'}, $args{'LargeContent'} ) = + $self->_EncodeLOB( $args{'LargeContent'}, $args{'ContentType'} ) + if defined $args{'LargeContent'}; + + return $self->SUPER::Create( + CustomField => $args{'CustomField'}, + ObjectType => $args{'ObjectType'}, + ObjectId => $args{'ObjectId'}, + Disabled => $args{'Disabled'}, + Content => $args{'Content'}, + LargeContent => $args{'LargeContent'}, + ContentType => $args{'ContentType'}, + ContentEncoding => $args{'ContentEncoding'}, + ); +} + + +sub LargeContent { + my $self = shift; + return $self->_DecodeLOB( + $self->ContentType, + $self->ContentEncoding, + $self->_Value( 'LargeContent', decode_utf8 => 0 ) + ); +} -=head1 NAME -RT::ObjectCustomFieldValue +=head2 LoadByCols +=cut -=head1 SYNOPSIS +sub LoadByCols { + my $self = shift; + my %args = (@_); + my $cf; + if ( $args{CustomField} ) { + $cf = RT::CustomField->new( $self->CurrentUser ); + $cf->Load( $args{CustomField} ); + if ( $cf->Type && $cf->Type eq 'IPAddressRange' ) { + + my ( $sIP, $eIP ) = $cf->ParseIPRange( $args{'Content'} ); + if ( $sIP && $eIP ) { + $self->SUPER::LoadByCols( %args, + Content => $sIP, + LargeContent => $eIP + ); + } + } + } + return $self->SUPER::LoadByCols(%args); +} -=head1 DESCRIPTION +=head2 LoadByTicketContentAndCustomField { Ticket => TICKET, CustomField => CUSTOMFIELD, Content => CONTENT } -=head1 METHODS +Loads a custom field value by Ticket, Content and which CustomField it's tied to =cut -package RT::ObjectCustomFieldValue; -use RT::Record; -use RT::CustomField; +sub LoadByTicketContentAndCustomField { + my $self = shift; + my %args = ( + Ticket => undef, + CustomField => undef, + Content => undef, + @_ + ); + + return $self->LoadByCols( + Content => $args{'Content'}, + CustomField => $args{'CustomField'}, + ObjectType => 'RT::Ticket', + ObjectId => $args{'Ticket'}, + Disabled => 0 + ); +} + +sub LoadByObjectContentAndCustomField { + my $self = shift; + my %args = ( + Object => undef, + CustomField => undef, + Content => undef, + @_ + ); + + my $obj = $args{'Object'} or return; + + return $self->LoadByCols( + Content => $args{'Content'}, + CustomField => $args{'CustomField'}, + ObjectType => ref($obj), + ObjectId => $obj->Id, + Disabled => 0 + ); +} + +=head2 CustomFieldObj -use vars qw( @ISA ); -@ISA= qw( RT::Record ); +Returns the CustomField Object which has the id returned by CustomField -sub _Init { - my $self = shift; +=cut - $self->Table('ObjectCustomFieldValues'); - $self->SUPER::_Init(@_); +sub CustomFieldObj { + my $self = shift; + my $CustomField = RT::CustomField->new( $self->CurrentUser ); + $CustomField->SetContextObject( $self->Object ); + $CustomField->Load( $self->__Value('CustomField') ); + return $CustomField; } +=head2 Content + +Return this custom field's content. If there's no "regular" +content, try "LargeContent" + +=cut + +my $re_ip_sunit = qr/[0-1][0-9][0-9]|2[0-4][0-9]|25[0-5]/; +my $re_ip_serialized = qr/$re_ip_sunit(?:\.$re_ip_sunit){3}/; + +sub Content { + my $self = shift; + return undef unless $self->CustomFieldObj->CurrentUserHasRight('SeeCustomField'); + my $content = $self->_Value('Content'); + if ( $self->CustomFieldObj->Type eq 'IPAddress' + || $self->CustomFieldObj->Type eq 'IPAddressRange' ) + { -=head2 Create PARAMHASH + if ( $content =~ /^\s*($re_ip_serialized)\s*$/o ) { + $content = sprintf "%d.%d.%d.%d", split /\./, $1; + } + + return $content if $self->CustomFieldObj->Type eq 'IPAddress'; + + my $large_content = $self->__Value('LargeContent'); + if ( $large_content =~ /^\s*($re_ip_serialized)\s*$/o ) { + my $eIP = sprintf "%d.%d.%d.%d", split /\./, $1; + if ( $content eq $eIP ) { + return $content; + } + else { + return $content . "-" . $eIP; + } + } + elsif ( $large_content =~ /^\s*($IPv6_re)\s*$/o ) { + my $eIP = $1; + if ( $content eq $eIP ) { + return $content; + } + else { + return $content . "-" . $eIP; + } + } + else { + return $content; + } + } + + if ( !(defined $content && length $content) && $self->ContentType && $self->ContentType eq 'text/plain' ) { + return $self->LargeContent; + } else { + return $content; + } +} -Create takes a hash of values and creates a row in the database: +=head2 Object - int(11) 'CustomField'. - varchar(255) 'ObjectType'. - int(11) 'ObjectId'. - int(11) 'SortOrder'. - varchar(255) 'Content'. - longtext 'LargeContent'. - varchar(80) 'ContentType'. - varchar(80) 'ContentEncoding'. - smallint(6) 'Disabled'. +Returns the object this value applies to =cut +sub Object { + my $self = shift; + my $Object = $self->__Value('ObjectType')->new( $self->CurrentUser ); + $Object->LoadById( $self->__Value('ObjectId') ); + return $Object; +} + +=head2 Delete +Disable this value. Used to remove "current" values from records while leaving them in the history. -sub Create { +=cut + + +sub Delete { my $self = shift; - my %args = ( - CustomField => '0', - ObjectType => '', - ObjectId => '0', - SortOrder => '0', - Content => '', - LargeContent => '', - ContentType => '', - ContentEncoding => '', - Disabled => '0', - - @_); - $self->SUPER::Create( - CustomField => $args{'CustomField'}, - ObjectType => $args{'ObjectType'}, - ObjectId => $args{'ObjectId'}, - SortOrder => $args{'SortOrder'}, - Content => $args{'Content'}, - LargeContent => $args{'LargeContent'}, - ContentType => $args{'ContentType'}, - ContentEncoding => $args{'ContentEncoding'}, - Disabled => $args{'Disabled'}, + return $self->SetDisabled(1); +} + +=head2 _FillInTemplateURL URL + +Takes a URL containing placeholders and returns the URL as filled in for this +ObjectCustomFieldValue. The values for the placeholders will be URI-escaped. + +Available placeholders: + +=over + +=item __id__ + +The id of the object in question. + +=item __CustomField__ + +The value of this custom field for the object in question. + +=item __WebDomain__, __WebPort__, __WebPath__, __WebBaseURL__ and __WebURL__ + +The value of the config option. + +=back + +=cut + +{ +my %placeholders = ( + id => { value => sub { $_[0]->ObjectId }, escape => 1 }, + CustomField => { value => sub { $_[0]->Content }, escape => 1 }, + WebDomain => { value => sub { RT->Config->Get('WebDomain') } }, + WebPort => { value => sub { RT->Config->Get('WebPort') } }, + WebPath => { value => sub { RT->Config->Get('WebPath') } }, + WebBaseURL => { value => sub { RT->Config->Get('WebBaseURL') } }, + WebURL => { value => sub { RT->Config->Get('WebURL') } }, ); +sub _FillInTemplateURL { + my $self = shift; + my $url = shift; + + return undef unless defined $url && length $url; + + # special case, whole value should be an URL + if ( $url =~ /^__CustomField__/ ) { + my $value = $self->Content; + # protect from potentially malicious URLs + if ( $value =~ /^\s*(?:javascript|data):/i ) { + my $object = $self->Object; + $RT::Logger->error( + "Potentially dangerous URL type in custom field '". $self->CustomFieldObj->Name ."'" + ." on ". ref($object) ." #". $object->id + ); + return undef; + } + $url =~ s/^__CustomField__/$value/; + } + + # default value, uri-escape + for my $key (keys %placeholders) { + $url =~ s{__${key}__}{ + my $value = $placeholders{$key}{'value'}->( $self ); + $value = '' if !defined($value); + RT::Interface::Web::EscapeURI(\$value) if $placeholders{$key}{'escape'}; + $value + }gxe; + } + + return $url; +} } + + +=head2 ValueLinkURL + +Returns a filled in URL template for this ObjectCustomFieldValue, suitable for +constructing a hyperlink in RT's webui. Returns undef if this custom field doesn't have +a LinkValueTo + +=cut + +sub LinkValueTo { + my $self = shift; + return $self->_FillInTemplateURL($self->CustomFieldObj->LinkValueTo); +} + + + +=head2 ValueIncludeURL + +Returns a filled in URL template for this ObjectCustomFieldValue, suitable for +constructing a hyperlink in RT's webui. Returns undef if this custom field doesn't have +a IncludeContentForValue + +=cut + +sub IncludeContentForValue { + my $self = shift; + return $self->_FillInTemplateURL($self->CustomFieldObj->IncludeContentForValue); +} + + +sub ParseIPRange { + my $self = shift; + my $value = shift or return; + $value = lc $value; + $value =~ s!^\s+!!; + $value =~ s!\s+$!!; + + if ( $value =~ /^$RE{net}{CIDR}{IPv4}{-keep}$/go ) { + my $cidr = join( '.', map $_||0, (split /\./, $1)[0..3] ) ."/$2"; + $value = (Net::CIDR::cidr2range( $cidr ))[0] || $value; + } + elsif ( $value =~ /^$IPv6_re(?:\/\d+)?$/o ) { + $value = (Net::CIDR::cidr2range( $value ))[0] || $value; + } + + my ($sIP, $eIP); + if ( $value =~ /^($RE{net}{IPv4})$/o ) { + $sIP = $eIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $1; + } + elsif ( $value =~ /^($RE{net}{IPv4})-($RE{net}{IPv4})$/o ) { + $sIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $1; + $eIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $2; + } + elsif ( $value =~ /^($IPv6_re)$/o ) { + $sIP = $self->ParseIP( $1 ); + $eIP = $sIP; + } + elsif ( $value =~ /^($IPv6_re)-($IPv6_re)$/o ) { + ($sIP, $eIP) = ( $1, $2 ); + $sIP = $self->ParseIP( $sIP ); + $eIP = $self->ParseIP( $eIP ); + } + else { + return; + } + + ($sIP, $eIP) = ($eIP, $sIP) if $sIP gt $eIP; + + return $sIP, $eIP; } +sub ParseIP { + my $self = shift; + my $value = shift or return; + $value = lc $value; + $value =~ s!^\s+!!; + $value =~ s!\s+$!!; + + if ( $value =~ /^($RE{net}{IPv4})$/o ) { + return sprintf "%03d.%03d.%03d.%03d", split /\./, $1; + } + elsif ( $value =~ /^$IPv6_re$/o ) { + + # up_fields are before '::' + # low_fields are after '::' but without v4 + # v4_fields are the v4 + my ( @up_fields, @low_fields, @v4_fields ); + my $v6; + if ( $value =~ /(.*:)(\d+\..*)/ ) { + ( $v6, my $v4 ) = ( $1, $2 ); + chop $v6 unless $v6 =~ /::$/; + while ( $v4 =~ /(\d+)\.(\d+)/g ) { + push @v4_fields, sprintf '%.2x%.2x', $1, $2; + } + } + else { + $v6 = $value; + } + + my ( $up, $low ); + if ( $v6 =~ /::/ ) { + ( $up, $low ) = split /::/, $v6; + } + else { + $up = $v6; + } + + @up_fields = split /:/, $up; + @low_fields = split /:/, $low if $low; + + my @zero_fields = + ('0000') x ( 8 - @v4_fields - @up_fields - @low_fields ); + my @fields = ( @up_fields, @zero_fields, @low_fields, @v4_fields ); + + return join ':', map { sprintf "%.4x", hex "0x$_" } @fields; + } + return; +} =head2 id -Returns the current value of id. +Returns the current value of id. (In the database, id is stored as int(11).) @@ -147,7 +522,7 @@ Returns the current value of id. =head2 CustomField -Returns the current value of CustomField. +Returns the current value of CustomField. (In the database, CustomField is stored as int(11).) @@ -155,17 +530,16 @@ Returns the current value of CustomField. =head2 SetCustomField VALUE -Set CustomField to VALUE. +Set CustomField to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, CustomField will be stored as a int(11).) =cut - =head2 ObjectType -Returns the current value of ObjectType. +Returns the current value of ObjectType. (In the database, ObjectType is stored as varchar(255).) @@ -173,7 +547,7 @@ Returns the current value of ObjectType. =head2 SetObjectType VALUE -Set ObjectType to VALUE. +Set ObjectType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectType will be stored as a varchar(255).) @@ -183,7 +557,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =head2 ObjectId -Returns the current value of ObjectId. +Returns the current value of ObjectId. (In the database, ObjectId is stored as int(11).) @@ -191,7 +565,7 @@ Returns the current value of ObjectId. =head2 SetObjectId VALUE -Set ObjectId to VALUE. +Set ObjectId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectId will be stored as a int(11).) @@ -201,7 +575,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =head2 SortOrder -Returns the current value of SortOrder. +Returns the current value of SortOrder. (In the database, SortOrder is stored as int(11).) @@ -209,7 +583,7 @@ Returns the current value of SortOrder. =head2 SetSortOrder VALUE -Set SortOrder to VALUE. +Set SortOrder to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, SortOrder will be stored as a int(11).) @@ -219,7 +593,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =head2 Content -Returns the current value of Content. +Returns the current value of Content. (In the database, Content is stored as varchar(255).) @@ -227,7 +601,7 @@ Returns the current value of Content. =head2 SetContent VALUE -Set Content to VALUE. +Set Content to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Content will be stored as a varchar(255).) @@ -237,17 +611,17 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =head2 LargeContent -Returns the current value of LargeContent. -(In the database, LargeContent is stored as longtext.) +Returns the current value of LargeContent. +(In the database, LargeContent is stored as longblob.) =head2 SetLargeContent VALUE -Set LargeContent to VALUE. +Set LargeContent to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. -(In the database, LargeContent will be stored as a longtext.) +(In the database, LargeContent will be stored as a longblob.) =cut @@ -255,7 +629,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =head2 ContentType -Returns the current value of ContentType. +Returns the current value of ContentType. (In the database, ContentType is stored as varchar(80).) @@ -263,7 +637,7 @@ Returns the current value of ContentType. =head2 SetContentType VALUE -Set ContentType to VALUE. +Set ContentType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ContentType will be stored as a varchar(80).) @@ -273,7 +647,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =head2 ContentEncoding -Returns the current value of ContentEncoding. +Returns the current value of ContentEncoding. (In the database, ContentEncoding is stored as varchar(80).) @@ -281,7 +655,7 @@ Returns the current value of ContentEncoding. =head2 SetContentEncoding VALUE -Set ContentEncoding to VALUE. +Set ContentEncoding to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ContentEncoding will be stored as a varchar(80).) @@ -291,7 +665,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =head2 Creator -Returns the current value of Creator. +Returns the current value of Creator. (In the database, Creator is stored as int(11).) @@ -300,7 +674,7 @@ Returns the current value of Creator. =head2 Created -Returns the current value of Created. +Returns the current value of Created. (In the database, Created is stored as datetime.) @@ -309,7 +683,7 @@ Returns the current value of Created. =head2 LastUpdatedBy -Returns the current value of LastUpdatedBy. +Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) @@ -318,7 +692,7 @@ Returns the current value of LastUpdatedBy. =head2 LastUpdated -Returns the current value of LastUpdated. +Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) @@ -327,7 +701,7 @@ Returns the current value of LastUpdated. =head2 Disabled -Returns the current value of Disabled. +Returns the current value of Disabled. (In the database, Disabled is stored as smallint(6).) @@ -335,7 +709,7 @@ Returns the current value of Disabled. =head2 SetDisabled VALUE -Set Disabled to VALUE. +Set Disabled to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Disabled will be stored as a smallint(6).) @@ -346,34 +720,34 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. sub _CoreAccessible { { - + id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, - CustomField => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, - ObjectType => + CustomField => + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + ObjectType => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, - ObjectId => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, - SortOrder => + ObjectId => + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + SortOrder => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, - Content => + Content => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, - LargeContent => - {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longtext', default => ''}, - ContentType => + LargeContent => + {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longblob', default => ''}, + ContentType => {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''}, - ContentEncoding => + ContentEncoding => {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''}, - Creator => + Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, - Created => + Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, - LastUpdatedBy => + LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, - LastUpdated => + LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, - Disabled => + Disabled => {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, } @@ -381,23 +755,4 @@ sub _CoreAccessible { RT::Base->_ImportOverlays(); -=head1 SEE ALSO - -This class allows "overlay" methods to be placed -into the following files _Overlay is for a System overlay by the original author, -_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations. - -These overlay files can contain new subs or subs to replace existing subs in this module. - -Each of these files should begin with the line - - no warnings qw(redefine); - -so that perl does not kick and scream when you redefine a subroutine or variable in your overlay. - -RT::ObjectCustomFieldValue_Overlay, RT::ObjectCustomFieldValue_Vendor, RT::ObjectCustomFieldValue_Local - -=cut - - 1;