diff options
148 files changed, 2746 insertions, 1549 deletions
@@ -284,6 +284,10 @@ L<FS::agent_payment_gateway> - Agent payment gateway class L<FS::cust_svc> - Service class +L<FS::part_export_machine> - Export hostname choice class + +L<FS::svc_export_machine> - Customer export hostname class + L<FS::cust_pkg> - Customer package class L<FS::cust_pkg_option> - Customer package option class diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 0ac269f4c..ca68c3596 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -2421,10 +2421,9 @@ sub ut_coordn { } - =item ut_domain COLUMN -Check/untaint host and domain names. +Check/untaint host and domain names. May not be null. =cut @@ -2432,11 +2431,27 @@ sub ut_domain { my( $self, $field ) = @_; #$self->getfield($field) =~/^(\w+\.)*\w+$/ $self->getfield($field) =~/^(([\w\-]+\.)*\w+)$/ - or return "Illegal (domain) $field: ". $self->getfield($field); + or return "Illegal (hostname) $field: ". $self->getfield($field); $self->setfield($field,$1); ''; } +=item ut_domainn COLUMN + +Check/untaint host and domain names. May be null. + +=cut + +sub ut_domainn { + my( $self, $field ) = @_; + if ( $self->getfield($field) =~ /^()$/ ) { + $self->setfield($field,''); + ''; + } else { + $self->ut_domain($field); + } +} + =item ut_name COLUMN Check/untaint proper names; allows alphanumerics, spaces and the following diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 4ef2a6352..6e3956a83 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1890,6 +1890,29 @@ sub tables_hashref { 'index' => [ [ 'svcnum' ], [ 'optionname' ] ], }, + 'svc_export_machine' => { + 'columns' => [ + 'svcexportmachinenum', 'serial', '', '', '', '', + 'svcnum', 'int', '', '', '', '', + 'machinenum', 'int', '', '', '', '', + ], + 'primary_key' => 'svcexportmachinenum', + 'unique' => [], + 'index' => [], + }, + + 'part_export_machine' => { + 'columns' => [ + 'machinenum', 'serial', '', '', '', '', + 'exportnum', 'int', '', '', '', '', + 'machine', 'varchar', 'NULL', $char_d, '', '', + 'disabled', 'char', 'NULL', 1, '', '', + ], + 'primary_key' => 'machinenum', + 'unique' => [ [ 'exportnum', 'machine' ] ], + 'index' => [ [ 'exportnum' ] ], + }, + 'part_pkg' => { 'columns' => [ 'pkgpart', 'serial', '', '', '', '', @@ -2623,11 +2646,11 @@ sub tables_hashref { 'part_export' => { 'columns' => [ - 'exportnum', 'serial', '', '', '', '', + 'exportnum', 'serial', '', '', '', '', 'exportname', 'varchar', 'NULL', $char_d, '', '', - 'machine', 'varchar', '', $char_d, '', '', - 'exporttype', 'varchar', '', $char_d, '', '', - 'nodomain', 'char', 'NULL', 1, '', '', + 'machine', 'varchar', 'NULL', $char_d, '', '', + 'exporttype', 'varchar', '', $char_d, '', '', + 'nodomain', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'exportnum', 'unique' => [], diff --git a/FS/FS/part_export.pm b/FS/FS/part_export.pm index 45773e097..c757d368d 100644 --- a/FS/FS/part_export.pm +++ b/FS/FS/part_export.pm @@ -4,10 +4,11 @@ use strict; use vars qw( @ISA @EXPORT_OK $DEBUG %exports ); use Exporter; use Tie::IxHash; -use base qw( FS::option_Common FS::m2m_Common ); # m2m for 'export_nas' +use base qw( FS::option_Common FS::m2m_Common ); use FS::Record qw( qsearch qsearchs dbh ); use FS::part_svc; use FS::part_export_option; +use FS::part_export_machine; use FS::export_svc; #for export modules, though they should probably just use it themselves @@ -108,6 +109,50 @@ otherwise returns false. If a hash reference of options is supplied, part_export_option records are created (see L<FS::part_export_option>). +=cut + +sub insert { + my $self = shift; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $error = $self->SUPER::insert(@_); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + #kinda false laziness with process_m2name + my @machines = map { $_ =~ s/^\s+//; $_ =~ s/\s+$//; $_ } + grep /\S/, + split /[\n\r]{1,2}/, + $self->part_export_machine_textarea; + + foreach my $machine ( @machines ) { + + my $part_export_machine = new FS::part_export_machine { + 'exportnum' => $self->exportnum, + 'machine' => $machine, + }; + $error = $part_export_machine->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; +} + =item delete Delete this record from the database. @@ -117,13 +162,13 @@ Delete this record from the database. #foreign keys would make this much less tedious... grr dumb mysql sub delete { my $self = shift; + local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; local $SIG{QUIT} = 'IGNORE'; local $SIG{TERM} = 'IGNORE'; local $SIG{TSTP} = 'IGNORE'; local $SIG{PIPE} = 'IGNORE'; - my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; @@ -147,10 +192,103 @@ sub delete { } } - $dbh->commit or die $dbh->errstr if $oldAutoCommit; + foreach my $part_export_machine ( $self->part_export_machine ) { + my $error = $part_export_machine->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; +} + +=item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ... ] + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +If a list or hash reference of options is supplied, option records are created +or modified. + +=cut + +sub replace { + my $self = shift; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $error = $self->SUPER::replace(@_); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + if ( $self->part_export_machine_textarea ) { + + my %part_export_machine = map { $_->machine => $_ } + $self->part_export_machine; + + my @machines = map { $_ =~ s/^\s+//; $_ =~ s/\s+$//; $_ } + grep /\S/, + split /[\n\r]{1,2}/, + $self->part_export_machine_textarea; + + foreach my $machine ( @machines ) { + + if ( $part_export_machine{$machine} ) { + + if ( $part_export_machine{$machine}->disabled eq 'Y' ) { + $part_export_machine{$machine}->disabled(''); + $error = $part_export_machine{$machine}->replace; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + delete $part_export_machine{$machine}; #so we don't disable it below + + } else { + + my $part_export_machine = new FS::part_export_machine { + 'exportnum' => $self->exportnum, + 'machine' => $machine + }; + $error = $part_export_machine->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + } + + } + + foreach my $part_export_machine ( values %part_export_machine ) { + $part_export_machine->disabled('Y'); + $error = $part_export_machine->replace; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; } =item check @@ -166,7 +304,7 @@ sub check { my $error = $self->ut_numbern('exportnum') || $self->ut_textn('exportname') - || $self->ut_domain('machine') + || $self->ut_domainn('machine') || $self->ut_alpha('exporttype') ; return $error if $error; @@ -192,6 +330,31 @@ sub label { ($self->exportname || $self->exporttype ). ' ('. $self->machine. ')'; } +=item label_html + +Returns a label for this export, "exportname: exporttype to machine". + +=cut + +sub label_html { + my $self = shift; + + my $label = $self->exportname + ? '<B>'. $self->exportname. '</B>: ' #<BR>'. + : ''; + + $label .= $self->exporttype; + + $label .= ' to '. ( $self->machine eq '_SVC_MACHINE' + ? 'per-service hostname' + : $self->machine + ) + if $self->machine; + + $label; + +} + #=item part_svc # #Returns the service definition (see L<FS::part_svc>) for this export. @@ -233,6 +396,20 @@ sub cust_svc { $self->export_svc; } +=item part_export_machine + +Returns all machines as FS::part_export_machine objects (see +L<FS::part_export_machine>). + +=cut + +sub part_export_machine { + my $self = shift; + map { $_ } #behavior of sort undefined in scalar context + sort { $a->machine cmp $b->machine } + qsearch('part_export_machine', { 'exportnum' => $self->exportnum } ); +} + =item export_svc Returns a list of associated FS::export_svc records. diff --git a/FS/FS/part_export/acct_google.pm b/FS/FS/part_export/acct_google.pm index afc45db81..d153728e9 100644 --- a/FS/FS/part_export/acct_google.pm +++ b/FS/FS/part_export/acct_google.pm @@ -16,10 +16,12 @@ tie my %options, 'Tie::IxHash', # admin logins. %info = ( - 'svc' => 'svc_acct', - 'desc' => 'Google hosted mail', - 'options' => \%options, - 'nodomain' => 'Y', + 'svc' => 'svc_acct', + 'desc' => 'Google hosted mail', + 'options' => \%options, + 'nodomain' => 'Y', + 'no_machine' => 1, + 'default_svc_class' => 'Email', 'notes' => <<'END' Export accounts to the Google Provisioning API. Requires REST::Google::Apps::Provisioning from CPAN. diff --git a/FS/FS/part_export/acct_http.pm b/FS/FS/part_export/acct_http.pm index b4c64ac62..23df7b37d 100644 --- a/FS/FS/part_export/acct_http.pm +++ b/FS/FS/part_export/acct_http.pm @@ -51,6 +51,7 @@ tie %options, 'Tie::IxHash', 'svc' => 'svc_acct', 'desc' => 'Send an HTTP or HTTPS GET or POST request, for accounts.', 'options' => \%options, + 'no_machine' => 1, 'notes' => <<'END' Send an HTTP or HTTPS GET or POST to the specified URL on account addition, modification and deletion. For HTTPS support, diff --git a/FS/FS/part_export/acct_plesk.pm b/FS/FS/part_export/acct_plesk.pm index d8d70a30e..50b6faebf 100644 --- a/FS/FS/part_export/acct_plesk.pm +++ b/FS/FS/part_export/acct_plesk.pm @@ -15,9 +15,11 @@ tie my %options, 'Tie::IxHash', ; %info = ( - 'svc' => 'svc_acct', - 'desc' => 'Real-time export to Plesk managed mail service', - 'options'=> \%options, + 'svc' => 'svc_acct', + 'desc' => 'Real-time export to Plesk managed mail service', + 'options' => \%options, + 'no_machine' => 1, + 'default_svc_class' => 'Email', 'notes' => <<'END' Real-time export to <a href="http://www.swsoft.com/">Plesk</a> managed server. diff --git a/FS/FS/part_export/acct_sql.pm b/FS/FS/part_export/acct_sql.pm index ffe39caa5..8163f2017 100644 --- a/FS/FS/part_export/acct_sql.pm +++ b/FS/FS/part_export/acct_sql.pm @@ -60,11 +60,13 @@ my $postfix_native_mailbox_map = keys %postfix_native_mailbox_map ); %info = ( - 'svc' => 'svc_acct', - 'desc' => 'Real-time export of accounts to SQL databases '. - '(vpopmail, Postfix+Courier IMAP, others?)', - 'options' => \%options, - 'nodomain' => '', + 'svc' => 'svc_acct', + 'desc' => 'Real-time export of accounts to SQL databases '. + '(vpopmail, Postfix+Courier IMAP, others?)', + 'options' => \%options, + 'nodomain' => '', + 'no_machine' => 1, + 'default_svc_class' => 'Email', 'notes' => <<END Export accounts (svc_acct records) to SQL databases. Currently has default configurations for vpopmail and Postfix+Courier IMAP but intended to be diff --git a/FS/FS/part_export/acct_sql_status.pm b/FS/FS/part_export/acct_sql_status.pm index e6aeb2071..248105f18 100644 --- a/FS/FS/part_export/acct_sql_status.pm +++ b/FS/FS/part_export/acct_sql_status.pm @@ -14,6 +14,7 @@ delete $options{$_} for qw( table schema static primary_key ); 'desc' => 'Mailbox status information from SQL', 'options' => \%options, 'nodomain' => '', + 'no_machine' => 1, 'notes' => <<END Read mailbox status information (vacation and spam settings) from an SQL database, tables "vacation" and "users" respectively. diff --git a/FS/FS/part_export/acct_xmlrpc.pm b/FS/FS/part_export/acct_xmlrpc.pm index 96ad1fa67..3070f281a 100644 --- a/FS/FS/part_export/acct_xmlrpc.pm +++ b/FS/FS/part_export/acct_xmlrpc.pm @@ -34,6 +34,7 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_acct', 'desc' => 'Configurable provisioning of accounts via the XML-RPC protocol', 'options' => \%options, + 'no_machine' => 1, 'notes' => <<'END', Configurable, real-time export of accounts via the XML-RPC protocol.<BR> <BR> diff --git a/FS/FS/part_export/amazon_ec2.pm b/FS/FS/part_export/amazon_ec2.pm index 0e65ca00c..06e2c238e 100644 --- a/FS/FS/part_export/amazon_ec2.pm +++ b/FS/FS/part_export/amazon_ec2.pm @@ -20,6 +20,7 @@ tie my %options, 'Tie::IxHash', 'desc' => 'Export to Amazon EC2', 'options' => \%options, + 'no_machine' => 1, 'notes' => <<'END' Create instances in the Amazon EC2 (Elastic compute cloud). Install Net::Amazon::EC2 perl module. Advisable to set svc_external-skip_manual config diff --git a/FS/FS/part_export/artera_turbo.pm b/FS/FS/part_export/artera_turbo.pm index c006db9cd..e22bbf2af 100644 --- a/FS/FS/part_export/artera_turbo.pm +++ b/FS/FS/part_export/artera_turbo.pm @@ -37,6 +37,7 @@ tie my %options, 'Tie::IxHash', 'Real-time export to Artera Turbo Reseller API', 'options' => \%options, #'nodomain' => 'Y', + 'no_machine' => 1, 'notes' => <<'END' Real-time export to <a href="http://www.arteraturbo.com/">Artera Turbo</a> Reseller API. Requires installation of diff --git a/FS/FS/part_export/broadband_http.pm b/FS/FS/part_export/broadband_http.pm index 9edfee5d3..c1ed7fca6 100644 --- a/FS/FS/part_export/broadband_http.pm +++ b/FS/FS/part_export/broadband_http.pm @@ -45,6 +45,7 @@ tie %options, 'Tie::IxHash', 'svc' => 'svc_broadband', 'desc' => 'Send an HTTP or HTTPS GET or POST request, for accounts.', 'options' => \%options, + 'no_machine' => 1, 'notes' => <<'END' <p>Send an HTTP or HTTPS GET or POST to the specified URL on account addition, modification and deletion. For HTTPS support, diff --git a/FS/FS/part_export/broadband_nas.pm b/FS/FS/part_export/broadband_nas.pm index a160c9944..5a8ffac3b 100644 --- a/FS/FS/part_export/broadband_nas.pm +++ b/FS/FS/part_export/broadband_nas.pm @@ -43,6 +43,7 @@ FS::UID->install_callback( 'svc' => 'svc_broadband', 'desc' => 'Create a NAS entry in Freeside', 'options' => \%options, + 'no_machine' => 1, 'weight' => 10, 'notes' => <<'END' <p>Create an entry in the NAS (RADIUS client) table, inheriting the IP diff --git a/FS/FS/part_export/broadband_shellcommands.pm b/FS/FS/part_export/broadband_shellcommands.pm index c7f0fbb33..cf9c36c8f 100644 --- a/FS/FS/part_export/broadband_shellcommands.pm +++ b/FS/FS/part_export/broadband_shellcommands.pm @@ -107,3 +107,4 @@ sub ssh_cmd { #subroutine, not method ''; } +1; diff --git a/FS/FS/part_export/broadband_snmp.pm b/FS/FS/part_export/broadband_snmp.pm index cb1740efc..44b4dbabb 100644 --- a/FS/FS/part_export/broadband_snmp.pm +++ b/FS/FS/part_export/broadband_snmp.pm @@ -52,6 +52,7 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_broadband', 'desc' => 'Send SNMP requests to the service IP address', 'options' => \%options, + 'no_machine' => 1, 'weight' => 10, 'notes' => <<'END' Send one or more SNMP SET requests to the IP address registered to the service. diff --git a/FS/FS/part_export/broadband_sql.pm b/FS/FS/part_export/broadband_sql.pm index 697d3cdac..4f526c805 100644 --- a/FS/FS/part_export/broadband_sql.pm +++ b/FS/FS/part_export/broadband_sql.pm @@ -24,6 +24,7 @@ tie my %options, 'Tie::IxHash', 'desc' => 'Real-time export of broadband services to SQL databases ', 'options' => \%options, 'nodomain' => '', + 'no_machine' => 1, 'notes' => <<END END ); diff --git a/FS/FS/part_export/broadband_sqlradius.pm b/FS/FS/part_export/broadband_sqlradius.pm index 5806362b5..b5d1a80cb 100644 --- a/FS/FS/part_export/broadband_sqlradius.pm +++ b/FS/FS/part_export/broadband_sqlradius.pm @@ -55,6 +55,7 @@ tie %options, 'Tie::IxHash', 'svc' => 'svc_broadband', 'desc' => 'Real-time export to SQL-backed RADIUS (such as FreeRadius) for broadband services', 'options' => \%options, + 'no_machine' => 1, 'nas' => 'Y', 'notes' => <<END, Real-time export of <b>radcheck</b>, <b>radreply</b>, and <b>usergroup</b> diff --git a/FS/FS/part_export/communigate_pro.pm b/FS/FS/part_export/communigate_pro.pm index a3ec5e0be..8b66225d2 100644 --- a/FS/FS/part_export/communigate_pro.pm +++ b/FS/FS/part_export/communigate_pro.pm @@ -36,6 +36,7 @@ tie %options, 'Tie::IxHash', 'svc' => [qw( svc_acct svc_domain svc_forward svc_mailinglist )], 'desc' => 'Real-time export of accounts, domains, mail forwards and mailing lists to a CommuniGate Pro mail server', 'options' => \%options, + 'default_svc_class' => 'Email', 'notes' => <<'END' Real time export of accounts, domains, mail forwards and mailing lists to a <a href="http://www.stalker.com/CommuniGatePro/">CommuniGate Pro</a> diff --git a/FS/FS/part_export/communigate_pro_singledomain.pm b/FS/FS/part_export/communigate_pro_singledomain.pm index e25043fbb..cecea2826 100644 --- a/FS/FS/part_export/communigate_pro_singledomain.pm +++ b/FS/FS/part_export/communigate_pro_singledomain.pm @@ -16,6 +16,7 @@ tie my %options, 'Tie::IxHash', %FS::part_export::communigate_pro::options, 'Real-time export to a CommuniGate Pro mail server, one domain only', 'options' => \%options, 'nodomain' => 'Y', + 'default_svc_class' => 'Email', 'notes' => <<'END' Real time export to a <a href="http://www.stalker.com/CommuniGatePro/">CommuniGate Pro</a> diff --git a/FS/FS/part_export/cp.pm b/FS/FS/part_export/cp.pm index 96fa43710..2ae97e12d 100644 --- a/FS/FS/part_export/cp.pm +++ b/FS/FS/part_export/cp.pm @@ -18,6 +18,7 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_acct', 'desc' => 'Real-time export to Critical Path Account Provisioning Protocol', 'options'=> \%options, + 'default_svc_class' => 'Email', 'notes' => <<'END' Real-time export to <a href="http://www.cp.net/">Critial Path Account Provisioning Protocol</a>. diff --git a/FS/FS/part_export/cpanel.pm b/FS/FS/part_export/cpanel.pm index 0ad00df01..6c61e3d2b 100644 --- a/FS/FS/part_export/cpanel.pm +++ b/FS/FS/part_export/cpanel.pm @@ -190,3 +190,5 @@ sub cpanel_connect { $whm; } + +1; diff --git a/FS/FS/part_export/cust_http.pm b/FS/FS/part_export/cust_http.pm index e8b677be2..e834f93ea 100644 --- a/FS/FS/part_export/cust_http.pm +++ b/FS/FS/part_export/cust_http.pm @@ -55,6 +55,7 @@ tie %options, 'Tie::IxHash', 'svc' => 'cust_main', 'desc' => 'Send an HTTP or HTTPS GET or POST request, for customers.', 'options' => \%options, + 'no_machine' => 1, 'notes' => <<'END' Send an HTTP or HTTPS GET or POST to the specified URL on customer addition, modification and deletion. For HTTPS support, diff --git a/FS/FS/part_export/cyrus.pm b/FS/FS/part_export/cyrus.pm index 84c9e5a30..246d5b3dc 100644 --- a/FS/FS/part_export/cyrus.pm +++ b/FS/FS/part_export/cyrus.pm @@ -17,6 +17,8 @@ tie my %options, 'Tie::IxHash', 'desc' => 'Real-time export to Cyrus IMAP server', 'options' => \%options, 'nodomain' => 'Y', + 'no_machine' => 1, #de facto... but "server" option should move to it + 'default_svc_class' => 'Email', 'notes' => <<'END' Integration with <a href="http://asg.web.cmu.edu/cyrus/imapd/">Cyrus IMAP Server</a>. diff --git a/FS/FS/part_export/dashcs_e911.pm b/FS/FS/part_export/dashcs_e911.pm index 320d0a67b..2717233cf 100644 --- a/FS/FS/part_export/dashcs_e911.pm +++ b/FS/FS/part_export/dashcs_e911.pm @@ -20,6 +20,7 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_phone', 'desc' => 'Provision e911 services via Dash Carrier Services', 'notes' => 'Provision e911 services via Dash Carrier Services', + 'no_machine' => 1, 'options' => \%options, ); diff --git a/FS/FS/part_export/domain_sql.pm b/FS/FS/part_export/domain_sql.pm index 0749fec09..ff0d949f1 100644 --- a/FS/FS/part_export/domain_sql.pm +++ b/FS/FS/part_export/domain_sql.pm @@ -26,6 +26,7 @@ my $postfix_transport_static = 'desc' => 'Real time export of domains to SQL databases '. '(postfix, others?)', 'options' => \%options, + 'no_machine' => 1, 'notes' => <<END Export domains (svc_domain records) to SQL databases. Currently this is a simple export with a default for Postfix, but it can be extended for other diff --git a/FS/FS/part_export/everyone_net.pm b/FS/FS/part_export/everyone_net.pm index 0fd32fa8b..7386973e4 100644 --- a/FS/FS/part_export/everyone_net.pm +++ b/FS/FS/part_export/everyone_net.pm @@ -18,6 +18,8 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_acct', 'desc' => 'Real-time export to Everyone.net outsourced mail service', 'options'=> \%options, + 'no_machine' => 1, + 'default_svc_class' => 'Email', 'notes' => <<'END' Real-time export to <a href="http://www.everyone.net/">Everyone.net</a> via the XRC Remote API. diff --git a/FS/FS/part_export/ez_prepaid.pm b/FS/FS/part_export/ez_prepaid.pm index d171eb135..9f454df54 100644 --- a/FS/FS/part_export/ez_prepaid.pm +++ b/FS/FS/part_export/ez_prepaid.pm @@ -34,6 +34,7 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_external', 'desc' => 'Purchase EZ-Prepaid PIN', 'options' => \%options, + 'no_machine' => 1, 'notes' => <<'END' <P>Export to the EZ-Prepaid PIN purchase service. If the purchase is allowed, the PIN will be stored as svc_external.id.</P> diff --git a/FS/FS/part_export/forward_sql.pm b/FS/FS/part_export/forward_sql.pm index 563efcc44..eb4137801 100644 --- a/FS/FS/part_export/forward_sql.pm +++ b/FS/FS/part_export/forward_sql.pm @@ -10,6 +10,7 @@ use FS::Record; 'desc' => 'Real-time export of forwards to SQL databases ', #.' (vpopmail, Postfix+Courier IMAP, others?)', 'options' => __PACKAGE__->sql_options, + 'no_machine' => 1, 'notes' => <<END Export mail forwards (svc_forward records) to SQL databases. diff --git a/FS/FS/part_export/globalpops_voip.pm b/FS/FS/part_export/globalpops_voip.pm index 6df21f406..9fe45ba0a 100644 --- a/FS/FS/part_export/globalpops_voip.pm +++ b/FS/FS/part_export/globalpops_voip.pm @@ -19,6 +19,7 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_phone', 'desc' => 'Provision phone numbers to VoIP Innovations (formerly GlobalPOPs VoIP)', 'options' => \%options, + 'no_machine' => 1, 'notes' => <<'END' Requires installation of <a href="http://search.cpan.org/dist/Net-GlobalPOPs-MediaServicesAPI">Net::GlobalPOPs::MediaServicesAPI</a> diff --git a/FS/FS/part_export/http.pm b/FS/FS/part_export/http.pm index 3749224ff..c35c89f12 100644 --- a/FS/FS/part_export/http.pm +++ b/FS/FS/part_export/http.pm @@ -43,6 +43,7 @@ tie %options, 'Tie::IxHash', 'svc' => 'svc_domain', 'desc' => 'Send an HTTP or HTTPS GET or POST request', 'options' => \%options, + 'no_machine' => 1, 'notes' => <<'END' Send an HTTP or HTTPS GET or POST to the specified URL. For HTTPS support, <a href="http://search.cpan.org/dist/Crypt-SSLeay">Crypt::SSLeay</a> diff --git a/FS/FS/part_export/http_status.pm b/FS/FS/part_export/http_status.pm index 5342106b4..6fbd3fbe6 100644 --- a/FS/FS/part_export/http_status.pm +++ b/FS/FS/part_export/http_status.pm @@ -17,6 +17,7 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_dsl', 'desc' => 'Retrieve status information via HTTP or HTTPS', 'options' => \%options, + 'no_machine' => 1, 'notes' => <<'END' Fields from the service can be substituted in the URL as $field. END diff --git a/FS/FS/part_export/ikano.pm b/FS/FS/part_export/ikano.pm index eedc9d0ac..23917bf9e 100644 --- a/FS/FS/part_export/ikano.pm +++ b/FS/FS/part_export/ikano.pm @@ -31,6 +31,7 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_dsl', 'desc' => 'Provision DSL to Ikano', 'options' => \%options, + 'no_machine' => 1, 'notes' => <<'END' Requires installation of <a href="http://search.cpan.org/dist/Net-Ikano">Net::Ikano</a> from CPAN. diff --git a/FS/FS/part_export/indosoft.pm b/FS/FS/part_export/indosoft.pm index b5734019b..02ae5efc5 100644 --- a/FS/FS/part_export/indosoft.pm +++ b/FS/FS/part_export/indosoft.pm @@ -17,6 +17,7 @@ tie my %options, 'Tie::IxHash', 'desc' => 'Export conferences to the Indosoft Conference Bridge', 'options' => \%options, + 'no_machine' => 1, 'notes' => <<'END' Export conferences to the Indosoft conference bridge. Net::Indosoft::Voicebridge is required. diff --git a/FS/FS/part_export/infostreet.pm b/FS/FS/part_export/infostreet.pm index ef16c7c54..51f57605a 100644 --- a/FS/FS/part_export/infostreet.pm +++ b/FS/FS/part_export/infostreet.pm @@ -19,6 +19,7 @@ tie my %options, 'Tie::IxHash', 'desc' => 'Real-time export to InfoStreet streetSmartAPI', 'options' => \%options, 'nodomain' => 'Y', + 'no_machine' => 1, 'notes' => <<'END' Real-time export to <a href="http://www.infostreet.com/">InfoStreet</a> streetSmartAPI. diff --git a/FS/FS/part_export/internal_diddb.pm b/FS/FS/part_export/internal_diddb.pm index a94e43e28..b51f63173 100644 --- a/FS/FS/part_export/internal_diddb.pm +++ b/FS/FS/part_export/internal_diddb.pm @@ -17,6 +17,7 @@ tie my %options, 'Tie::IxHash', 'desc' => 'Provision phone numbers from the internal DID database', 'notes' => 'After adding the export, DIDs may be imported under Tools -> Importing -> Import phone numbers (DIDs)', 'options' => \%options, + 'no_machine' => 1, ); sub rebless { shift; } diff --git a/FS/FS/part_export/ldap.pm b/FS/FS/part_export/ldap.pm index 838532021..fe634d230 100644 --- a/FS/FS/part_export/ldap.pm +++ b/FS/FS/part_export/ldap.pm @@ -41,6 +41,7 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_acct', 'desc' => 'Real-time export to LDAP', 'options' => \%options, + 'default_svc_class' => 'Email', 'notes' => <<'END' Real-time export to arbitrary LDAP attributes. Requires installation of <a href="http://search.cpan.org/dist/Net-LDAP">Net::LDAP</a> from CPAN. diff --git a/FS/FS/part_export/netsapiens.pm b/FS/FS/part_export/netsapiens.pm index 6e2ee8ae3..2e37d04b6 100644 --- a/FS/FS/part_export/netsapiens.pm +++ b/FS/FS/part_export/netsapiens.pm @@ -72,10 +72,11 @@ tie my %options, 'Tie::IxHash', ; %info = ( - 'svc' => [ 'svc_phone', ], # 'part_device', - 'desc' => 'Provision phone numbers to NetSapiens', - 'options' => \%options, - 'notes' => <<'END' + 'svc' => [ 'svc_phone', ], # 'part_device', + 'desc' => 'Provision phone numbers to NetSapiens', + 'options' => \%options, + 'no_machine' => 1, + 'notes' => <<'END' Requires installation of <a href="http://search.cpan.org/dist/REST-Client">REST::Client</a> from CPAN. diff --git a/FS/FS/part_export/null.pm b/FS/FS/part_export/null.pm index 0145af3a4..3a764883c 100644 --- a/FS/FS/part_export/null.pm +++ b/FS/FS/part_export/null.pm @@ -11,3 +11,4 @@ sub _export_insert {} sub _export_replace {} sub _export_delete {} +1; diff --git a/FS/FS/part_export/phone_shellcommands.pm b/FS/FS/part_export/phone_shellcommands.pm index 040af27a7..5c1ae0153 100644 --- a/FS/FS/part_export/phone_shellcommands.pm +++ b/FS/FS/part_export/phone_shellcommands.pm @@ -138,3 +138,4 @@ sub ssh_cmd { #subroutine, not method &Net::SSH::ssh_cmd( { @_ } ); } +1; diff --git a/FS/FS/part_export/phone_sqlopensips.pm b/FS/FS/part_export/phone_sqlopensips.pm index 3d01c1624..7b07ecf4a 100644 --- a/FS/FS/part_export/phone_sqlopensips.pm +++ b/FS/FS/part_export/phone_sqlopensips.pm @@ -21,10 +21,11 @@ tie %options, 'Tie::IxHash', ; %info = ( - 'svc' => 'svc_phone', - 'desc' => 'Export DIDs to OpenSIPs dr_rules table', - 'options' => \%options, - 'notes' => 'Export DIDs to OpenSIPs dr_rules table', + 'svc' => 'svc_phone', + 'desc' => 'Export DIDs to OpenSIPs dr_rules table', + 'options' => \%options, + 'no_machine' => 1, + 'notes' => 'Export DIDs to OpenSIPs dr_rules table', ); sub rebless { shift; } @@ -93,3 +94,4 @@ sub dr_reload { ''; } +1; diff --git a/FS/FS/part_export/phone_sqlradius.pm b/FS/FS/part_export/phone_sqlradius.pm index 6b14bed3c..46c372cb4 100644 --- a/FS/FS/part_export/phone_sqlradius.pm +++ b/FS/FS/part_export/phone_sqlradius.pm @@ -39,10 +39,11 @@ tie %options, 'Tie::IxHash', ; %info = ( - 'svc' => 'svc_phone', - 'desc' => 'Real-time export to SQL-backed RADIUS (FreeRADIUS, ICRADIUS) for phone provisioning and rating', - 'options' => \%options, - 'notes' => <<END, + 'svc' => 'svc_phone', + 'desc' => 'Real-time export to SQL-backed RADIUS (FreeRADIUS, ICRADIUS) for phone provisioning and rating', + 'options' => \%options, + 'no_machine' => 1, + 'notes' => <<END, Real-time export of <b>radcheck</b> table to any SQL database for <a href="http://www.freeradius.org/">FreeRADIUS</a> or <a href="http://radius.innercite.com/">ICRADIUS</a>. diff --git a/FS/FS/part_export/postfix.pm b/FS/FS/part_export/postfix.pm index 4fd19ee61..9a8d617f3 100644 --- a/FS/FS/part_export/postfix.pm +++ b/FS/FS/part_export/postfix.pm @@ -22,6 +22,7 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_forward', 'desc' => 'Postfix text files', 'options' => \%options, + 'default_svc_class' => 'Email', 'notes' => <<'END' Batch export of Postfix aliases and virtual files. <a href="http://search.cpan.org/dist/File-Rsync">File::Rsync</a> diff --git a/FS/FS/part_export/prizm.pm b/FS/FS/part_export/prizm.pm index 02e89c6d3..996448951 100644 --- a/FS/FS/part_export/prizm.pm +++ b/FS/FS/part_export/prizm.pm @@ -79,11 +79,12 @@ possibly harmful. EOT %info = ( - 'svc' => 'svc_broadband', - 'desc' => 'Real-time export to Northbound Interface', - 'options' => \%options, - 'nodomain' => 'Y', - 'notes' => $notes, + 'svc' => 'svc_broadband', + 'desc' => 'Real-time export to Northbound Interface', + 'options' => \%options, + 'nodomain' => 'Y', + 'no_machine' => 1, + 'notes' => $notes, ); sub prizm_command { diff --git a/FS/FS/part_export/radiator.pm b/FS/FS/part_export/radiator.pm index 2ac3edb22..f09d36abb 100644 --- a/FS/FS/part_export/radiator.pm +++ b/FS/FS/part_export/radiator.pm @@ -11,6 +11,8 @@ tie my %options, 'Tie::IxHash', %FS::part_export::sqlradius::options; 'desc' => 'Real-time export to RADIATOR', 'options' => \%options, 'nodomain' => '', + 'no_machine' => 1, + 'default_svc_class' => 'Internet', 'notes' => <<'END', Real-time export of the <b>radusers</b> table to any SQL database in <a href="http://www.open.com.au/radiator/">Radiator</a>-native format. diff --git a/FS/FS/part_export/router.pm b/FS/FS/part_export/router.pm index 6a1d676f4..3071ece74 100644 --- a/FS/FS/part_export/router.pm +++ b/FS/FS/part_export/router.pm @@ -87,6 +87,7 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_broadband', 'desc' => 'Send a command to a router.', 'options' => \%options, + 'no_machine' => 1, 'notes' => 'Installation of Net::Telnet from CPAN is required for telnet connections. This export will execute if the following virtual fields are set on the router: admin_user, admin_password, admin_address, admin_timeout, admin_prompt. Option virtual fields are: admin_cmd_insert, admin_cmd_replace, admin_cmd_delete, admin_cmd_suspend, admin_cmd_unsuspend. See the module documentation for a full list of required/supported router virtual fields.', ); diff --git a/FS/FS/part_export/rt_ticket.pm b/FS/FS/part_export/rt_ticket.pm index b53b7da8a..7ae6105a0 100644 --- a/FS/FS/part_export/rt_ticket.pm +++ b/FS/FS/part_export/rt_ticket.pm @@ -127,6 +127,7 @@ tie my %options, 'Tie::IxHash', ( 'Create an RT ticket', 'options' => \%options, 'nodomain' => '', + 'no_machine' => 1, 'notes' => ' Create a ticket in RT. The subject and body of the ticket will be generated from a message template.' diff --git a/FS/FS/part_export/send_email.pm b/FS/FS/part_export/send_email.pm index 05f623633..6ba131f18 100644 --- a/FS/FS/part_export/send_email.pm +++ b/FS/FS/part_export/send_email.pm @@ -85,6 +85,7 @@ tie my %options, 'Tie::IxHash', ( 'Send an email message', 'options' => \%options, 'nodomain' => '', + 'no_machine' => 1, 'notes' => ' Send an email message. The subject and body of the message will be generated from a message template.' diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm index 20e909135..b9d6551db 100644 --- a/FS/FS/part_export/shellcommands.pm +++ b/FS/FS/part_export/shellcommands.pm @@ -97,12 +97,13 @@ tie my %options, 'Tie::IxHash', ; %info = ( - 'svc' => 'svc_acct', - 'desc' => + 'svc' => 'svc_acct', + 'desc' => 'Real-time export via remote SSH (i.e. useradd, userdel, etc.)', - 'options' => \%options, - 'nodomain' => 'Y', - 'notes' => <<'END' + 'options' => \%options, + 'nodomain' => 'Y', + 'svc_machine' => 1, + 'notes' => <<'END' Run remote commands via SSH. Usernames are considered unique (also see shellcommands_withdomain). You probably want this if the commands you are running will not accept a domain as a parameter. You will need to @@ -124,24 +125,7 @@ running will not accept a domain as a parameter. You will need to this.form.unsuspend_stdin.value=""; '> <LI> - <INPUT TYPE="button" VALUE="FreeBSD before 4.10 / 5.3" onClick=' - this.form.useradd.value = "lockf /etc/passwd.lock pw useradd $username -d $dir -m -s $shell -u $uid -c $finger -h 0"; - this.form.useradd_stdin.value = "$_password\n"; - this.form.userdel.value = "lockf /etc/passwd.lock pw userdel $username -r"; this.form.userdel_stdin.value=""; - this.form.usermod.value = "lockf /etc/passwd.lock pw usermod $old_username -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -g $new_gid -c $new_finger -h 0"; - this.form.usermod_stdin.value = "$new__password\n"; this.form.suspend.value = "lockf /etc/passwd.lock pw lock $username"; - this.form.suspend_stdin.value=""; - this.form.unsuspend.value = "lockf /etc/passwd.lock pw unlock $username"; this.form.unsuspend_stdin.value=""; - '> - Note: On FreeBSD versions before 5.3 and 4.10 (4.10 is after 4.9, not - 4.1!), due to deficient locking in pw(1), you must disable the chpass(1), - chsh(1), chfn(1), passwd(1), and vipw(1) commands, or replace them with - wrappers that prepend "lockf /etc/passwd.lock". Alternatively, apply the - patch in - <A HREF="http://www.freebsd.org/cgi/query-pr.cgi?pr=23501">FreeBSD PR#23501</A> - and use the "FreeBSD 4.10 / 5.3 or later" button below. - <LI> - <INPUT TYPE="button" VALUE="FreeBSD 4.10 / 5.3 or later" onClick=' + <INPUT TYPE="button" VALUE="FreeBSD" onClick=' this.form.useradd.value = "pw useradd $username -d $dir -m -s $shell -u $uid -g $gid -c $finger -h 0"; this.form.useradd_stdin.value = "$_password\n"; this.form.userdel.value = "pw userdel $username -r"; diff --git a/FS/FS/part_export/sqlmail.pm b/FS/FS/part_export/sqlmail.pm index cbdaf7f52..19505b488 100644 --- a/FS/FS/part_export/sqlmail.pm +++ b/FS/FS/part_export/sqlmail.pm @@ -37,6 +37,7 @@ tie my %options, 'Tie::IxHash', 'desc' => 'Real-time export to SQL-backed mail server', 'options' => \%options, 'nodomain' => '', + 'default_svc_class' => 'Email', 'notes' => <<'END' Database schema can be made to work with Courier IMAP, Exim and Dovecot. Others could work but are untested. (more detailed description from diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm index 721396671..6760d09b7 100644 --- a/FS/FS/part_export/sqlradius.pm +++ b/FS/FS/part_export/sqlradius.pm @@ -110,6 +110,7 @@ END 'desc' => 'Real-time export to SQL-backed RADIUS (FreeRADIUS, ICRADIUS)', 'options' => \%options, 'nodomain' => 'Y', + 'no_machine' => 1, 'nas' => 'Y', # show export_nas selection in UI 'default_svc_class' => 'Internet', 'notes' => $notes1. diff --git a/FS/FS/part_export/textradius.pm b/FS/FS/part_export/textradius.pm index 869c7c7dc..07de87563 100644 --- a/FS/FS/part_export/textradius.pm +++ b/FS/FS/part_export/textradius.pm @@ -18,6 +18,7 @@ tie my %options, 'Tie::IxHash', 'desc' => 'Real-time export to a text /etc/raddb/users file (Livingston, Cistron)', 'options' => \%options, + 'default_svc_class' => 'Internet', 'notes' => <<'END' This will edit a text RADIUS users file in place on a remote server. Requires installation of diff --git a/FS/FS/part_export/trango.pm b/FS/FS/part_export/trango.pm index e7f1126dd..64d2cc4ec 100644 --- a/FS/FS/part_export/trango.pm +++ b/FS/FS/part_export/trango.pm @@ -68,6 +68,7 @@ tie my %options, 'Tie::IxHash', ( 'svc' => 'svc_broadband', 'desc' => 'Sends SNMP SETs to a Trango AP.', 'options' => \%options, + 'no_machine' => 1, 'notes' => 'Requires Net::SNMP. See the documentation for FS::part_export::trango for required virtual fields and usage information.', ); diff --git a/FS/FS/part_export/vitelity.pm b/FS/FS/part_export/vitelity.pm index 12c3a7fce..350a5ad48 100644 --- a/FS/FS/part_export/vitelity.pm +++ b/FS/FS/part_export/vitelity.pm @@ -26,6 +26,7 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_phone', 'desc' => 'Provision phone numbers to Vitelity', 'options' => \%options, + 'no_machine' => 1, 'notes' => <<'END' Requires installation of <a href="http://search.cpan.org/dist/Net-Vitelity">Net::Vitelity</a> diff --git a/FS/FS/part_export/vpopmail.pm b/FS/FS/part_export/vpopmail.pm index 799a8e1c1..5fca1704c 100644 --- a/FS/FS/part_export/vpopmail.pm +++ b/FS/FS/part_export/vpopmail.pm @@ -23,6 +23,7 @@ tie my %options, 'Tie::IxHash', 'svc' => 'svc_acct', 'desc' => 'Real-time export to vpopmail text files', 'options' => \%options, + 'default_svc_class' => 'Email', 'notes' => <<'END' This export is currently unmaintained. See shellcommands_withdomain for an export that uses vpopmail CLI commands instead.<BR> diff --git a/FS/FS/part_export/www_plesk.pm b/FS/FS/part_export/www_plesk.pm index ccf9b3e17..a247f054e 100644 --- a/FS/FS/part_export/www_plesk.pm +++ b/FS/FS/part_export/www_plesk.pm @@ -18,10 +18,11 @@ tie my %options, 'Tie::IxHash', ; %info = ( - 'svc' => 'svc_www', - 'desc' => 'Real-time export to Plesk managed hosting service', - 'options'=> \%options, - 'notes' => <<'END' + 'svc' => 'svc_www', + 'desc' => 'Real-time export to Plesk managed hosting service', + 'options' => \%options, + 'no_machine' => 1, + 'notes' => <<'END' Real-time export to <a href="http://www.swsoft.com/">Plesk</a> managed server. Requires installation of diff --git a/FS/FS/part_export/www_shellcommands.pm b/FS/FS/part_export/www_shellcommands.pm index d6116aba1..bef2e9470 100644 --- a/FS/FS/part_export/www_shellcommands.pm +++ b/FS/FS/part_export/www_shellcommands.pm @@ -188,3 +188,4 @@ sub ssh_cmd { #subroutine, not method ''; } +1; diff --git a/FS/FS/part_export_machine.pm b/FS/FS/part_export_machine.pm new file mode 100644 index 000000000..1598e0372 --- /dev/null +++ b/FS/FS/part_export_machine.pm @@ -0,0 +1,155 @@ +package FS::part_export_machine; + +use strict; +use base qw( FS::Record ); +use FS::Record qw( dbh qsearch ); #qsearchs ); +use FS::part_export; +use FS::svc_export_machine; + +=head1 NAME + +FS::part_export_machine - Object methods for part_export_machine records + +=head1 SYNOPSIS + + use FS::part_export_machine; + + $record = new FS::part_export_machine \%hash; + $record = new FS::part_export_machine { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::part_export_machine object represents an export hostname choice. +FS::part_export_machine inherits from FS::Record. The following fields are +currently supported: + +=over 4 + +=item machinenum + +primary key + +=item exportnum + +Export, see L<FS::part_export> + +=item machine + +Hostname or IP address + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new record. To add the record to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I<hash> method. + +=cut + +sub table { 'part_export_machine'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=item delete + +Delete this record from the database. + +=cut + +sub delete { + my $self = shift; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $error = $self->SUPER::delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + foreach my $svc_export_machine ( $self->svc_export_machine ) { + my $error = $svc_export_machine->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + +} + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=item check + +Checks all fields to make sure this is a valid record. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('machinenum') + || $self->ut_foreign_key('exportnum', 'part_export', 'exportnum') + || $self->ut_domain('machine') + || $self->ut_enum('disabled', [ '', 'Y' ]) + ; + return $error if $error; + + $self->SUPER::check; +} + +=item svc_export_machine + +=cut + +sub svc_export_machine { + my $self = shift; + qsearch( 'svc_export_machine', { 'machinenum' => $self->machinenum } ); +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::part_export>, L<FS::Record> + +=cut + +1; + diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm index dd18e87f9..7f22411e0 100644 --- a/FS/FS/part_svc.pm +++ b/FS/FS/part_svc.pm @@ -591,7 +591,7 @@ sub _svc_defs { }; my $mod = $1; - if ( $mod =~ /^svc_[A-Z]/ or $mod =~ /^svc_acct_pop$/ ) { + if ( $mod =~ /^svc_[A-Z]/ or $mod =~ /^(svc_acct_pop|svc_export_machine)$/ ) { warn "skipping FS::$mod" if $DEBUG; next; } diff --git a/FS/FS/svc_export_machine.pm b/FS/FS/svc_export_machine.pm new file mode 100644 index 000000000..39629d8af --- /dev/null +++ b/FS/FS/svc_export_machine.pm @@ -0,0 +1,111 @@ +package FS::svc_export_machine; + +use strict; +use base qw( FS::Record ); +use FS::Record; # qw( qsearch qsearchs ); +use FS::cust_svc; +use FS::part_export_machine; + +=head1 NAME + +FS::svc_export_machine - Object methods for svc_export_machine records + +=head1 SYNOPSIS + + use FS::svc_export_machine; + + $record = new FS::svc_export_machine \%hash; + $record = new FS::svc_export_machine { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::svc_export_machine object represents a customer service export +hostname. FS::svc_export_machine inherits from FS::Record. The following +fields are currently supported: + +=over 4 + +=item svcexportmachinenum + +primary key + +=item svcnum + +Customer service, see L<FS::cust_svc> + +=item machinenum + +Export hostname, see L<FS::part_export_machine> + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new record. To add the record to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I<hash> method. + +=cut + +sub table { 'svc_export_machine'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=item delete + +Delete this record from the database. + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=item check + +Checks all fields to make sure this is a valid record. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('svcexportmachinenum') + || $self->ut_foreign_key('svcnum', 'cust_svc', 'svcnum') + || $self->ut_foreign_key('machinenum', 'part_export_machine', 'machinenum' ) + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::cust_svc>, L<FS::part_export_machine>, L<FS::Record> + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index bb10fb7b1..479dcad60 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -668,3 +668,7 @@ t/cust_bill_pkg_discount_void.t FS/Trace.pm FS/agent_pkg_class.pm t/agent_pkg_class.t +FS/part_export_machine.pm +t/part_export_machine.t +FS/svc_export_machine.pm +t/svc_export_machine.t diff --git a/FS/t/part_export_machine.t b/FS/t/part_export_machine.t new file mode 100644 index 000000000..792bb5092 --- /dev/null +++ b/FS/t/part_export_machine.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::part_export_machine; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/svc_export_machine.t b/FS/t/svc_export_machine.t new file mode 100644 index 000000000..5279be2ca --- /dev/null +++ b/FS/t/svc_export_machine.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::svc_export_machine; +$loaded=1; +print "ok 1\n"; diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi index 8e28f4fc6..b7ecc00a6 100755 --- a/httemplate/browse/part_export.cgi +++ b/httemplate/browse/part_export.cgi @@ -36,10 +36,9 @@ function part_export_areyousure(href) { <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->exportnum %></A></TD> <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> -% if( $part_export->exportname ) { - <B><% $part_export->exportname %>:</B><BR> -% } -<% $part_export->exporttype %> to <% $part_export->machine %> (<A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>">edit</A> | <A HREF="javascript:part_export_areyousure('<% $p %>misc/delete-part_export.cgi?<% $part_export->exportnum %>')">delete</A>)</TD> + <% $part_export->label_html %> + (<A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>">edit</A> | <A HREF="javascript:part_export_areyousure('<% $p %>misc/delete-part_export.cgi?<% $part_export->exportnum %>')">delete</A>) + </TD> <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> <% itable() %> diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index 26d090a3d..a8f4a7c84 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -141,16 +141,7 @@ function part_export_areyousure(href) { % <TR> - <TD><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"> -<% $part_export->exportnum %>: -% if ($part_export->exportname) { -<B><% $part_export->exportname %></B> ( -% } -<% $part_export->exporttype %> to <% $part_export->machine %> -% if ($part_export->exportname) { -) -% } -</A></TD> + <TD><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->label_html %></A></TD> </TR> % } diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi index d7219b74a..0407ee77b 100644 --- a/httemplate/edit/part_export.cgi +++ b/httemplate/edit/part_export.cgi @@ -13,12 +13,6 @@ </TD> </TR> <TR> - <TD ALIGN="right">Export host</TD> - <TD> - <INPUT TYPE="text" NAME="machine" VALUE="<% $part_export->machine %>"> - </TD> -</TR> -<TR> <TD ALIGN="right">Export</TD> <TD><% $widget->html %> @@ -63,7 +57,7 @@ my $widget = new HTML::Widgets::SelectLayers( 'options' => \%layers, 'form_name' => 'dummy', 'form_action' => 'process/part_export.cgi', - 'form_text' => [qw( exportnum exportname machine )], + 'form_text' => [qw( exportnum exportname )], # 'form_checkbox' => [qw()], 'html_between' => "</TD></TR></TABLE>\n", 'layer_callback' => sub { @@ -71,9 +65,69 @@ my $widget = new HTML::Widgets::SelectLayers( my $html = qq!<INPUT TYPE="hidden" NAME="exporttype" VALUE="$layer">!. ntable("#cccccc",2); - $html .= '<TR><TD ALIGN="right">Description</TD><TD BGCOLOR=#ffffff>'. - $exports->{$layer}{notes}. '</TD></TR>' - if $layer; + if ( $layer ) { + $html .= '<TR><TD ALIGN="right">Description</TD><TD BGCOLOR=#ffffff>'. + $exports->{$layer}{notes}. '</TD></TR>'; + + if ( $exports->{$layer}{no_machine} ) { + $html .= '<INPUT TYPE="hidden" NAME="machine" VALUE="">'. + '<INPUT TYPE="hidden" NAME="svc_machine" VALUE=N">'; + } else { + $html .= '<TR><TD ALIGN="right">Hostname or IP</TD><TD>'; + my $machine = $part_export->machine; + if ( $exports->{$layer}{svc_machine} ) { + my( $N_CHK, $Y_CHK) = ( 'CHECKED', '' ); + my( $machine_DISABLED, $pem_DISABLED) = ( '', 'DISABLED' ); + my $part_export_machine = ''; + if ( $cgi->param('svc_machine') eq 'Y' + || $machine eq '_SVC_MACHINE' + ) + { + $Y_CHK = 'CHECKED'; + $N_CHK = 'CHECKED'; + $machine_DISABLED = 'DISABLED'; + $pem_DISABLED = ''; + $machine = ''; + $part_export_machine = + $cgi->param('part_export_machine') + || join "\n", + map $_->machine, + grep ! $_->disabled, + $part_export->part_export_machine; + } + my $oc = qq(onChange="${layer}_svc_machine_changed(this)"); + $html .= qq[ + <INPUT TYPE="radio" NAME="svc_machine" VALUE="N" $N_CHK $oc> + <INPUT TYPE="text" NAME="machine" ID="${layer}_machine" VALUE="$machine" $machine_DISABLED> + <BR> + <INPUT TYPE="radio" NAME="svc_machine" VALUE="Y" $Y_CHK $oc> + Selected in each customer service from these choices + <TEXTAREA NAME="part_export_machine" ID="${layer}_part_export_machine" $pem_DISABLED>$part_export_machine</TEXTAREA> + + <SCRIPT TYPE="text/javascript"> + function ${layer}_svc_machine_changed (what) { + if ( what.checked ) { + var machine = document.getElementById("${layer}_machine"); + var part_export_machine = document.getElementById("${layer}_part_export_machine"); + if ( what.value == 'Y' ) { + machine.disabled = true; + part_export_machine.disabled = false; + } else if ( what.value == 'N' ) { + machine.disabled = false; + part_export_machine.disabled = true; + } + } + } + </SCRIPT> + ]; + } else { + $html .= qq(<INPUT TYPE="text" NAME="machine" VALUE="$machine">). + '<INPUT TYPE="hidden" NAME="svc_machine" VALUE=N">'; + } + $html .= "</TD></TR>"; + } + + } foreach my $option ( keys %{$exports->{$layer}{options}} ) { my $optinfo = $exports->{$layer}{options}{$option}; diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index 4bd083798..007c24629 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -144,12 +144,7 @@ % && qsearchs( 'export_svc', { % exportnum => $part_export->exportnum, % svcpart => $clone || $part_svc->svcpart }); -% $html .= '>'.$part_export->exportnum. ': '; -% $html .= $part_export->exportname . '<DIV ALIGN="right"><FONT SIZE=-1>' -% if ( $part_export->exportname ); -% $html .= $part_export->exporttype. ' to '. $part_export->machine; -% $html .= '</FONT></DIV>' if ( $part_export->exportname ); -% $html .= '</TD>'; +% $html .= '>'. $part_export->label_html. '</TD>'; % $count++; % $html .= '</TR><TR>' unless $count % $columns; % } diff --git a/httemplate/edit/process/part_export.cgi b/httemplate/edit/process/part_export.cgi index 21150ef67..6432d6b15 100644 --- a/httemplate/edit/process/part_export.cgi +++ b/httemplate/edit/process/part_export.cgi @@ -28,6 +28,11 @@ my $new = new FS::part_export ( { } fields('part_export') } ); +if ( $cgi->param('svc_machine') eq 'Y' ) { + $new->machine('_SVC_MACHINE'); + $new->part_export_machine_textarea( $cgi->param('part_export_machine') ); +} + my $error; if ( $exportnum ) { #warn $old; @@ -420,7 +420,7 @@ sub show { } elsif (my $spec = is_object_spec($_, $type)) { push @objects, $spec; - $rawprint = 1 if $_ =~ /\/content$/ or $_ !~ /^ticket/; + $rawprint = 1 if $_ =~ /\/content$/ or $_ =~ /\/links/ or $_ !~ /^ticket/; } else { my $datum = /^-/ ? "option" : "argument"; diff --git a/rt/bin/rt.in b/rt/bin/rt.in index e54a07add..2a9f643c8 100644 --- a/rt/bin/rt.in +++ b/rt/bin/rt.in @@ -420,7 +420,7 @@ sub show { } elsif (my $spec = is_object_spec($_, $type)) { push @objects, $spec; - $rawprint = 1 if $_ =~ /\/content$/ or $_ !~ /^ticket/; + $rawprint = 1 if $_ =~ /\/content$/ or $_ =~ /\/links/ or $_ !~ /^ticket/; } else { my $datum = /^-/ ? "option" : "argument"; diff --git a/rt/configure b/rt/configure index 1862c5fe6..76ef85b92 100755 --- a/rt/configure +++ b/rt/configure @@ -1,7 +1,7 @@ #! /bin/sh # From configure.ac Revision. # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.67 for RT rt-4.0.6. +# Generated by GNU Autoconf 2.68 for RT rt-4.0.7. # # Report bugs to <rt-bugs@bestpractical.com>. # @@ -92,6 +92,7 @@ fi IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. +as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -216,11 +217,18 @@ IFS=$as_save_IFS # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. + # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV export CONFIG_SHELL - exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"} + case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; + esac + exec "$CONFIG_SHELL" $as_opts "$as_myself" ${1+"$@"} fi if test x$as_have_required = xno; then : @@ -552,8 +560,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='RT' PACKAGE_TARNAME='rt' -PACKAGE_VERSION='rt-4.0.6' -PACKAGE_STRING='RT rt-4.0.6' +PACKAGE_VERSION='rt-4.0.7' +PACKAGE_STRING='RT rt-4.0.7' PACKAGE_BUGREPORT='rt-bugs@bestpractical.com' PACKAGE_URL='' @@ -1165,7 +1173,7 @@ Try \`$0 --help' for more information" $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 - : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option} + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac @@ -1303,7 +1311,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures RT rt-4.0.6 to adapt to many kinds of systems. +\`configure' configures RT rt-4.0.7 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1364,7 +1372,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of RT rt-4.0.6:";; + short | recursive ) echo "Configuration of RT rt-4.0.7:";; esac cat <<\_ACEOF @@ -1488,8 +1496,8 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -RT configure rt-4.0.6 -generated by GNU Autoconf 2.67 +RT configure rt-4.0.7 +generated by GNU Autoconf 2.68 Copyright (C) 2010 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation @@ -1535,7 +1543,7 @@ sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi - eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_compile @@ -1581,7 +1589,7 @@ fi # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo - eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_link @@ -1589,8 +1597,8 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by RT $as_me rt-4.0.6, which was -generated by GNU Autoconf 2.67. Invocation command line was +It was created by RT $as_me rt-4.0.7, which was +generated by GNU Autoconf 2.68. Invocation command line was $ $0 $@ @@ -1848,7 +1856,7 @@ $as_echo "$as_me: loading site script $ac_site_file" >&6;} || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file -See \`config.log' for more details" "$LINENO" 5 ; } +See \`config.log' for more details" "$LINENO" 5; } fi done @@ -1946,7 +1954,7 @@ rt_version_major=4 rt_version_minor=0 -rt_version_patch=6 +rt_version_patch=7 test "x$rt_version_major" = 'x' && rt_version_major=0 test "x$rt_version_minor" = 'x' && rt_version_minor=0 @@ -1998,7 +2006,7 @@ ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 $as_echo_n "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then -if test "${ac_cv_path_install+set}" = set; then : +if ${ac_cv_path_install+:} false; then : $as_echo_n "(cached) " >&6 else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -2079,7 +2087,7 @@ test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' set dummy perl; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_path_PERL+set}" = set; then : +if ${ac_cv_path_PERL+:} false; then : $as_echo_n "(cached) " >&6 else case $PERL in @@ -2800,7 +2808,7 @@ if test -n "$ac_tool_prefix"; then set dummy ${ac_tool_prefix}gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_CC+set}" = set; then : +if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then @@ -2840,7 +2848,7 @@ if test -z "$ac_cv_prog_CC"; then set dummy gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_ac_ct_CC+set}" = set; then : +if ${ac_cv_prog_ac_ct_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then @@ -2893,7 +2901,7 @@ if test -z "$CC"; then set dummy ${ac_tool_prefix}cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_CC+set}" = set; then : +if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then @@ -2933,7 +2941,7 @@ if test -z "$CC"; then set dummy cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_CC+set}" = set; then : +if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then @@ -2992,7 +3000,7 @@ if test -z "$CC"; then set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_CC+set}" = set; then : +if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then @@ -3036,7 +3044,7 @@ do set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_ac_ct_CC+set}" = set; then : +if ${ac_cv_prog_ac_ct_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then @@ -3091,7 +3099,7 @@ fi test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH -See \`config.log' for more details" "$LINENO" 5 ; } +See \`config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 @@ -3206,7 +3214,7 @@ sed 's/^/| /' conftest.$ac_ext >&5 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables -See \`config.log' for more details" "$LINENO" 5 ; } +See \`config.log' for more details" "$LINENO" 5; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } @@ -3249,7 +3257,7 @@ else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link -See \`config.log' for more details" "$LINENO" 5 ; } +See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest conftest$ac_cv_exeext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 @@ -3308,7 +3316,7 @@ $as_echo "$ac_try_echo"; } >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run C compiled programs. If you meant to cross compile, use \`--host'. -See \`config.log' for more details" "$LINENO" 5 ; } +See \`config.log' for more details" "$LINENO" 5; } fi fi fi @@ -3319,7 +3327,7 @@ rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out ac_clean_files=$ac_clean_files_save { $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 $as_echo_n "checking for suffix of object files... " >&6; } -if test "${ac_cv_objext+set}" = set; then : +if ${ac_cv_objext+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext @@ -3360,7 +3368,7 @@ sed 's/^/| /' conftest.$ac_ext >&5 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile -See \`config.log' for more details" "$LINENO" 5 ; } +See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest.$ac_cv_objext conftest.$ac_ext fi @@ -3370,7 +3378,7 @@ OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 $as_echo_n "checking whether we are using the GNU C compiler... " >&6; } -if test "${ac_cv_c_compiler_gnu+set}" = set; then : +if ${ac_cv_c_compiler_gnu+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext @@ -3407,7 +3415,7 @@ ac_test_CFLAGS=${CFLAGS+set} ac_save_CFLAGS=$CFLAGS { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 $as_echo_n "checking whether $CC accepts -g... " >&6; } -if test "${ac_cv_prog_cc_g+set}" = set; then : +if ${ac_cv_prog_cc_g+:} false; then : $as_echo_n "(cached) " >&6 else ac_save_c_werror_flag=$ac_c_werror_flag @@ -3485,7 +3493,7 @@ else fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 $as_echo_n "checking for $CC option to accept ISO C89... " >&6; } -if test "${ac_cv_prog_cc_c89+set}" = set; then : +if ${ac_cv_prog_cc_c89+:} false; then : $as_echo_n "(cached) " >&6 else ac_cv_prog_cc_c89=no @@ -3583,7 +3591,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu { $as_echo "$as_me:${as_lineno-$LINENO}: checking for aginitlib in -lgraph" >&5 $as_echo_n "checking for aginitlib in -lgraph... " >&6; } -if test "${ac_cv_lib_graph_aginitlib+set}" = set; then : +if ${ac_cv_lib_graph_aginitlib+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS @@ -3617,7 +3625,7 @@ LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_graph_aginitlib" >&5 $as_echo "$ac_cv_lib_graph_aginitlib" >&6; } -if test "x$ac_cv_lib_graph_aginitlib" = x""yes; then : +if test "x$ac_cv_lib_graph_aginitlib" = xyes; then : RT_GRAPHVIZ="1" fi @@ -3643,7 +3651,7 @@ fi set dummy gdlib-config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_RT_GD+set}" = set; then : +if ${ac_cv_prog_RT_GD+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$RT_GD"; then @@ -3699,7 +3707,7 @@ fi set dummy gpg; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_RT_GPG+set}" = set; then : +if ${ac_cv_prog_RT_GPG+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$RT_GPG"; then @@ -3984,10 +3992,21 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then - test "x$cache_file" != "x/dev/null" && + if test "x$cache_file" != "x/dev/null"; then { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 $as_echo "$as_me: updating cache $cache_file" >&6;} - cat confcache >$cache_file + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi else { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} @@ -4055,7 +4074,7 @@ LTLIBOBJS=$ac_ltlibobjs -: ${CONFIG_STATUS=./config.status} +: "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" @@ -4156,6 +4175,7 @@ fi IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. +as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -4462,8 +4482,8 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by RT $as_me rt-4.0.6, which was -generated by GNU Autoconf 2.67. Invocation command line was +This file was extended by RT $as_me rt-4.0.7, which was +generated by GNU Autoconf 2.68. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS @@ -4515,8 +4535,8 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -RT config.status rt-4.0.6 -configured by $0, generated by GNU Autoconf 2.67, +RT config.status rt-4.0.7 +configured by $0, generated by GNU Autoconf 2.68, with options \\"\$ac_cs_config\\" Copyright (C) 2010 Free Software Foundation, Inc. @@ -4658,7 +4678,7 @@ do "t/data/configs/apache2.2+mod_perl.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.2+mod_perl.conf" ;; "t/data/configs/apache2.2+fastcgi.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.2+fastcgi.conf" ;; - *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5 ;; + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done @@ -4679,9 +4699,10 @@ fi # after its creation but before its name has been assigned to `$tmp'. $debug || { - tmp= + tmp= ac_tmp= trap 'exit_status=$? - { test -z "$tmp" || test ! -d "$tmp" || rm -fr "$tmp"; } && exit $exit_status + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } @@ -4689,12 +4710,13 @@ $debug || { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && - test -n "$tmp" && test -d "$tmp" + test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. @@ -4716,7 +4738,7 @@ else ac_cs_awk_cr=$ac_cr fi -echo 'BEGIN {' >"$tmp/subs1.awk" && +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF @@ -4744,7 +4766,7 @@ done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -cat >>"\$tmp/subs1.awk" <<\\_ACAWK && +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h @@ -4792,7 +4814,7 @@ t delim rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK -cat >>"\$tmp/subs1.awk" <<_ACAWK && +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" @@ -4824,7 +4846,7 @@ if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat -fi < "$tmp/subs1.awk" > "$tmp/subs.awk" \ +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF @@ -4864,7 +4886,7 @@ do esac case $ac_mode$ac_tag in :[FHL]*:*);; - :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5 ;; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac @@ -4883,7 +4905,7 @@ do for ac_f do case $ac_f in - -) ac_f="$tmp/stdin";; + -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. @@ -4892,7 +4914,7 @@ do [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || - as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5 ;; + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" @@ -4918,8 +4940,8 @@ $as_echo "$as_me: creating $ac_file" >&6;} esac case $ac_tag in - *:-:* | *:-) cat >"$tmp/stdin" \ - || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac @@ -5049,21 +5071,22 @@ s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t $ac_datarootdir_hack " -eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$tmp/subs.awk" >$tmp/out \ - || as_fn_error $? "could not create $ac_file" "$LINENO" 5 +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && - { ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } && - { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 $as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} - rm -f "$tmp/stdin" + rm -f "$ac_tmp/stdin" case $ac_file in - -) cat "$tmp/out" && rm -f "$tmp/out";; - *) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";; + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; diff --git a/rt/docs/web_deployment.pod b/rt/docs/web_deployment.pod index 4c3f73fb5..5d2cd4c00 100644 --- a/rt/docs/web_deployment.pod +++ b/rt/docs/web_deployment.pod @@ -23,7 +23,7 @@ to use L<Starman>, a high performance preforking server: /opt/rt4/sbin/rt-server --server Starman --port 8080 B<NOTICE>: After you run the standalone server as root, you will need to -remove your C<var/mason> directory, or the non-standalone servers +remove your C<var/mason_data> directory, or the non-standalone servers (Apache, etc), which run as a non-privileged user, will not be able to write to it and will not work. diff --git a/rt/etc/initialdata b/rt/etc/initialdata index cc07cec59..7ab746db1 100644 --- a/rt/etc/initialdata +++ b/rt/etc/initialdata @@ -1,4 +1,4 @@ -# Initial data for a fresh RT3 Installation. +# Initial data for a fresh RT installation. @Users = ( { Name => 'root', diff --git a/rt/etc/schema.SQLite b/rt/etc/schema.SQLite index 138971cfb..6897be2d6 100644 --- a/rt/etc/schema.SQLite +++ b/rt/etc/schema.SQLite @@ -3,7 +3,7 @@ CREATE TABLE Attachments ( id INTEGER PRIMARY KEY , TransactionId INTEGER , - Parent integer NULL , + Parent integer NULL DEFAULT 0 , MessageId varchar(160) NULL , Subject varchar(255) NULL , Filename varchar(255) NULL , @@ -11,7 +11,7 @@ CREATE TABLE Attachments ( ContentEncoding varchar(80) NULL , Content LONGTEXT NULL , Headers LONGTEXT NULL , - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL ) ; @@ -30,12 +30,12 @@ CREATE TABLE Queues ( CommentAddress varchar(120) NULL , Lifecycle varchar(32) NULL , SubjectTag varchar(120) NULL , - InitialPriority integer NULL , - FinalPriority integer NULL , - DefaultDueIn integer NULL , - Creator integer NULL , + InitialPriority integer NULL DEFAULT 0 , + FinalPriority integer NULL DEFAULT 0 , + DefaultDueIn integer NULL DEFAULT 0 , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL , Disabled int2 NOT NULL DEFAULT 0 @@ -51,11 +51,11 @@ CREATE TABLE Links ( Base varchar(240) NULL , Target varchar(240) NULL , Type varchar(20) NOT NULL , - LocalTarget integer NULL , - LocalBase integer NULL , - LastUpdatedBy integer NULL , + LocalTarget integer NULL DEFAULT 0 , + LocalBase integer NULL DEFAULT 0 , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL , - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL ) ; @@ -106,9 +106,9 @@ CREATE TABLE ScripConditions ( Argument varchar(255) NULL , ApplicableTransTypes varchar(60) NULL , - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL ) ; @@ -119,8 +119,8 @@ CREATE TABLE ScripConditions ( CREATE TABLE Transactions ( id INTEGER PRIMARY KEY , ObjectType varchar(255) NULL , - ObjectId integer NULL , - TimeTaken integer NULL , + ObjectId integer NULL DEFAULT 0 , + TimeTaken integer NULL DEFAULT 0 , Type varchar(20) NULL , Field varchar(40) NULL , OldValue varchar(255) NULL , @@ -130,7 +130,7 @@ CREATE TABLE Transactions ( NewReference integer NULL , Data varchar(255) NULL , - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL ) ; @@ -143,19 +143,19 @@ CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId); CREATE TABLE Scrips ( id INTEGER PRIMARY KEY , Description varchar(255), - ScripCondition integer NULL , - ScripAction integer NULL , + ScripCondition integer NULL DEFAULT 0 , + ScripAction integer NULL DEFAULT 0 , ConditionRules text NULL , ActionRules text NULL , CustomIsApplicableCode text NULL , CustomPrepareCode text NULL , CustomCommitCode text NULL , Stage varchar(32) NULL , - Queue integer NULL , - Template integer NULL , - Creator integer NULL , + Queue integer NULL DEFAULT 0 , + Template integer NULL DEFAULT 0 , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL ) ; @@ -167,7 +167,7 @@ CREATE TABLE ACL ( id INTEGER PRIMARY KEY , PrincipalType varchar(25) NOT NULL, - PrincipalId INTEGER, + PrincipalId INTEGER DEFAULT 0, RightName varchar(25) NOT NULL , ObjectType varchar(25) NOT NULL , ObjectId INTEGER default 0, @@ -185,8 +185,8 @@ CREATE TABLE ACL ( CREATE TABLE GroupMembers ( id INTEGER PRIMARY KEY , - GroupId integer NULL, - MemberId integer NULL, + GroupId integer NULL DEFAULT 0, + MemberId integer NULL DEFAULT 0, Creator integer NOT NULL DEFAULT 0 , Created DATETIME NULL , LastUpdatedBy integer NOT NULL DEFAULT 0 , @@ -250,9 +250,9 @@ CREATE TABLE Users ( Timezone char(50) NULL , PGPKey text NULL, - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL ) ; @@ -270,20 +270,20 @@ CREATE INDEX Users4 ON Users (EmailAddress); CREATE TABLE Tickets ( id INTEGER PRIMARY KEY , - EffectiveId integer NULL , - Queue integer NULL , + EffectiveId integer NULL DEFAULT 0 , + Queue integer NULL DEFAULT 0 , Type varchar(16) NULL , - IssueStatement integer NULL , - Resolution integer NULL , - Owner integer NULL , + IssueStatement integer NULL DEFAULT 0 , + Resolution integer NULL DEFAULT 0 , + Owner integer NULL DEFAULT 0 , Subject varchar(200) NULL DEFAULT '[no subject]' , - InitialPriority integer NULL , - FinalPriority integer NULL , - Priority integer NULL , - TimeEstimated integer NULL , - TimeWorked integer NULL , + InitialPriority integer NULL DEFAULT 0 , + FinalPriority integer NULL DEFAULt 0 , + Priority integer NULL DEFAULT 0 , + TimeEstimated integer NULL DEFAULT 0 , + TimeWorked integer NULL DEFAULT 0 , Status varchar(64) NULL , - TimeLeft integer NULL , + TimeLeft integer NULL DEFAULT 0 , Told DATETIME NULL , Starts DATETIME NULL , Started DATETIME NULL , @@ -291,9 +291,9 @@ CREATE TABLE Tickets ( Resolved DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL , - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , Disabled int2 NOT NULL DEFAULT 0 @@ -315,9 +315,9 @@ CREATE TABLE ScripActions ( Description varchar(255) NULL , ExecModule varchar(60) NULL , Argument varchar(255) NULL , - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL ) ; @@ -333,11 +333,11 @@ CREATE TABLE Templates ( Description varchar(255) NULL , Type varchar(16) NULL , Language varchar(16) NULL , - TranslationOf integer NULL , + TranslationOf integer NULL DEFAULT 0 , Content blob NULL , LastUpdated DATETIME NULL , - LastUpdatedBy integer NULL , - Creator integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL ) ; @@ -437,10 +437,10 @@ CREATE TABLE Attributes ( Content LONGTEXT NULL , ContentType varchar(16), ObjectType varchar(25) NOT NULL , - ObjectId INTEGER default 0, - Creator integer NULL , + ObjectId INTEGER , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL ) ; @@ -483,22 +483,22 @@ Parent integer NOT NULL DEFAULT 0, Name varchar(255) NOT NULL DEFAULT '', Description varchar(255) NOT NULL DEFAULT '', ObjectType varchar(64) NOT NULL DEFAULT '', -ObjectId integer NOT NULL +ObjectId integer NOT NULL DEFAULT 0 ); CREATE TABLE ObjectTopics ( id INTEGER PRIMARY KEY, -Topic integer NOT NULL, +Topic integer NOT NULL DEFAULT 0, ObjectType varchar(64) NOT NULL DEFAULT '', -ObjectId integer NOT NULL +ObjectId integer NOT NULL DEFAULT 0 ); CREATE TABLE ObjectClasses ( id INTEGER PRIMARY KEY, -Class integer NOT NULL, +Class integer NOT NULL DEFAULT 0, ObjectType varchar(64) NOT NULL DEFAULT '', -ObjectId integer NOT NULL, +ObjectId integer NOT NULL DEFAULT 0, Creator integer NOT NULL DEFAULT 0, Created TIMESTAMP NULL, LastUpdatedBy integer NOT NULL DEFAULT 0, diff --git a/rt/etc/upgrade/3.3.0/schema.mysql b/rt/etc/upgrade/3.3.0/schema.mysql index f6998363e..d8b04991e 100644 --- a/rt/etc/upgrade/3.3.0/schema.mysql +++ b/rt/etc/upgrade/3.3.0/schema.mysql @@ -1,37 +1,32 @@ -alter Table Transactions ADD Column (ObjectType varchar(64) not null); -update Transactions set ObjectType = 'RT::Ticket'; -alter table Transactions drop column EffectiveTicket; -alter table Transactions add column ReferenceType varchar(255) NULL; -alter table Transactions add column OldReference integer NULL; -alter table Transactions add column NewReference integer NULL; -alter table Transactions drop index transactions1; -alter table Transactions change Ticket ObjectId integer NOT NULL DEFAULT 0 ; +drop index transactions1 ON Transactions; -CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId); - -alter table TicketCustomFieldValues rename ObjectCustomFieldValues; +alter Table Transactions + ADD COLUMN (ObjectType varchar(64) not null), + DROP COLUMN EffectiveTicket, + ADD COLUMN ReferenceType varchar(255) NULL, + ADD COLUMN OldReference integer NULL, + ADD COLUMN NewReference integer NULL, + CHANGE Ticket ObjectId integer NOT NULL DEFAULT 0; -alter table ObjectCustomFieldValues change Ticket ObjectId integer NOT NULL DEFAULT 0 ; +UPDATE Transactions set ObjectType = 'RT::Ticket'; +CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId); -alter table ObjectCustomFieldValues add column ObjectType varchar(255) not null; +alter table TicketCustomFieldValues rename ObjectCustomFieldValues, + change Ticket ObjectId integer NOT NULL DEFAULT 0 , + add column ObjectType varchar(255) not null, + add column Current bool default 1, + add column LargeContent LONGTEXT NULL, + add column ContentType varchar(80) NULL, + add column ContentEncoding varchar(80) NULL; update ObjectCustomFieldValues set ObjectType = 'RT::Ticket'; -alter table ObjectCustomFieldValues add column Current bool default 1; - -alter table ObjectCustomFieldValues add column LargeContent LONGTEXT NULL; - -alter table ObjectCustomFieldValues add column ContentType varchar(80) NULL; - -alter table ObjectCustomFieldValues add column ContentEncoding varchar(80) NULL; - # These could fail if there's no such index and there's no "drop index if exists" syntax #alter table ObjectCustomFieldValues drop index ticketcustomfieldvalues1; #alter table ObjectCustomFieldValues drop index ticketcustomfieldvalues2; -alter table ObjectCustomFieldValues add index ObjectCustomFieldValues1 (Content); - -alter table ObjectCustomFieldValues add index ObjectCustomFieldValues2 (CustomField,ObjectType,ObjectId); +alter table ObjectCustomFieldValues add index ObjectCustomFieldValues1 (Content), + add index ObjectCustomFieldValues2 (CustomField,ObjectType,ObjectId); CREATE TABLE ObjectCustomFields ( @@ -50,10 +45,10 @@ CREATE TABLE ObjectCustomFields ( INSERT into ObjectCustomFields (id, CustomField, ObjectId, SortOrder, Creator, LastUpdatedBy) SELECT null, id, Queue, SortOrder, Creator, LastUpdatedBy from CustomFields; -alter table CustomFields add column LookupType varchar(255) NOT NULL; -alter table CustomFields add column Repeated int2 NOT NULL DEFAULT 0 ; -alter table CustomFields add column Pattern varchar(255) NULL; -alter table CustomFields add column MaxValues integer; +alter table CustomFields add column LookupType varchar(255) NOT NULL, + add column Repeated int2 NOT NULL DEFAULT 0 , + add column Pattern varchar(255) NULL, + add column MaxValues integer; # See above # alter table CustomFields drop index CustomFields1; @@ -62,4 +57,4 @@ UPDATE CustomFields SET MaxValues = 1 WHERE Type LIKE '%Single'; UPDATE CustomFields SET Type = 'Select' WHERE Type LIKE 'Select%'; UPDATE CustomFields SET Type = 'Freeform' WHERE Type LIKE 'Freeform%'; UPDATE CustomFields Set LookupType = 'RT::Queue-RT::Ticket'; -alter table CustomFields drop column Queue; +alter table CustomFields drop column Queue; diff --git a/rt/etc/upgrade/3.3.11/schema.mysql b/rt/etc/upgrade/3.3.11/schema.mysql index cc35d40f2..eff84783f 100644 --- a/rt/etc/upgrade/3.3.11/schema.mysql +++ b/rt/etc/upgrade/3.3.11/schema.mysql @@ -1,5 +1,5 @@ -ALTER TABLE ObjectCustomFieldValues ADD COLUMN SortOrder INTEGER NOT NULL DEFAULT 0; -ALTER TABLE ObjectCustomFieldValues ADD COLUMN Disabled int2 NOT NULL DEFAULT 0; +ALTER TABLE ObjectCustomFieldValues ADD COLUMN SortOrder INTEGER NOT NULL DEFAULT 0, + ADD COLUMN Disabled int2 NOT NULL DEFAULT 0; UPDATE ObjectCustomFieldValues SET Disabled = 1 WHERE Current = 0; ALTER TABLE ObjectCustomFieldValues DROP COLUMN Current; diff --git a/rt/etc/upgrade/3.9.5/schema.mysql b/rt/etc/upgrade/3.9.5/schema.mysql index 4bd0907c0..fe5018c78 100644 --- a/rt/etc/upgrade/3.9.5/schema.mysql +++ b/rt/etc/upgrade/3.9.5/schema.mysql @@ -6,15 +6,15 @@ AND CustomFieldValues.id = Attributes.ObjectId); DELETE FROM Attributes WHERE Name = 'Category' AND ObjectType = 'RT::CustomFieldValue'; -ALTER TABLE Groups ADD COLUMN Creator integer NOT NULL DEFAULT 0; -ALTER TABLE Groups ADD COLUMN Created DATETIME NULL; -ALTER TABLE Groups ADD COLUMN LastUpdatedBy integer NOT NULL DEFAULT 0; -ALTER TABLE Groups ADD COLUMN LastUpdated DATETIME NULL; -ALTER TABLE GroupMembers ADD COLUMN Creator integer NOT NULL DEFAULT 0; -ALTER TABLE GroupMembers ADD COLUMN Created DATETIME NULL; -ALTER TABLE GroupMembers ADD COLUMN LastUpdatedBy integer NOT NULL DEFAULT 0; -ALTER TABLE GroupMembers ADD COLUMN LastUpdated DATETIME NULL; -ALTER TABLE ACL ADD COLUMN Creator integer NOT NULL DEFAULT 0; -ALTER TABLE ACL ADD COLUMN Created DATETIME NULL; -ALTER TABLE ACL ADD COLUMN LastUpdatedBy integer NOT NULL DEFAULT 0; -ALTER TABLE ACL ADD COLUMN LastUpdated DATETIME NULL; +ALTER TABLE Groups ADD COLUMN Creator integer NOT NULL DEFAULT 0, + ADD COLUMN Created DATETIME NULL, + ADD COLUMN LastUpdatedBy integer NOT NULL DEFAULT 0, + ADD COLUMN LastUpdated DATETIME NULL; +ALTER TABLE GroupMembers ADD COLUMN Creator integer NOT NULL DEFAULT 0, + ADD COLUMN Created DATETIME NULL, + ADD COLUMN LastUpdatedBy integer NOT NULL DEFAULT 0, + ADD COLUMN LastUpdated DATETIME NULL; +ALTER TABLE ACL ADD COLUMN Creator integer NOT NULL DEFAULT 0, + ADD COLUMN Created DATETIME NULL, + ADD COLUMN LastUpdatedBy integer NOT NULL DEFAULT 0, + ADD COLUMN LastUpdated DATETIME NULL; diff --git a/rt/etc/upgrade/3.9.7/schema.mysql b/rt/etc/upgrade/3.9.7/schema.mysql index 1be165647..4cbed6cc7 100644 --- a/rt/etc/upgrade/3.9.7/schema.mysql +++ b/rt/etc/upgrade/3.9.7/schema.mysql @@ -1,6 +1,6 @@ ALTER TABLE Users ADD COLUMN AuthToken VARCHAR(16) CHARACTER SET ascii NULL; -ALTER TABLE CustomFields ADD COLUMN BasedOn INTEGER NULL; -ALTER TABLE CustomFields ADD COLUMN RenderType VARCHAR(64) NULL; -ALTER TABLE CustomFields ADD COLUMN ValuesClass VARCHAR(64) CHARACTER SET ascii NULL; -ALTER TABLE Queues ADD COLUMN SubjectTag VARCHAR(120) NULL; -ALTER TABLE Queues ADD COLUMN Lifecycle VARCHAR(32) NULL; +ALTER TABLE CustomFields ADD COLUMN BasedOn INTEGER NULL, + ADD COLUMN RenderType VARCHAR(64) NULL, + ADD COLUMN ValuesClass VARCHAR(64) CHARACTER SET ascii NULL; +ALTER TABLE Queues ADD COLUMN SubjectTag VARCHAR(120) NULL, + ADD COLUMN Lifecycle VARCHAR(32) NULL; diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm index 31489c8ff..efd2bdaf6 100644 --- a/rt/lib/RT/Action/CreateTickets.pm +++ b/rt/lib/RT/Action/CreateTickets.pm @@ -567,7 +567,8 @@ sub Parse { $self->_ParseMultilineTemplate(%args); } elsif ( $args{'Content'} =~ /(?:\t|,)/i ) { $self->_ParseXSVTemplate(%args); - + } else { + RT->Logger->error("Invalid Template Content (Couldn't find ===, and is not a csv/tsv template) - unable to parse: $args{Content}"); } } diff --git a/rt/lib/RT/Articles.pm b/rt/lib/RT/Articles.pm index 8dd661d2e..47d0ebea2 100644 --- a/rt/lib/RT/Articles.pm +++ b/rt/lib/RT/Articles.pm @@ -360,6 +360,7 @@ sub LimitCustomField { QUOTEVALUE => $args{'QUOTEVALUE'}, ENTRYAGGREGATOR => 'AND', #$args{'ENTRYAGGREGATOR'}, SUBCLAUSE => $clause, + CASESENSITIVE => 0, ); $self->SUPER::Limit( ALIAS => $ObjectValuesAlias, @@ -380,6 +381,7 @@ sub LimitCustomField { QUOTEVALUE => $args{'QUOTEVALUE'}, ENTRYAGGREGATOR => $args{'ENTRYAGGREGATOR'}, SUBCLAUSE => $clause, + CASESENSITIVE => 0, ); $self->SUPER::Limit( ALIAS => $ObjectValuesAlias, @@ -389,6 +391,7 @@ sub LimitCustomField { QUOTEVALUE => $args{'QUOTEVALUE'}, ENTRYAGGREGATOR => $args{'ENTRYAGGREGATOR'}, SUBCLAUSE => $clause, + CASESENSITIVE => 0, ); } } diff --git a/rt/lib/RT/Config.pm b/rt/lib/RT/Config.pm index f87ef84c9..014c76468 100644 --- a/rt/lib/RT/Config.pm +++ b/rt/lib/RT/Config.pm @@ -411,8 +411,8 @@ our %META = ( Description => q|What tickets to display in the 'More about requestor' box|, #loc Values => [qw(Active Inactive All None)], ValuesLabel => { - Active => "Show the Requestor's 10 highest priority open tickets", #loc - Inactive => "Show the Requestor's 10 highest priority closed tickets", #loc + Active => "Show the Requestor's 10 highest priority active tickets", #loc + Inactive => "Show the Requestor's 10 highest priority inactive tickets", #loc All => "Show the Requestor's 10 highest priority tickets", #loc None => "Show no tickets for the Requestor", #loc }, @@ -749,7 +749,7 @@ our %META = ( my %seen; foreach my $encoding ( grep defined && length, splice @$value ) { - next if $seen{ $encoding }++; + next if $seen{ $encoding }; if ( $encoding eq '*' ) { unshift @$value, '*'; next; diff --git a/rt/lib/RT/Crypt/GnuPG.pm b/rt/lib/RT/Crypt/GnuPG.pm index ab444d068..c5fb12bef 100644 --- a/rt/lib/RT/Crypt/GnuPG.pm +++ b/rt/lib/RT/Crypt/GnuPG.pm @@ -1683,6 +1683,7 @@ my %ignore_keyword = map { $_ => 1 } qw( BEGIN_ENCRYPTION SIG_ID VALIDSIG ENC_TO BEGIN_DECRYPTION END_DECRYPTION GOODMDC TRUST_UNDEFINED TRUST_NEVER TRUST_MARGINAL TRUST_FULLY TRUST_ULTIMATE + DECRYPTION_INFO ); sub ParseStatus { diff --git a/rt/lib/RT/Dashboard.pm b/rt/lib/RT/Dashboard.pm index 14ffa6ad3..2e2bbc489 100644 --- a/rt/lib/RT/Dashboard.pm +++ b/rt/lib/RT/Dashboard.pm @@ -454,6 +454,36 @@ sub CurrentUserCanCreateAny { return 0; } +=head2 Delete + +Deletes the dashboard and related subscriptions. +Returns a tuple of status and message, where status is true upon success. + +=cut + +sub Delete { + my $self = shift; + my $id = $self->id; + my ( $status, $msg ) = $self->SUPER::Delete(@_); + if ( $status ) { + # delete all the subscriptions + my $subscriptions = RT::Attributes->new( RT->SystemUser ); + $subscriptions->Limit( + FIELD => 'Name', + VALUE => 'Subscription', + ); + $subscriptions->Limit( + FIELD => 'Description', + VALUE => "Subscription to dashboard $id", + ); + while ( my $subscription = $subscriptions->Next ) { + $subscription->Delete(); + } + } + + return ( $status, $msg ); +} + RT::Base->_ImportOverlays(); 1; diff --git a/rt/lib/RT/Generated.pm b/rt/lib/RT/Generated.pm index 2abcf3b6e..9fd946f5b 100644 --- a/rt/lib/RT/Generated.pm +++ b/rt/lib/RT/Generated.pm @@ -50,7 +50,7 @@ package RT; use warnings; use strict; -our $VERSION = '4.0.6'; +our $VERSION = '4.0.7'; diff --git a/rt/lib/RT/I18N.pm b/rt/lib/RT/I18N.pm index cadf7cc7c..e453cfa04 100644 --- a/rt/lib/RT/I18N.pm +++ b/rt/lib/RT/I18N.pm @@ -227,7 +227,7 @@ sub SetMIMEEntityToEncoding { my $body = $entity->bodyhandle; - if ( $enc ne $charset && $body ) { + if ( $body && ($enc ne $charset || $enc =~ /^utf-?8(?:-strict)?$/i) ) { my $string = $body->as_string or return; $RT::Logger->debug( "Converting '$charset' to '$enc' for " @@ -335,7 +335,7 @@ sub DecodeMIMEWordsToEncoding { } # now we have got a decoded subject, try to convert into the encoding - unless ( $charset eq $to_charset ) { + if ( $charset ne $to_charset || $charset =~ /^utf-?8(?:-strict)?$/i ) { Encode::from_to( $enc_str, $charset, $to_charset ); } @@ -537,7 +537,7 @@ sub SetMIMEHeadToEncoding { my @values = $head->get_all($tag); $head->delete($tag); foreach my $value (@values) { - if ( $charset ne $enc ) { + if ( $charset ne $enc || $enc =~ /^utf-?8(?:-strict)?$/i ) { Encode::_utf8_off($value); Encode::from_to( $value, $charset => $enc ); } diff --git a/rt/lib/RT/Interface/Email.pm b/rt/lib/RT/Interface/Email.pm index 02a1ec0c0..4c3ee9986 100755 --- a/rt/lib/RT/Interface/Email.pm +++ b/rt/lib/RT/Interface/Email.pm @@ -787,7 +787,7 @@ sub GetForwardFrom { my $ticket = $args{Ticket} || $txn->Object; if ( RT->Config->Get('ForwardFromUser') ) { - return ( $txn || $ticket )->CurrentUser->UserObj->EmailAddress; + return ( $txn || $ticket )->CurrentUser->EmailAddress; } else { return $ticket->QueueObj->CorrespondAddress @@ -1221,8 +1221,16 @@ sub SetInReplyTo { if @references > 10; my $mail = $args{'Message'}; - $mail->head->set( 'In-Reply-To' => join ' ', @rtid? (@rtid) : (@id) ) if @id || @rtid; - $mail->head->set( 'References' => join ' ', @references ); + $mail->head->set( 'In-Reply-To' => Encode::encode_utf8(join ' ', @rtid? (@rtid) : (@id)) ) if @id || @rtid; + $mail->head->set( 'References' => Encode::encode_utf8(join ' ', @references) ); +} + +sub ExtractTicketId { + my $entity = shift; + + my $subject = $entity->head->get('Subject') || ''; + chomp $subject; + return ParseTicketId( $subject ); } sub ParseTicketId { @@ -1448,7 +1456,7 @@ sub Gateway { } # }}} - $args{'ticket'} ||= ParseTicketId( $Subject ); + $args{'ticket'} ||= ExtractTicketId( $Message ); $SystemTicket = RT::Ticket->new( RT->SystemUser ); $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ; @@ -1704,17 +1712,20 @@ sub _RunUnsafeAction { return ( 0, "Ticket not taken" ); } } elsif ( $args{'Action'} =~ /^resolve$/i ) { - my ( $status, $msg ) = $args{'Ticket'}->SetStatus('resolved'); - unless ($status) { + my $new_status = $args{'Ticket'}->FirstInactiveStatus; + if ($new_status) { + my ( $status, $msg ) = $args{'Ticket'}->SetStatus($new_status); + unless ($status) { - #Warn the sender that we couldn't actually submit the comment. - MailError( - To => $args{'ErrorsTo'}, - Subject => "Ticket not resolved", - Explanation => $msg, - MIMEObj => $args{'Message'} - ); - return ( 0, "Ticket not resolved" ); + #Warn the sender that we couldn't actually submit the comment. + MailError( + To => $args{'ErrorsTo'}, + Subject => "Ticket not resolved", + Explanation => $msg, + MIMEObj => $args{'Message'} + ); + return ( 0, "Ticket not resolved" ); + } } } else { return ( 0, "Not supported unsafe action $args{'Action'}", $args{'Ticket'} ); diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm index 94da3072d..1aae7581e 100644 --- a/rt/lib/RT/Interface/Web.pm +++ b/rt/lib/RT/Interface/Web.pm @@ -261,7 +261,15 @@ sub HandleRequest { $HTML::Mason::Commands::m->comp( '/Elements/SetupSessionCookie', %$ARGS ); SendSessionCookie(); - $HTML::Mason::Commands::session{'CurrentUser'} = RT::CurrentUser->new() unless _UserLoggedIn(); + + if ( _UserLoggedIn() ) { + # make user info up to date + $HTML::Mason::Commands::session{'CurrentUser'} + ->Load( $HTML::Mason::Commands::session{'CurrentUser'}->id ); + } + else { + $HTML::Mason::Commands::session{'CurrentUser'} = RT::CurrentUser->new(); + } # Process session-related callbacks before any auth attempts $HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Session', CallbackPage => '/autohandler' ); @@ -287,7 +295,7 @@ sub HandleRequest { my $m = $HTML::Mason::Commands::m; # REST urls get a special 401 response - if ($m->request_comp->path =~ '^/REST/\d+\.\d+/') { + if ($m->request_comp->path =~ m{^/REST/\d+\.\d+/}) { $HTML::Mason::Commands::r->content_type("text/plain"); $m->error_format("text"); $m->out("RT/$RT::VERSION 401 Credentials required\n"); @@ -457,7 +465,7 @@ sub MaybeShowInstallModePage { my $m = $HTML::Mason::Commands::m; if ( $m->base_comp->path =~ RT->Config->Get('WebNoAuthRegex') ) { $m->call_next(); - } elsif ( $m->request_comp->path !~ '^(/+)Install/' ) { + } elsif ( $m->request_comp->path !~ m{^(/+)Install/} ) { RT::Interface::Web::Redirect( RT->Config->Get('WebURL') . "Install/index.html" ); } else { $m->call_next(); @@ -557,7 +565,7 @@ sub ShowRequestedPage { unless ( $HTML::Mason::Commands::session{'CurrentUser'}->Privileged ) { # if the user is trying to access a ticket, redirect them - if ( $m->request_comp->path =~ '^(/+)Ticket/Display.html' && $ARGS->{'id'} ) { + if ( $m->request_comp->path =~ m{^(/+)Ticket/Display.html} && $ARGS->{'id'} ) { RT::Interface::Web::Redirect( RT->Config->Get('WebURL') . "SelfService/Display.html?id=" . $ARGS->{'id'} ); } @@ -659,7 +667,7 @@ sub AttemptExternalAuth { delete $HTML::Mason::Commands::session{'CurrentUser'}; $user = $orig_user; - if ( RT->Config->Get('WebExternalOnly') ) { + unless ( RT->Config->Get('WebFallbackToInternalAuth') ) { TangentForLoginWithError('You are not an authorized user'); } } @@ -970,7 +978,7 @@ sub MobileClient { my $self = shift; -if (($ENV{'HTTP_USER_AGENT'} || '') =~ /(?:hiptop|Blazer|Novarra|Vagabond|SonyEricsson|Symbian|NetFront|UP.Browser|UP.Link|Windows CE|MIDP|J2ME|DoCoMo|J-PHONE|PalmOS|PalmSource|iPhone|iPod|AvantGo|Nokia|Android|WebOS|S60)/io && !$HTML::Mason::Commands::session{'NotMobile'}) { +if (($ENV{'HTTP_USER_AGENT'} || '') =~ /(?:hiptop|Blazer|Novarra|Vagabond|SonyEricsson|Symbian|NetFront|UP.Browser|UP.Link|Windows CE|MIDP|J2ME|DoCoMo|J-PHONE|PalmOS|PalmSource|iPhone|iPod|AvantGo|Nokia|Android|WebOS|S60|Mobile)/io && !$HTML::Mason::Commands::session{'NotMobile'}) { return 1; } else { return undef; @@ -1183,6 +1191,14 @@ our %is_whitelisted_component = ( # information for the search. Because it's a straight-up read, in # addition to embedding its own auth, it's fine. '/NoAuth/rss/dhandler' => 1, + + # While these can be used for denial-of-service against RT + # (construct a very inefficient query and trick lots of users into + # running them against RT) it's incredibly useful to be able to link + # to a search result or bookmark a result page. + '/Search/Results.html' => 1, + '/Search/Simple.html' => 1, + '/m/tickets/search' => 1, ); sub IsCompCSRFWhitelisted { @@ -1237,7 +1253,19 @@ sub IsRefererCSRFWhitelisted { my $configs; for my $config ( $base_url, RT->Config->Get('ReferrerWhitelist') ) { push @$configs,$config; - return 1 if $referer->host_port eq $config; + + my $host_port = $referer->host_port; + if ($config =~ /\*/) { + # Turn a literal * into a domain component or partial component match. + # Refer to http://tools.ietf.org/html/rfc2818#page-5 + my $regex = join "[a-zA-Z0-9\-]*", + map { quotemeta($_) } + split /\*/, $config; + + return 1 if $host_port =~ /^$regex$/i; + } else { + return 1 if $host_port eq $config; + } } return (0,$referer,$configs); @@ -1962,7 +1990,7 @@ sub MakeMIMEEntity { ); my $Message = MIME::Entity->build( Type => 'multipart/mixed', - "Message-Id" => RT::Interface::Email::GenMessageId, + "Message-Id" => Encode::encode_utf8( RT::Interface::Email::GenMessageId ), map { $_ => Encode::encode_utf8( $args{ $_} ) } grep defined $args{$_}, qw(Subject From Cc) ); diff --git a/rt/lib/RT/Record.pm b/rt/lib/RT/Record.pm index e134178be..fd238de16 100755 --- a/rt/lib/RT/Record.pm +++ b/rt/lib/RT/Record.pm @@ -639,6 +639,8 @@ sub __Value { my $value = $self->SUPER::__Value($field); + return undef if (!defined $value); + if ( $args{'decode_utf8'} ) { if ( !utf8::is_utf8($value) ) { utf8::decode($value); @@ -1675,7 +1677,7 @@ sub _AddCustomFieldValue { 0, $self->loc( "Custom field [_1] does not apply to this object", - $args{'Field'} + ref $args{'Field'} ? $args{'Field'}->id : $args{'Field'} ) ); } diff --git a/rt/lib/RT/Scrip.pm b/rt/lib/RT/Scrip.pm index 950661624..8f97e747f 100755 --- a/rt/lib/RT/Scrip.pm +++ b/rt/lib/RT/Scrip.pm @@ -545,7 +545,7 @@ sub _Set { } } - return $self->__Set(@_); + return $self->SUPER::_Set(@_); } diff --git a/rt/lib/RT/Scrips.pm b/rt/lib/RT/Scrips.pm index 13a4b7d7d..fa33f7ec7 100755 --- a/rt/lib/RT/Scrips.pm +++ b/rt/lib/RT/Scrips.pm @@ -178,16 +178,6 @@ Commit all of this object's prepared scrips sub Commit { my $self = shift; - # RT::Scrips->_SetupSourceObjects will clobber - # the CurrentUser, but we need to keep this ticket - # so that the _TransactionBatch cache is maintained - # and doesn't run twice. sigh. - $self->_StashCurrentUser( TicketObj => $self->{TicketObj} ) if $self->{TicketObj}; - - #We're really going to need a non-acled ticket for the scrips to work - $self->_SetupSourceObjects( TicketObj => $self->{'TicketObj'}, - TransactionObj => $self->{'TransactionObj'} ); - foreach my $scrip (@{$self->Prepared}) { $RT::Logger->debug( "Committing scrip #". $scrip->id @@ -199,8 +189,6 @@ sub Commit { TransactionObj => $self->{'TransactionObj'} ); } - # Apply the bandaid. - $self->_RestoreCurrentUser( TicketObj => $self->{TicketObj} ) if $self->{TicketObj}; } @@ -221,12 +209,6 @@ sub Prepare { Type => undef, @_ ); - # RT::Scrips->_SetupSourceObjects will clobber - # the CurrentUser, but we need to keep this ticket - # so that the _TransactionBatch cache is maintained - # and doesn't run twice. sigh. - $self->_StashCurrentUser( TicketObj => $args{TicketObj} ) if $args{TicketObj}; - #We're really going to need a non-acled ticket for the scrips to work $self->_SetupSourceObjects( TicketObj => $args{'TicketObj'}, Ticket => $args{'Ticket'}, @@ -259,10 +241,6 @@ sub Prepare { } - # Apply the bandaid. - $self->_RestoreCurrentUser( TicketObj => $args{TicketObj} ) if $args{TicketObj}; - - return (@{$self->Prepared}); }; @@ -279,40 +257,6 @@ sub Prepared { return ($self->{'prepared_scrips'} || []); } -=head2 _StashCurrentUser TicketObj => RT::Ticket - -Saves aside the current user of the original ticket that was passed to these scrips. -This is used to make sure that we don't accidentally leak the RT_System current user -back to the calling code. - -=cut - -sub _StashCurrentUser { - my $self = shift; - my %args = @_; - - $self->{_TicketCurrentUser} = $args{TicketObj}->CurrentUser; -} - -=head2 _RestoreCurrentUser TicketObj => RT::Ticket - -Uses the current user saved by _StashCurrentUser to reset a Ticket object -back to the caller's current user and avoid leaking an RT_System ticket to -calling code. - -=cut - -sub _RestoreCurrentUser { - my $self = shift; - my %args = @_; - unless ( $self->{_TicketCurrentUser} ) { - RT->Logger->debug("Called _RestoreCurrentUser without a stashed current user object"); - return; - } - $args{TicketObj}->CurrentUser($self->{_TicketCurrentUser}); - -} - =head2 _SetupSourceObjects { TicketObj , Ticket, Transaction, TransactionObj } Setup a ticket and transaction for this Scrip collection to work with as it runs through the @@ -334,14 +278,22 @@ sub _SetupSourceObjects { @_ ); - if ( $self->{'TicketObj'} = $args{'TicketObj'} ) { - # This clobbers the passed in TicketObj by turning it into one - # whose current user is RT_System. Anywhere in the Web UI - # currently calling into this is thus susceptable to a privilege - # leak; the only current call site is ->Apply, which bandaids - # over the top of this by re-asserting the CurrentUser - # afterwards. - $self->{'TicketObj'}->CurrentUser( $self->CurrentUser ); + if ( $args{'TicketObj'} ) { + # This loads a clean copy of the Ticket object to ensure that we + # don't accidentally escalate the privileges of the passed in + # ticket (this function can be invoked from the UI). + # We copy the TransactionBatch transactions so that Scrips + # running against the new Ticket will have access to them. We + # use RanTransactionBatch to guard against running + # TransactionBatch Scrips more than once. + $self->{'TicketObj'} = RT::Ticket->new( $self->CurrentUser ); + $self->{'TicketObj'}->Load( $args{'TicketObj'}->Id ); + if ( $args{'TicketObj'}->TransactionBatch ) { + # try to ensure that we won't infinite loop if something dies, triggering DESTROY while + # we have the _TransactionBatch objects; + $self->{'TicketObj'}->RanTransactionBatch(1); + $self->{'TicketObj'}->{'_TransactionBatch'} = $args{'TicketObj'}->{'_TransactionBatch'}; + } } else { $self->{'TicketObj'} = RT::Ticket->new( $self->CurrentUser ); diff --git a/rt/lib/RT/Search/Googleish.pm b/rt/lib/RT/Search/Googleish.pm index a1254836a..1b4071f4d 100644 --- a/rt/lib/RT/Search/Googleish.pm +++ b/rt/lib/RT/Search/Googleish.pm @@ -110,7 +110,7 @@ sub QueryToSQL { (\w+) # A straight word (?:\. # With an optional .foo ($RE{delimited}{-delim=>q['"]} - |\w+ + |[\w-]+ # Allow \w + dashes ) # Which could be ."foo bar", too )? ) @@ -225,6 +225,11 @@ sub GuessType { return "default"; } +# $_[0] is $self +# $_[1] is escaped value without surrounding single quotes +# $_[2] is a boolean of "was quoted by the user?" +# ensure this is false before you do smart matching like $_[1] eq "me" +# $_[3] is escaped subkey, if any (see HandleCf) sub HandleDefault { return subject => "Subject LIKE '$_[1]'"; } sub HandleSubject { return subject => "Subject LIKE '$_[1]'"; } sub HandleFulltext { return content => "Content LIKE '$_[1]'"; } @@ -242,7 +247,14 @@ sub HandleStatus { } } sub HandleOwner { - return owner => (!$_[2] and $_[1] eq "me") ? "Owner.id = '__CurrentUser__'" : "Owner = '$_[1]'"; + if (!$_[2] and $_[1] eq "me") { + return owner => "Owner.id = '__CurrentUser__'"; + } + elsif (!$_[2] and $_[1] =~ /\w+@\w+/) { + return owner => "Owner.EmailAddress = '$_[1]'"; + } else { + return owner => "Owner = '$_[1]'"; + } } sub HandleWatcher { return watcher => (!$_[2] and $_[1] eq "me") ? "Watcher.id = '__CurrentUser__'" : "Watcher = '$_[1]'"; diff --git a/rt/lib/RT/SearchBuilder.pm b/rt/lib/RT/SearchBuilder.pm index 3e9855110..4278f7587 100644 --- a/rt/lib/RT/SearchBuilder.pm +++ b/rt/lib/RT/SearchBuilder.pm @@ -211,29 +211,35 @@ sub LimitCustomField { @_ ); my $alias = $self->Join( - TYPE => 'left', - ALIAS1 => 'main', - FIELD1 => 'id', - TABLE2 => 'ObjectCustomFieldValues', - FIELD2 => 'ObjectId' + TYPE => 'left', + ALIAS1 => 'main', + FIELD1 => 'id', + TABLE2 => 'ObjectCustomFieldValues', + FIELD2 => 'ObjectId' ); $self->Limit( - ALIAS => $alias, - FIELD => 'CustomField', - OPERATOR => '=', - VALUE => $args{'CUSTOMFIELD'}, + ALIAS => $alias, + FIELD => 'CustomField', + OPERATOR => '=', + VALUE => $args{'CUSTOMFIELD'}, ) if ($args{'CUSTOMFIELD'}); $self->Limit( - ALIAS => $alias, - FIELD => 'ObjectType', - OPERATOR => '=', - VALUE => $self->_SingularClass, + ALIAS => $alias, + FIELD => 'ObjectType', + OPERATOR => '=', + VALUE => $self->_SingularClass, ); $self->Limit( - ALIAS => $alias, - FIELD => 'Content', - OPERATOR => $args{'OPERATOR'}, - VALUE => $args{'VALUE'}, + ALIAS => $alias, + FIELD => 'Content', + OPERATOR => $args{'OPERATOR'}, + VALUE => $args{'VALUE'}, + ); + $self->Limit( + ALIAS => $alias, + FIELD => 'Disabled', + OPERATOR => '=', + VALUE => 0, ); } diff --git a/rt/lib/RT/Shredder.pm b/rt/lib/RT/Shredder.pm index 40c73b36d..4f96e162d 100644 --- a/rt/lib/RT/Shredder.pm +++ b/rt/lib/RT/Shredder.pm @@ -539,9 +539,9 @@ sub WipeoutAll { my $self = $_[0]; - while ( my ($k, $v) = each %{ $self->{'cache'} } ) { - next if $v->{'State'} & (WIPED | IN_WIPING); - $self->Wipeout( Object => $v->{'Object'} ); + foreach my $cache_val ( values %{ $self->{'cache'} } ) { + next if $cache_val->{'State'} & (WIPED | IN_WIPING); + $self->Wipeout( Object => $cache_val->{'Object'} ); } } diff --git a/rt/lib/RT/Test.pm b/rt/lib/RT/Test.pm index 7d69dd60d..3e7c910ec 100644 --- a/rt/lib/RT/Test.pm +++ b/rt/lib/RT/Test.pm @@ -131,14 +131,14 @@ sub import { if (RT->Config->Get('DevelMode')) { require Module::Refresh; } - $class->bootstrap_db( %args ); - RT::InitPluginPaths(); + RT::InitClasses(); + + $class->bootstrap_db( %args ); __reconnect_rt() unless $args{nodb}; - RT::InitClasses(); RT::InitLogging(); RT->Plugins; diff --git a/rt/lib/RT/Ticket.pm b/rt/lib/RT/Ticket.pm index 00f88b657..577c44429 100755 --- a/rt/lib/RT/Ticket.pm +++ b/rt/lib/RT/Ticket.pm @@ -1124,7 +1124,7 @@ sub AddWatcher { return (0, $self->loc("Couldn't parse address from '[_1]' string", $args{'Email'} )) unless $addr; - if ( lc $self->CurrentUser->UserObj->EmailAddress + if ( lc $self->CurrentUser->EmailAddress eq lc RT::User->CanonicalizeEmailAddress( $addr->address ) ) { $args{'PrincipalId'} = $self->CurrentUser->id; @@ -1305,7 +1305,7 @@ sub DeleteWatcher { } } else { - $RT::Logger->warn("$self -> DeleteWatcher got passed a bogus type"); + $RT::Logger->warning("$self -> DeleteWatcher got passed a bogus type"); return ( 0, $self->loc('Error in parameters to Ticket->DeleteWatcher') ); } @@ -1989,6 +1989,31 @@ sub FirstActiveStatus { return $next; } +=head2 FirstInactiveStatus + +Returns the first inactive status that the ticket could transition to, +according to its current Queue's lifecycle. May return undef if there +is no such possible status to transition to, or we are already in it. +This is used in resolve action in UnsafeEmailCommands, for instance. + +=cut + +sub FirstInactiveStatus { + my $self = shift; + + my $lifecycle = $self->QueueObj->Lifecycle; + my $status = $self->Status; + my @inactive = $lifecycle->Inactive; + # no change if no inactive statuses in the lifecycle + return undef unless @inactive; + + # no change if the ticket is already has first status from the list of inactive + return undef if lc $status eq lc $inactive[0]; + + my ($next) = grep $lifecycle->IsInactive($_), $lifecycle->Transitions($status); + return $next; +} + =head2 SetStarted Takes a date in ISO format or undef @@ -2315,7 +2340,9 @@ sub _RecordNote { my $msgid = $args{'MIMEObj'}->head->get('Message-ID'); unless (defined $msgid && $msgid =~ /<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@\Q$org\E>/) { $args{'MIMEObj'}->head->set( - 'RT-Message-ID' => RT::Interface::Email::GenMessageId( Ticket => $self ) + 'RT-Message-ID' => Encode::encode_utf8( + RT::Interface::Email::GenMessageId( Ticket => $self ) + ) ); } @@ -3340,6 +3367,28 @@ sub SeenUpTo { return $txns->First; } +=head2 RanTransactionBatch + +Acts as a guard around running TransactionBatch scrips. + +Should be false until you enter the code that runs TransactionBatch scrips + +Accepts an optional argument to indicate that TransactionBatch Scrips should no longer be run on this object. + +=cut + +sub RanTransactionBatch { + my $self = shift; + my $val = shift; + + if ( defined $val ) { + return $self->{_RanTransactionBatch} = $val; + } else { + return $self->{_RanTransactionBatch}; + } + +} + =head2 TransactionBatch @@ -3376,6 +3425,22 @@ sub ApplyTransactionBatch { sub _ApplyTransactionBatch { my $self = shift; + + return if $self->RanTransactionBatch; + $self->RanTransactionBatch(1); + + my $still_exists = RT::Ticket->new( RT->SystemUser ); + $still_exists->Load( $self->Id ); + if (not $still_exists->Id) { + # The ticket has been removed from the database, but we still + # have pending TransactionBatch txns for it. Unfortunately, + # because it isn't in the DB anymore, attempting to run scrips + # on it may produce unpredictable results; simply drop the + # batched transactions. + $RT::Logger->warning("TransactionBatch was fired on a ticket that no longer exists; unable to run scrips! Call ->ApplyTransactionBatch before shredding the ticket, for consistent results."); + return; + } + my $batch = $self->TransactionBatch; my %seen; @@ -3423,10 +3488,7 @@ sub DESTROY { return; } - my $batch = $self->TransactionBatch; - return unless $batch && @$batch; - - return $self->_ApplyTransactionBatch; + return $self->ApplyTransactionBatch; } diff --git a/rt/lib/RT/Tickets.pm b/rt/lib/RT/Tickets.pm index 485d7df53..c9986f41e 100755 --- a/rt/lib/RT/Tickets.pm +++ b/rt/lib/RT/Tickets.pm @@ -436,6 +436,10 @@ sub _LinkLimit { my $is_null = 0; $is_null = 1 if !$value || $value =~ /^null$/io; + unless ($is_null) { + $value = RT::URI->new( $sb->CurrentUser )->CanonicalizeURI( $value ); + } + my $direction = $meta->[1] || ''; my ($matchfield, $linkfield) = ('', ''); if ( $direction eq 'To' ) { @@ -1651,6 +1655,7 @@ sub _CustomFieldLimit { FIELD => $column, OPERATOR => $op, VALUE => $value, + CASESENSITIVE => 0, %rest ) ); $self->_CloseParen; @@ -1713,6 +1718,7 @@ sub _CustomFieldLimit { FIELD => 'Content', OPERATOR => $op, VALUE => $value, + CASESENSITIVE => 0, %rest ); } @@ -1739,6 +1745,7 @@ sub _CustomFieldLimit { OPERATOR => $op, VALUE => $value, ENTRYAGGREGATOR => 'AND', + CASESENSITIVE => 0, ) ); } } @@ -1748,6 +1755,7 @@ sub _CustomFieldLimit { FIELD => 'Content', OPERATOR => $op, VALUE => $value, + CASESENSITIVE => 0, %rest ); @@ -1774,6 +1782,7 @@ sub _CustomFieldLimit { OPERATOR => $op, VALUE => $value, ENTRYAGGREGATOR => 'AND', + CASESENSITIVE => 0, ) ); $self->_CloseParen; } @@ -1830,6 +1839,7 @@ sub _CustomFieldLimit { FIELD => $column, OPERATOR => $op, VALUE => $value, + CASESENSITIVE => 0, ) ); } else { @@ -1839,6 +1849,7 @@ sub _CustomFieldLimit { FIELD => 'Content', OPERATOR => $op, VALUE => $value, + CASESENSITIVE => 0, ); } $self->_SQLLimit( diff --git a/rt/lib/RT/URI.pm b/rt/lib/RT/URI.pm index fce04598a..284a75ee0 100644 --- a/rt/lib/RT/URI.pm +++ b/rt/lib/RT/URI.pm @@ -91,7 +91,26 @@ sub new { return ($self); } +=head2 CanonicalizeURI <URI> +Returns the canonical form of the given URI by calling L</FromURI> and then L</URI>. + +If the URI is unparseable by FromURI the passed in URI is simply returned untouched. + +=cut + +sub CanonicalizeURI { + my $self = shift; + my $uri = shift; + if ($self->FromURI($uri)) { + my $canonical = $self->URI; + if ($canonical and $uri ne $canonical) { + RT->Logger->debug("Canonicalizing URI '$uri' to '$canonical'"); + $uri = $canonical; + } + } + return $uri; +} =head2 FromObject <Object> diff --git a/rt/lib/RT/User.pm b/rt/lib/RT/User.pm index 9b4a82683..e7f7c2ad6 100755 --- a/rt/lib/RT/User.pm +++ b/rt/lib/RT/User.pm @@ -932,7 +932,7 @@ sub IsPassword { # crypt() output return 0 unless crypt(encode_utf8($value), $stored) eq $stored; } else { - $RT::Logger->warn("Unknown password form"); + $RT::Logger->warning("Unknown password form"); return 0; } diff --git a/rt/sbin/standalone_httpd b/rt/sbin/standalone_httpd index 3386cd1fe..cef0f3102 100755 --- a/rt/sbin/standalone_httpd +++ b/rt/sbin/standalone_httpd @@ -172,7 +172,7 @@ if (caller) { require Plack::Runner; my $is_fastcgi = $0 =~ m/fcgi$/; -my $r = Plack::Runner->new( $0 =~ 'standalone' ? ( server => 'Standalone' ) : +my $r = Plack::Runner->new( $0 =~ /standalone/ ? ( server => 'Standalone' ) : $is_fastcgi ? ( server => 'FCGI' ) : (), env => 'deployment' ); diff --git a/rt/sbin/standalone_httpd.in b/rt/sbin/standalone_httpd.in index 45c377088..f84f6c103 100644 --- a/rt/sbin/standalone_httpd.in +++ b/rt/sbin/standalone_httpd.in @@ -172,7 +172,7 @@ if (caller) { require Plack::Runner; my $is_fastcgi = $0 =~ m/fcgi$/; -my $r = Plack::Runner->new( $0 =~ 'standalone' ? ( server => 'Standalone' ) : +my $r = Plack::Runner->new( $0 =~ /standalone/ ? ( server => 'Standalone' ) : $is_fastcgi ? ( server => 'FCGI' ) : (), env => 'deployment' ); diff --git a/rt/share/html/Admin/Queues/Modify.html b/rt/share/html/Admin/Queues/Modify.html index 5682eee28..85cd62f16 100755 --- a/rt/share/html/Admin/Queues/Modify.html +++ b/rt/share/html/Admin/Queues/Modify.html @@ -51,7 +51,7 @@ -<form action="<%RT->Config->Get('WebPath')%>/Admin/Queues/Modify.html" name="ModifyQueue" method="post"> +<form action="<%RT->Config->Get('WebPath')%>/Admin/Queues/Modify.html" name="ModifyQueue" method="post" enctype="multipart/form-data"> <input type="hidden" class="hidden" name="SetEnabled" value="1" /> <input type="hidden" class="hidden" name="id" value="<% $Create? 'new': $QueueObj->Id %>" /> diff --git a/rt/share/html/Approvals/Elements/PendingMyApproval b/rt/share/html/Approvals/Elements/PendingMyApproval index d2061da84..169c25cb6 100755 --- a/rt/share/html/Approvals/Elements/PendingMyApproval +++ b/rt/share/html/Approvals/Elements/PendingMyApproval @@ -74,7 +74,7 @@ $tickets->LimitOwner( VALUE => $session{'CurrentUser'}->Id ); # also consider AdminCcs as potential approvers. my $group_tickets = RT::Tickets->new( $session{'CurrentUser'} ); -$group_tickets->LimitWatcher( VALUE => $session{'CurrentUser'}->UserObj->EmailAddress, TYPE => 'AdminCc' ); +$group_tickets->LimitWatcher( VALUE => $session{'CurrentUser'}->EmailAddress, TYPE => 'AdminCc' ); my $created_before = RT::Date->new( $session{'CurrentUser'} ); my $created_after = RT::Date->new( $session{'CurrentUser'} ); diff --git a/rt/share/html/Approvals/autohandler b/rt/share/html/Approvals/autohandler index a05770654..3e0f2c6db 100644 --- a/rt/share/html/Approvals/autohandler +++ b/rt/share/html/Approvals/autohandler @@ -46,8 +46,13 @@ %# %# END BPS TAGGED BLOCK }}} <%init> -$m->call_next(%ARGS) if $session{'CurrentUser'}->UserObj->HasRight( +if ( $session{'CurrentUser'}->UserObj->HasRight( Right => 'ShowApprovalsTab', Object => $RT::System, -); +) ) { + $m->call_next(%ARGS); +} +else { + Abort("No permission to view approval"); +} </%init> diff --git a/rt/share/html/Dashboards/Subscription.html b/rt/share/html/Dashboards/Subscription.html index 3669e4687..3a57102c7 100644 --- a/rt/share/html/Dashboards/Subscription.html +++ b/rt/share/html/Dashboards/Subscription.html @@ -171,7 +171,7 @@ <&|/l&>Recipient</&>: </td><td class="value"> <input name="Recipient" id="Recipient" size="30" value="<%$fields{Recipient} ? $fields{Recipient} : ''%>" /> -<div class="hints"><% loc("Leave blank to send to your current email address ([_1])", $session{'CurrentUser'}->UserObj->EmailAddress) %></div> +<div class="hints"><% loc("Leave blank to send to your current email address ([_1])", $session{'CurrentUser'}->EmailAddress) %></div> </td></tr> </table> </&> diff --git a/rt/share/html/Elements/ColumnMap b/rt/share/html/Elements/ColumnMap index b9c3b4bc8..f268a5d1c 100644 --- a/rt/share/html/Elements/ColumnMap +++ b/rt/share/html/Elements/ColumnMap @@ -118,7 +118,7 @@ my $COLUMN_MAP = { CheckBox => { title => sub { my $name = $_[1] || 'SelectedTickets'; - my $checked = $m->request_args->{ $name .'All' }? 'checked="checked"': ''; + my $checked = $DECODED_ARGS->{ $name .'All' }? 'checked="checked"': ''; return \qq{<input type="checkbox" name="}, $name, \qq{All" value="1" $checked onclick="setCheckbox(this.form, }, @@ -130,9 +130,9 @@ my $COLUMN_MAP = { my $name = $_[2] || 'SelectedTickets'; return \qq{<input type="checkbox" name="}, $name, \qq{" value="$id" checked="checked" />} - if $m->request_args->{ $name . 'All'}; + if $DECODED_ARGS->{ $name . 'All'}; - my $arg = $m->request_args->{ $name }; + my $arg = $DECODED_ARGS->{ $name }; my $checked = ''; if ( $arg && ref $arg ) { $checked = 'checked="checked"' if grep $_ == $id, @$arg; @@ -149,7 +149,7 @@ my $COLUMN_MAP = { my $id = $_[0]->id; my $name = $_[2] || 'SelectedTicket'; - my $arg = $m->request_args->{ $name }; + my $arg = $DECODED_ARGS->{ $name }; my $checked = ''; $checked = 'checked="checked"' if $arg && $arg == $id; return \qq{<input type="radio" name="}, $name, \qq{" value="$id" $checked />}; diff --git a/rt/share/html/Elements/EditCustomField b/rt/share/html/Elements/EditCustomField index b74c4844e..8b87fd425 100644 --- a/rt/share/html/Elements/EditCustomField +++ b/rt/share/html/Elements/EditCustomField @@ -71,7 +71,7 @@ if ( $Object && $Object->id ) { # Always fill $Default with submited values if it's empty if ( ( !defined $Default || !length $Default ) && $DefaultsFromTopArguments ) { - my %TOP = $m->request_args; + my %TOP = %$DECODED_ARGS; $Default = $TOP{ $NamePrefix .$CustomField->Id . '-Values' } || $TOP{ $NamePrefix .$CustomField->Id . '-Value' }; } diff --git a/rt/share/html/Elements/Header b/rt/share/html/Elements/Header index 1830c4bf2..65d06f879 100755 --- a/rt/share/html/Elements/Header +++ b/rt/share/html/Elements/Header @@ -130,7 +130,8 @@ if ($m->comp_exists($stylesheet_plugin) ) { # $m->callback( %ARGS, CallbackName => 'Head' ); $head .= $m->scomp( '/Elements/Callback', _CallbackName => 'Head', %ARGS ); -my $etc = qq[ class="\L$style" ]; +my $sbs = RT->Config->Get("UseSideBySideLayout", $session{'CurrentUser'}) ? ' sidebyside' : ''; +my $etc = qq[ class="\L$style$sbs" ]; $etc .= qq[ id="comp-$id"] if $id; </%INIT> diff --git a/rt/share/html/Elements/HeaderJavascript b/rt/share/html/Elements/HeaderJavascript index 28788db57..d5741f4e6 100644 --- a/rt/share/html/Elements/HeaderJavascript +++ b/rt/share/html/Elements/HeaderJavascript @@ -67,7 +67,7 @@ $onload => undef % } % if ( $RichText and RT->Config->Get('MessageBoxRichText', $session{'CurrentUser'})) { - jQuery().ready(function () { ReplaceAllTextareas(<%$m->request_args->{'CKeditorEncoded'} || 0 |n,j%>) }); + jQuery().ready(function () { ReplaceAllTextareas(<%$DECODED_ARGS->{'CKeditorEncoded'} || 0 |n,j%>) }); % } --></script> <%ARGS> diff --git a/rt/share/html/Elements/ListActions b/rt/share/html/Elements/ListActions index 999d3fe5b..8929ff731 100755 --- a/rt/share/html/Elements/ListActions +++ b/rt/share/html/Elements/ListActions @@ -65,7 +65,7 @@ if ( ref( $session{'Actions'}{''} ) eq 'ARRAY' ) { unshift @actions, @{ delete $session{'Actions'}{''} }; } -my $actions_pointer = $m->request_args->{'results'}; +my $actions_pointer = $DECODED_ARGS->{'results'}; if ($actions_pointer && ref( $session{'Actions'}->{$actions_pointer} ) eq 'ARRAY' ) { unshift @actions, @{ delete $session{'Actions'}->{$actions_pointer} }; diff --git a/rt/share/html/Elements/MessageBox b/rt/share/html/Elements/MessageBox index 61995e057..69227bfa9 100755 --- a/rt/share/html/Elements/MessageBox +++ b/rt/share/html/Elements/MessageBox @@ -46,7 +46,7 @@ %# %# END BPS TAGGED BLOCK }}} <textarea autocomplete="off" class="messagebox" <% $width_attr %>="<% $Width %>" rows="<% $Height %>" <% $wrap_type |n %> name="<% $Name %>" id="<% $Name %>">\ -% $m->comp('/Articles/Elements/IncludeArticle', %ARGS); +% $m->comp('/Articles/Elements/IncludeArticle', %ARGS) if $IncludeArticle; % $m->callback( %ARGS, SignatureRef => \$signature ); <% $Default || '' %><% $message %><% $signature %></textarea> % $m->callback( %ARGS, CallbackName => 'AfterTextArea' ); @@ -89,4 +89,5 @@ $Width => RT->Config->Get('MessageBoxWidth', $session{'CurrentUser'} $Height => RT->Config->Get('MessageBoxHeight', $session{'CurrentUser'} ) || 15 $Wrap => RT->Config->Get('MessageBoxWrap', $session{'CurrentUser'} ) || 'SOFT' $IncludeSignature => RT->Config->Get('MessageBoxIncludeSignature'); +$IncludeArticle => 1; </%ARGS> diff --git a/rt/share/html/Elements/QueueSummaryByStatus b/rt/share/html/Elements/QueueSummaryByStatus index 09f274f74..f649d2850 100644 --- a/rt/share/html/Elements/QueueSummaryByStatus +++ b/rt/share/html/Elements/QueueSummaryByStatus @@ -122,9 +122,13 @@ my $statuses = {}; use RT::Report::Tickets; my $report = RT::Report::Tickets->new( RT->SystemUser ); -my $query = @queues - ? join(' OR ', map "Queue = ".$_->{id}, @queues) - : 'id < 0'; +my $query = + "(". + join(" OR ", map {s{(['\\])}{\\$1}g; "Status = '$_'"} @statuses) #' + .") AND (". + join(' OR ', map "Queue = ".$_->{id}, @queues) + .")"; +$query = 'id < 0' unless @queues; $report->SetupGroupings( Query => $query, GroupBy => [qw(Status Queue)] ); while ( my $entry = $report->Next ) { diff --git a/rt/share/html/Elements/RT__CustomField/ColumnMap b/rt/share/html/Elements/RT__CustomField/ColumnMap index ecb219d9e..b04398434 100644 --- a/rt/share/html/Elements/RT__CustomField/ColumnMap +++ b/rt/share/html/Elements/RT__CustomField/ColumnMap @@ -118,7 +118,7 @@ my $COLUMN_MAP = { RemoveCheckBox => { title => sub { my $name = 'RemoveCustomField'; - my $checked = $m->request_args->{ $name .'All' }? 'checked="checked"': ''; + my $checked = $DECODED_ARGS->{ $name .'All' }? 'checked="checked"': ''; return \qq{<input type="checkbox" name="}, $name, \qq{All" value="1" $checked onclick="setCheckbox(this.form, }, @@ -130,7 +130,7 @@ my $COLUMN_MAP = { return '' if $_[0]->IsApplied; my $name = 'RemoveCustomField'; - my $arg = $m->request_args->{ $name }; + my $arg = $DECODED_ARGS->{ $name }; my $checked = ''; if ( $arg && ref $arg ) { diff --git a/rt/share/html/Elements/SelectWatcherType b/rt/share/html/Elements/SelectWatcherType index 44beee00d..4f1df60b2 100755 --- a/rt/share/html/Elements/SelectWatcherType +++ b/rt/share/html/Elements/SelectWatcherType @@ -56,7 +56,7 @@ <%INIT> my @types; -if ($Scope =~ 'queue') { +if ($Scope =~ /queue/) { @types = RT::Queue->ManageableRoleGroupTypes; } else { diff --git a/rt/share/html/Elements/Tabs b/rt/share/html/Elements/Tabs index 3193b488d..3aac9d803 100755 --- a/rt/share/html/Elements/Tabs +++ b/rt/share/html/Elements/Tabs @@ -845,7 +845,7 @@ my $build_selfservice_nav = sub { } elsif ( $queue_id ) { Menu->child( new => title => loc('New ticket'), path => '/SelfService/Create.html?Queue=' . $queue_id ); } - my $tickets = Menu->child( tickets => title => loc('Tickets')); + my $tickets = Menu->child( tickets => title => loc('Tickets'), path => '/SelfService/' ); $tickets->child( open => title => loc('Open tickets'), path => '/SelfService/' ); $tickets->child( closed => title => loc('Closed tickets'), path => '/SelfService/Closed.html' ); diff --git a/rt/share/html/Helpers/Autocomplete/Users b/rt/share/html/Helpers/Autocomplete/Users index dbc2d888f..c2b92c1bf 100644 --- a/rt/share/html/Helpers/Autocomplete/Users +++ b/rt/share/html/Helpers/Autocomplete/Users @@ -116,6 +116,9 @@ foreach (split /\s*,\s*/, $exclude) { my @suggestions; +$users->Limit( FIELD => $return, OPERATOR => '!=', VALUE => '' ); +$users->Limit( FIELD => $return, OPERATOR => 'IS NOT', VALUE => 'NULL', ENTRYAGGREGATOR => 'AND' ); + while ( my $user = $users->Next ) { next if $user->id == RT->SystemUser->id or $user->id == RT->Nobody->id; diff --git a/rt/share/html/NoAuth/css/aileron/boxes.css b/rt/share/html/NoAuth/css/aileron/boxes.css index ed6623cba..f90ac9f77 100644 --- a/rt/share/html/NoAuth/css/aileron/boxes.css +++ b/rt/share/html/NoAuth/css/aileron/boxes.css @@ -91,10 +91,6 @@ .titlebox .titlebox-title { position: relative; - /* This is for [rt3 #19044]. Move it to an IE-specific file if it causes - * problems. If we remove CSS3PIE, it can also probably go away, although it - * probably won't hurt. */ - z-index: 1; } .titlebox .titlebox-title a { diff --git a/rt/share/html/NoAuth/css/aileron/ticket.css b/rt/share/html/NoAuth/css/aileron/ticket.css index 4d069d9f9..7b573f72c 100644 --- a/rt/share/html/NoAuth/css/aileron/ticket.css +++ b/rt/share/html/NoAuth/css/aileron/ticket.css @@ -87,8 +87,7 @@ div#ticket-history { float: left; margin: 0.25em 0.70em 0.25em 0.25em; width: 1em; - height: 1.25em; - padding: 0.75em 0 0 0; + padding: 0; border-right: 1px solid #999; border-bottom: 1px solid #999; -moz-border-radius-bottomright: 0.25em; @@ -100,6 +99,16 @@ div#ticket-history { div#ticket-history span.type a { color: #fff; + padding-top: 0.75em; + display: block; +} + +#ticket-history a#lasttrans { + display: inline; + height: 0; + width: 0; + padding: 0; + margin: 0; } diff --git a/rt/share/html/NoAuth/css/ballard/boxes.css b/rt/share/html/NoAuth/css/ballard/boxes.css index 912ac55f4..9610cd5e7 100644 --- a/rt/share/html/NoAuth/css/ballard/boxes.css +++ b/rt/share/html/NoAuth/css/ballard/boxes.css @@ -54,6 +54,7 @@ margin-left: 1em; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; + border-radius: 0.5em; margin-bottom: 2em; border-bottom: 2px solid #aaa; border-right: 2px solid #aaa; @@ -71,6 +72,7 @@ margin-top: 1em; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; + border-radius: 0.5em; margin-right: 0.25em; } @@ -114,6 +116,7 @@ padding-right: 0.75em; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; + border-radius: 0.5em; border-bottom: 2px solid #aaa; border-right: 2px solid #aaa; @@ -138,10 +141,12 @@ padding-top: 0.5em; -moz-border-radius-bottomleft: 0.25em; -webkit-border-bottom-left-radius: 0.25em; + border-bottom-left-radius: 0.25em; -moz-border-radius-topright: 0.25em; -webkit-border-top-right-radius: 0.25em; + border-top-right-radius: 0.25em; } diff --git a/rt/share/html/NoAuth/css/ballard/layout.css b/rt/share/html/NoAuth/css/ballard/layout.css index 8dc0cc162..8b600b828 100644 --- a/rt/share/html/NoAuth/css/ballard/layout.css +++ b/rt/share/html/NoAuth/css/ballard/layout.css @@ -60,8 +60,10 @@ div#body { padding: 1.8em 1em 1em 1em; -moz-border-radius-topleft: 0.5em; -webkit-border-top-left-radius: 0.5em; + border-top-left-radius: 0.5em; -moz-border-radius-bottomleft: 0.5em; -webkit-border-bottom-left-radius: 0.5em; + border-bottom-left-radius: 0.5em; margin-left: 10em; margin-top: 3em; margin-right: 0; @@ -89,8 +91,10 @@ div#footer { border-left: 2px solid #aaa; -moz-border-radius-topleft: 0.5em; -webkit-border-top-left-radius: 0.5em; + border-top-left-radius: 0.5em; -moz-border-radius-bottomleft: 0.5em; -webkit-border-bottom-left-radius: 0.5em; + border-bottom-left-radius: 0.5em; } div#footer #time { diff --git a/rt/share/html/NoAuth/css/ballard/nav.css b/rt/share/html/NoAuth/css/ballard/nav.css index 196f0e6c0..dc29818fe 100644 --- a/rt/share/html/NoAuth/css/ballard/nav.css +++ b/rt/share/html/NoAuth/css/ballard/nav.css @@ -49,8 +49,10 @@ background-color: #fff; -moz-border-radius-bottomright: 0.5em; -webkit-border-bottom-right-radius: 0.5em; + border-bottom-right-radius: 0.5em; -moz-border-radius-topright: 0.5em; -webkit-border-top-right-radius: 0.5em; + border-top-right-radius: 0.5em; width: 10em; font-size: 0.85em; position: absolute; @@ -130,6 +132,7 @@ border: 1px solid #ccc; -moz-border-radius-bottomleft: 0.5em; -webkit-border-bottom-left-radius: 0.5em; + border-bottom-left-radius: 0.5em; padding: 0; padding-top: 0.5em; padding-right: 0.5em; diff --git a/rt/share/html/NoAuth/css/ballard/ticket-search.css b/rt/share/html/NoAuth/css/ballard/ticket-search.css index 19ee847ff..fb252b5e3 100644 --- a/rt/share/html/NoAuth/css/ballard/ticket-search.css +++ b/rt/share/html/NoAuth/css/ballard/ticket-search.css @@ -163,6 +163,7 @@ border-bottom: 1px solid #999; -moz-border-radius-bottomleft: 0.5em; -webkit-border-bottom-left-radius: 0.5em; + border-bottom-left-radius: 0.5em; } diff --git a/rt/share/html/NoAuth/css/ballard/ticket.css b/rt/share/html/NoAuth/css/ballard/ticket.css index 06b6678c9..4d416e175 100644 --- a/rt/share/html/NoAuth/css/ballard/ticket.css +++ b/rt/share/html/NoAuth/css/ballard/ticket.css @@ -77,6 +77,7 @@ div#ticket-history { color: #ccc; -moz-border-radius-bottomleft: 0.5em; -webkit-border-bottom-left-radius: 0.5em; + border-bottom-left-radius: 0.5em; white-space: nowrap; } @@ -91,6 +92,7 @@ div#ticket-history { border-bottom: 1px solid #999; -moz-border-radius: 0.25em; -webkit-border-bottom-right-radius: 0.25em; + border-bottom-right-radius: 0.25em; } div#ticket-history span.type a { @@ -150,6 +152,7 @@ border-bottom: 2px solid #aaa; margin-top: 0.5em; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; +border-radius: 0.5em; } diff --git a/rt/share/html/NoAuth/css/base/forms.css b/rt/share/html/NoAuth/css/base/forms.css index eab97b19b..19af1b2a3 100644 --- a/rt/share/html/NoAuth/css/base/forms.css +++ b/rt/share/html/NoAuth/css/base/forms.css @@ -87,6 +87,7 @@ input[type=reset], input[type=submit], input[class=button], button { padding-right: 0.5em; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; + border-radius: 0.5em; } input.button:hover, button:hover, input[type=reset]:hover, input[type=submit]:hover, input[class=button]:hover { diff --git a/rt/share/html/NoAuth/css/base/jquery-ui-timepicker-addon.css b/rt/share/html/NoAuth/css/base/jquery-ui-timepicker-addon.css new file mode 100644 index 000000000..7eb871568 --- /dev/null +++ b/rt/share/html/NoAuth/css/base/jquery-ui-timepicker-addon.css @@ -0,0 +1,7 @@ +.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } +.ui-timepicker-div dl { text-align: left; } +.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } +.ui-timepicker-div dl dd { margin: 0 10px 10px 65px; } +.ui-timepicker-div td { font-size: 90%; } +.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } +.ui-datepicker-buttonpane button.ui-datepicker-current { opacity: 1.0; } diff --git a/rt/share/html/NoAuth/css/base/jquery-ui.css b/rt/share/html/NoAuth/css/base/jquery-ui.css index 820996ea8..8fe4f1545 100644 --- a/rt/share/html/NoAuth/css/base/jquery-ui.css +++ b/rt/share/html/NoAuth/css/base/jquery-ui.css @@ -46,5 +46,3 @@ %# %# END BPS TAGGED BLOCK }}} @import "jquery-ui.custom.modified.css"; -@import "ui.timepickr.css"; -@import "ui.timepickr.custom.css"; diff --git a/rt/share/html/NoAuth/css/base/jquery-ui.custom.modified.css b/rt/share/html/NoAuth/css/base/jquery-ui.custom.modified.css index 7a323229a..3b1e1a00e 100644 --- a/rt/share/html/NoAuth/css/base/jquery-ui.custom.modified.css +++ b/rt/share/html/NoAuth/css/base/jquery-ui.custom.modified.css @@ -452,3 +452,27 @@ width: 200px; /*must have*/ height: 200px; /*must have*/ } +/* + * jQuery UI Slider 1.8.4 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider#theming + */ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; } diff --git a/rt/share/html/NoAuth/css/base/main.css b/rt/share/html/NoAuth/css/base/main.css index 9f77c8aee..dac733d87 100644 --- a/rt/share/html/NoAuth/css/base/main.css +++ b/rt/share/html/NoAuth/css/base/main.css @@ -49,6 +49,7 @@ @import "yui-fonts.css"; @import "jquery-ui.css"; +@import "jquery-ui-timepicker-addon.css"; @import "superfish.css"; @import "superfish-navbar.css"; @import "superfish-vertical.css"; diff --git a/rt/share/html/NoAuth/css/base/superfish-navbar.css b/rt/share/html/NoAuth/css/base/superfish-navbar.css index 9a3f24cd9..459156ec7 100644 --- a/rt/share/html/NoAuth/css/base/superfish-navbar.css +++ b/rt/share/html/NoAuth/css/base/superfish-navbar.css @@ -90,4 +90,6 @@ ul.sf-navbar .current ul ul { -moz-border-radius-topright: 0; -webkit-border-top-right-radius: 0; -webkit-border-bottom-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; } diff --git a/rt/share/html/NoAuth/css/base/superfish.css b/rt/share/html/NoAuth/css/base/superfish.css index 31198e423..7cb3b567c 100644 --- a/rt/share/html/NoAuth/css/base/superfish.css +++ b/rt/share/html/NoAuth/css/base/superfish.css @@ -130,6 +130,8 @@ li.sfHover > a > .sf-sub-indicator { -moz-border-radius-topright: 17px; -webkit-border-top-right-radius: 17px; -webkit-border-bottom-left-radius: 17px; + border-top-right-radius: 17px; + border-bottom-left-radius: 17px; } .sf-shadow ul.sf-shadow-off { background: transparent; diff --git a/rt/share/html/NoAuth/css/base/ticket-form.css b/rt/share/html/NoAuth/css/base/ticket-form.css index daab263b1..869eba774 100644 --- a/rt/share/html/NoAuth/css/base/ticket-form.css +++ b/rt/share/html/NoAuth/css/base/ticket-form.css @@ -82,21 +82,17 @@ iframe.richtext-editor { .messagebox-container.action-response iframe { background-color: #fcc !important; -} - -/* -% if ( RT->Config->Get("UseSideBySideLayout", $session{'CurrentUser'}) ) { -*/ +} -#ticket-create-metadata, -#ticket-update-metadata { +.sidebyside #ticket-create-metadata, +.sidebyside #ticket-update-metadata { float: right; width: 40%; clear: right; } -#ticket-create-message, -#ticket-update-message { +.sidebyside #ticket-create-message, +.sidebyside #ticket-update-message { float: left; width: 58%; clear: left; @@ -104,10 +100,10 @@ iframe.richtext-editor { @media (max-width: 950px) { /* Revert to a single column when we're less than 1000px wide */ - #ticket-create-metadata, - #ticket-update-metadata, - #ticket-create-message, - #ticket-update-message + .sidebyside #ticket-create-metadata, + .sidebyside #ticket-update-metadata, + .sidebyside #ticket-create-message, + .sidebyside #ticket-update-message { float: none; width: auto; @@ -115,15 +111,12 @@ iframe.richtext-editor { } } -#comp-Ticket-Update #body { +.sidebyside #comp-Ticket-Update #body { padding-top: 3em; } -#ticket-create-message .button[name="AddMoreAttach"], -#ticket-update-message .button[name="AddMoreAttach"] { +.sidebyside #ticket-create-message .button[name="AddMoreAttach"], +.sidebyside #ticket-update-message .button[name="AddMoreAttach"] { float: right; } -/* -% } -*/ diff --git a/rt/share/html/NoAuth/css/base/ui.timepickr.css b/rt/share/html/NoAuth/css/base/ui.timepickr.css deleted file mode 100644 index e2dacf7a9..000000000 --- a/rt/share/html/NoAuth/css/base/ui.timepickr.css +++ /dev/null @@ -1,56 +0,0 @@ -/* - jQuery ui.timepickr - http://code.google.com/p/jquery-utils/ - - copyright Maxime Haineault <haineault@gmail.com> - http://haineault.com - - MIT License (http://www.opensource.org/licenses/mit-license.php -*/ -.ui-timepickr { - position:absolute; - width:480px; -} - -.ui-timepickr-row { - margin:0; - padding:0; - margin-top:2px; - display:none; - position:relative; -} - -.ui-timepickr-button { - float:left; - margin:0; - padding:0; - list-style:none; - list-style-type:none; -} - -.ui-timepickr-button span { - font-size:.7em; - padding:4px 6px 4px 6px; - margin-left:2px; - text-align:center; - cursor:pointer; - display:block; - text-align:center; - - - /* system theme (default) */ - border-width:1px; - border-style:solid; - /*border-color:ThreeDLightShadow ThreeDShadow ThreeDShadow ThreeDLightShadow; - color:ButtonText; - background:ButtonFace;*/ -} - -.ui-timepickr-button span.ui-state-hover { - /*color:HighlightText; - background:Highlight;*/ -} - -.ui-state-hover span { - /*background:#c30;*/ -} diff --git a/rt/share/html/NoAuth/css/base/ui.timepickr.custom.css b/rt/share/html/NoAuth/css/base/ui.timepickr.custom.css deleted file mode 100644 index ad2aa66ce..000000000 --- a/rt/share/html/NoAuth/css/base/ui.timepickr.custom.css +++ /dev/null @@ -1,54 +0,0 @@ -%# BEGIN BPS TAGGED BLOCK {{{ -%# -%# COPYRIGHT: -%# -%# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC -%# <sales@bestpractical.com> -%# -%# (Except where explicitly superseded by other copyright notices) -%# -%# -%# LICENSE: -%# -%# This work is made available to you under the terms of Version 2 of -%# the GNU General Public License. A copy of that license should have -%# been provided with this software, but in any event can be snarfed -%# from www.gnu.org. -%# -%# This work is distributed in the hope that it will be useful, but -%# WITHOUT ANY WARRANTY; without even the implied warranty of -%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%# General Public License for more details. -%# -%# You should have received a copy of the GNU General Public License -%# along with this program; if not, write to the Free Software -%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -%# 02110-1301 or visit their web page on the internet at -%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -%# -%# -%# CONTRIBUTION SUBMISSION POLICY: -%# -%# (The following paragraph is not intended to limit the rights granted -%# to you to modify and distribute this software under the terms of -%# the GNU General Public License and is only of importance to you if -%# you choose to contribute your changes and enhancements to the -%# community by submitting them to Best Practical Solutions, LLC.) -%# -%# By intentionally submitting any modifications, corrections or -%# derivatives to this work, or any other work intended for use with -%# Request Tracker, to Best Practical Solutions, LLC, you confirm that -%# you are the copyright holder for those contributions and you grant -%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -%# royalty-free, perpetual, license to use, copy, create derivative -%# works based on those contributions, and sublicense and distribute -%# those contributions and any derivatives thereof. -%# -%# END BPS TAGGED BLOCK }}} -.ui-timepickr { - font-size: 1.1em; -} - -.ui-timepickr-button span { - background: white; -} diff --git a/rt/share/html/NoAuth/css/web2/nav.css b/rt/share/html/NoAuth/css/web2/nav.css index be63c5984..e404b61c8 100644 --- a/rt/share/html/NoAuth/css/web2/nav.css +++ b/rt/share/html/NoAuth/css/web2/nav.css @@ -239,6 +239,7 @@ border: 1px solid #ccc; -moz-border-radius-bottomleft: 0.5em; -webkit-border-bottom-left-radius: 0.5em; + border-bottom-left-radius: 0.5em; border-right: none; border-top: none; list-style-type: none; diff --git a/rt/share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js b/rt/share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js index e90b4fe4b..0466005dc 100644 --- a/rt/share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js +++ b/rt/share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js @@ -222,3 +222,53 @@ c=this._daylightSavingAdjust(new Date(c,e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(t function(a){if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b)); return this.each(function(){typeof a=="string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new L;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.4";window["DP_jQuery_"+y]=d})(jQuery); ; +/*! + * jQuery UI Mouse 1.8.4 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Mouse + * + * Depends: + * jquery.ui.widget.js + */ +(function(c){c.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(b){return a._mouseDown(b)}).bind("click."+this.widgetName,function(b){if(a._preventClickEvent){a._preventClickEvent=false;b.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName)},_mouseDown:function(a){a.originalEvent=a.originalEvent||{};if(!a.originalEvent.mouseHandled){this._mouseStarted&& +this._mouseUp(a);this._mouseDownEvent=a;var b=this,e=a.which==1,f=typeof this.options.cancel=="string"?c(a.target).parents().add(a.target).filter(this.options.cancel).length:false;if(!e||f||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){b.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted=this._mouseStart(a)!==false;if(!this._mouseStarted){a.preventDefault(); +return true}}this._mouseMoveDelegate=function(d){return b._mouseMove(d)};this._mouseUpDelegate=function(d){return b._mouseUp(d)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);c.browser.safari||a.preventDefault();return a.originalEvent.mouseHandled=true}},_mouseMove:function(a){if(c.browser.msie&&!(document.documentMode>=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&& +this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=a.target==this._mouseDownEvent.target;this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX- +a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); +/* + * jQuery UI Slider 1.8.4 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function(d){d.widget("ui.slider",d.ui.mouse,{widgetEventPrefix:"slide",options:{animate:false,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null},_create:function(){var a=this,b=this.options;this._mouseSliding=this._keySliding=false;this._animateOff=true;this._handleIndex=null;this._detectOrientation();this._mouseInit();this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget ui-widget-content ui-corner-all");b.disabled&&this.element.addClass("ui-slider-disabled ui-disabled"); +this.range=d([]);if(b.range){if(b.range===true){this.range=d("<div></div>");if(!b.values)b.values=[this._valueMin(),this._valueMin()];if(b.values.length&&b.values.length!==2)b.values=[b.values[0],b.values[0]]}else this.range=d("<div></div>");this.range.appendTo(this.element).addClass("ui-slider-range");if(b.range==="min"||b.range==="max")this.range.addClass("ui-slider-range-"+b.range);this.range.addClass("ui-widget-header")}d(".ui-slider-handle",this.element).length===0&&d("<a href='#'></a>").appendTo(this.element).addClass("ui-slider-handle"); +if(b.values&&b.values.length)for(;d(".ui-slider-handle",this.element).length<b.values.length;)d("<a href='#'></a>").appendTo(this.element).addClass("ui-slider-handle");this.handles=d(".ui-slider-handle",this.element).addClass("ui-state-default ui-corner-all");this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(c){c.preventDefault()}).hover(function(){b.disabled||d(this).addClass("ui-state-hover")},function(){d(this).removeClass("ui-state-hover")}).focus(function(){if(b.disabled)d(this).blur(); +else{d(".ui-slider .ui-state-focus").removeClass("ui-state-focus");d(this).addClass("ui-state-focus")}}).blur(function(){d(this).removeClass("ui-state-focus")});this.handles.each(function(c){d(this).data("index.ui-slider-handle",c)});this.handles.keydown(function(c){var e=true,f=d(this).data("index.ui-slider-handle"),h,g,i;if(!a.options.disabled){switch(c.keyCode){case d.ui.keyCode.HOME:case d.ui.keyCode.END:case d.ui.keyCode.PAGE_UP:case d.ui.keyCode.PAGE_DOWN:case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:e= +false;if(!a._keySliding){a._keySliding=true;d(this).addClass("ui-state-active");h=a._start(c,f);if(h===false)return}break}i=a.options.step;h=a.options.values&&a.options.values.length?(g=a.values(f)):(g=a.value());switch(c.keyCode){case d.ui.keyCode.HOME:g=a._valueMin();break;case d.ui.keyCode.END:g=a._valueMax();break;case d.ui.keyCode.PAGE_UP:g=a._trimAlignValue(h+(a._valueMax()-a._valueMin())/5);break;case d.ui.keyCode.PAGE_DOWN:g=a._trimAlignValue(h-(a._valueMax()-a._valueMin())/5);break;case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:if(h=== +a._valueMax())return;g=a._trimAlignValue(h+i);break;case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:if(h===a._valueMin())return;g=a._trimAlignValue(h-i);break}a._slide(c,f,g);return e}}).keyup(function(c){var e=d(this).data("index.ui-slider-handle");if(a._keySliding){a._keySliding=false;a._stop(c,e);a._change(c,e);d(this).removeClass("ui-state-active")}});this._refreshValue();this._animateOff=false},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider"); +this._mouseDestroy();return this},_mouseCapture:function(a){var b=this.options,c,e,f,h,g;if(b.disabled)return false;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();c=this._normValueFromMouse({x:a.pageX,y:a.pageY});e=this._valueMax()-this._valueMin()+1;h=this;this.handles.each(function(i){var j=Math.abs(c-h.values(i));if(e>j){e=j;f=d(this);g=i}});if(b.range===true&&this.values(1)===b.min){g+=1;f=d(this.handles[g])}if(this._start(a, +g)===false)return false;this._mouseSliding=true;h._handleIndex=g;f.addClass("ui-state-active").focus();b=f.offset();this._clickOffset=!d(a.target).parents().andSelf().is(".ui-slider-handle")?{left:0,top:0}:{left:a.pageX-b.left-f.width()/2,top:a.pageY-b.top-f.height()/2-(parseInt(f.css("borderTopWidth"),10)||0)-(parseInt(f.css("borderBottomWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0)};this._slide(a,g,c);return this._animateOff=true},_mouseStart:function(){return true},_mouseDrag:function(a){var b= +this._normValueFromMouse({x:a.pageX,y:a.pageY});this._slide(a,this._handleIndex,b);return false},_mouseStop:function(a){this.handles.removeClass("ui-state-active");this._mouseSliding=false;this._stop(a,this._handleIndex);this._change(a,this._handleIndex);this._clickOffset=this._handleIndex=null;return this._animateOff=false},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(a){var b;if(this.orientation==="horizontal"){b= +this.elementSize.width;a=a.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{b=this.elementSize.height;a=a.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}b=a/b;if(b>1)b=1;if(b<0)b=0;if(this.orientation==="vertical")b=1-b;a=this._valueMax()-this._valueMin();return this._trimAlignValue(this._valueMin()+b*a)},_start:function(a,b){var c={handle:this.handles[b],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(b); +c.values=this.values()}return this._trigger("start",a,c)},_slide:function(a,b,c){var e;if(this.options.values&&this.options.values.length){e=this.values(b?0:1);if(this.options.values.length===2&&this.options.range===true&&(b===0&&c>e||b===1&&c<e))c=e;if(c!==this.values(b)){e=this.values();e[b]=c;a=this._trigger("slide",a,{handle:this.handles[b],value:c,values:e});this.values(b?0:1);a!==false&&this.values(b,c,true)}}else if(c!==this.value()){a=this._trigger("slide",a,{handle:this.handles[b],value:c}); +a!==false&&this.value(c)}},_stop:function(a,b){var c={handle:this.handles[b],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(b);c.values=this.values()}this._trigger("stop",a,c)},_change:function(a,b){if(!this._keySliding&&!this._mouseSliding){var c={handle:this.handles[b],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(b);c.values=this.values()}this._trigger("change",a,c)}},value:function(a){if(arguments.length){this.options.value= +this._trimAlignValue(a);this._refreshValue();this._change(null,0)}return this._value()},values:function(a,b){var c,e,f;if(arguments.length>1){this.options.values[a]=this._trimAlignValue(b);this._refreshValue();this._change(null,a)}if(arguments.length)if(d.isArray(arguments[0])){c=this.options.values;e=arguments[0];for(f=0;f<c.length;f+=1){c[f]=this._trimAlignValue(e[f]);this._change(null,f)}this._refreshValue()}else return this.options.values&&this.options.values.length?this._values(a):this.value(); +else return this._values()},_setOption:function(a,b){var c,e=0;if(d.isArray(this.options.values))e=this.options.values.length;d.Widget.prototype._setOption.apply(this,arguments);switch(a){case "disabled":if(b){this.handles.filter(".ui-state-focus").blur();this.handles.removeClass("ui-state-hover");this.handles.attr("disabled","disabled");this.element.addClass("ui-disabled")}else{this.handles.removeAttr("disabled");this.element.removeClass("ui-disabled")}break;case "orientation":this._detectOrientation(); +this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation);this._refreshValue();break;case "value":this._animateOff=true;this._refreshValue();this._change(null,0);this._animateOff=false;break;case "values":this._animateOff=true;this._refreshValue();for(c=0;c<e;c+=1)this._change(null,c);this._animateOff=false;break}},_value:function(){var a=this.options.value;return a=this._trimAlignValue(a)},_values:function(a){var b,c;if(arguments.length){b=this.options.values[a]; +return b=this._trimAlignValue(b)}else{b=this.options.values.slice();for(c=0;c<b.length;c+=1)b[c]=this._trimAlignValue(b[c]);return b}},_trimAlignValue:function(a){if(a<this._valueMin())return this._valueMin();if(a>this._valueMax())return this._valueMax();var b=this.options.step>0?this.options.step:1,c=a%b;a=a-c;if(Math.abs(c)*2>=b)a+=c>0?b:-b;return parseFloat(a.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var a= +this.options.range,b=this.options,c=this,e=!this._animateOff?b.animate:false,f,h={},g,i,j,l;if(this.options.values&&this.options.values.length)this.handles.each(function(k){f=(c.values(k)-c._valueMin())/(c._valueMax()-c._valueMin())*100;h[c.orientation==="horizontal"?"left":"bottom"]=f+"%";d(this).stop(1,1)[e?"animate":"css"](h,b.animate);if(c.options.range===true)if(c.orientation==="horizontal"){if(k===0)c.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},b.animate);if(k===1)c.range[e?"animate":"css"]({width:f- +g+"%"},{queue:false,duration:b.animate})}else{if(k===0)c.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},b.animate);if(k===1)c.range[e?"animate":"css"]({height:f-g+"%"},{queue:false,duration:b.animate})}g=f});else{i=this.value();j=this._valueMin();l=this._valueMax();f=l!==j?(i-j)/(l-j)*100:0;h[c.orientation==="horizontal"?"left":"bottom"]=f+"%";this.handle.stop(1,1)[e?"animate":"css"](h,b.animate);if(a==="min"&&this.orientation==="horizontal")this.range.stop(1,1)[e?"animate":"css"]({width:f+"%"}, +b.animate);if(a==="max"&&this.orientation==="horizontal")this.range[e?"animate":"css"]({width:100-f+"%"},{queue:false,duration:b.animate});if(a==="min"&&this.orientation==="vertical")this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},b.animate);if(a==="max"&&this.orientation==="vertical")this.range[e?"animate":"css"]({height:100-f+"%"},{queue:false,duration:b.animate})}}});d.extend(d.ui.slider,{version:"1.8.4"})})(jQuery); diff --git a/rt/share/html/NoAuth/js/jquery-ui-patch-datepicker.js b/rt/share/html/NoAuth/js/jquery-ui-patch-datepicker.js index 40cc0db99..2ac101f93 100644 --- a/rt/share/html/NoAuth/js/jquery-ui-patch-datepicker.js +++ b/rt/share/html/NoAuth/js/jquery-ui-patch-datepicker.js @@ -58,4 +58,35 @@ return data; }; + + $.datepicker._checkOffset_orig = $.datepicker._checkOffset; + $.datepicker._checkOffset = function(inst, offset, isFixed) { + // copied from the original + var dpHeight = inst.dpDiv.outerHeight(); + var inputHeight = inst.input ? inst.input.outerHeight() : 0; + var viewHeight = document.documentElement.clientHeight + $(document).scrollTop(); + + // save the original offset rather than the new offset because the + // original function modifies the passed arg as a side-effect + var old_offset = { top: offset.top, left: offset.left }; + offset = $.datepicker._checkOffset_orig(inst, offset, isFixed); + + // Negate any up or down positioning by adding instead of subtracting + offset.top += Math.min(old_offset.top, (old_offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? + Math.abs(dpHeight + inputHeight) : 0); + + return offset; + }; + + + $.timepicker._newInst_orig = $.timepicker._newInst; + $.timepicker._newInst = function($input, o) { + var tp_inst = $.timepicker._newInst_orig($input, o); + tp_inst._defaults.onClose = function(dateText, dp_inst) { + if ($.isFunction(o.onClose)) + o.onClose.call($input[0], dateText, dp_inst, tp_inst); + }; + return tp_inst; + }; + })(jQuery); diff --git a/rt/share/html/NoAuth/js/jquery-ui-timepicker-addon.js b/rt/share/html/NoAuth/js/jquery-ui-timepicker-addon.js new file mode 100644 index 000000000..0a4ff026e --- /dev/null +++ b/rt/share/html/NoAuth/js/jquery-ui-timepicker-addon.js @@ -0,0 +1,1326 @@ +/* +* jQuery timepicker addon +* By: Trent Richardson [http://trentrichardson.com] +* Version 1.0.0 +* Last Modified: 02/05/2012 +* +* Copyright 2012 Trent Richardson +* Dual licensed under the MIT and GPL licenses. +* http://trentrichardson.com/Impromptu/GPL-LICENSE.txt +* http://trentrichardson.com/Impromptu/MIT-LICENSE.txt +* +* HERES THE CSS: +* .ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } +* .ui-timepicker-div dl { text-align: left; } +* .ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } +* .ui-timepicker-div dl dd { margin: 0 10px 10px 65px; } +* .ui-timepicker-div td { font-size: 90%; } +* .ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } +*/ + +(function($) { + +// Prevent "Uncaught RangeError: Maximum call stack size exceeded" +$.ui.timepicker = $.ui.timepicker || {}; +if ($.ui.timepicker.version) { + return; +} + +$.extend($.ui, { timepicker: { version: "1.0.0" } }); + +/* Time picker manager. + Use the singleton instance of this class, $.timepicker, to interact with the time picker. + Settings for (groups of) time pickers are maintained in an instance object, + allowing multiple different settings on the same page. */ + +function Timepicker() { + this.regional = []; // Available regional settings, indexed by language code + this.regional[''] = { // Default regional settings + currentText: 'Now', + closeText: 'Done', + ampm: false, + amNames: ['AM', 'A'], + pmNames: ['PM', 'P'], + timeFormat: 'hh:mm tt', + timeSuffix: '', + timeOnlyTitle: 'Choose Time', + timeText: 'Time', + hourText: 'Hour', + minuteText: 'Minute', + secondText: 'Second', + millisecText: 'Millisecond', + timezoneText: 'Time Zone' + }; + this._defaults = { // Global defaults for all the datetime picker instances + showButtonPanel: true, + timeOnly: false, + showHour: true, + showMinute: true, + showSecond: false, + showMillisec: false, + showTimezone: false, + showTime: true, + stepHour: 1, + stepMinute: 1, + stepSecond: 1, + stepMillisec: 1, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + timezone: '+0000', + hourMin: 0, + minuteMin: 0, + secondMin: 0, + millisecMin: 0, + hourMax: 23, + minuteMax: 59, + secondMax: 59, + millisecMax: 999, + minDateTime: null, + maxDateTime: null, + onSelect: null, + hourGrid: 0, + minuteGrid: 0, + secondGrid: 0, + millisecGrid: 0, + alwaysSetTime: true, + separator: ' ', + altFieldTimeOnly: true, + showTimepicker: true, + timezoneIso8609: false, + timezoneList: null, + addSliderAccess: false, + sliderAccessArgs: null + }; + $.extend(this._defaults, this.regional['']); +}; + +$.extend(Timepicker.prototype, { + $input: null, + $altInput: null, + $timeObj: null, + inst: null, + hour_slider: null, + minute_slider: null, + second_slider: null, + millisec_slider: null, + timezone_select: null, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + timezone: '+0000', + hourMinOriginal: null, + minuteMinOriginal: null, + secondMinOriginal: null, + millisecMinOriginal: null, + hourMaxOriginal: null, + minuteMaxOriginal: null, + secondMaxOriginal: null, + millisecMaxOriginal: null, + ampm: '', + formattedDate: '', + formattedTime: '', + formattedDateTime: '', + timezoneList: null, + + /* Override the default settings for all instances of the time picker. + @param settings object - the new settings to use as defaults (anonymous object) + @return the manager object */ + setDefaults: function(settings) { + extendRemove(this._defaults, settings || {}); + return this; + }, + + //######################################################################## + // Create a new Timepicker instance + //######################################################################## + _newInst: function($input, o) { + var tp_inst = new Timepicker(), + inlineSettings = {}; + + for (var attrName in this._defaults) { + var attrValue = $input.attr('time:' + attrName); + if (attrValue) { + try { + inlineSettings[attrName] = eval(attrValue); + } catch (err) { + inlineSettings[attrName] = attrValue; + } + } + } + tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, o, { + beforeShow: function(input, dp_inst) { + if ($.isFunction(o.beforeShow)) + return o.beforeShow(input, dp_inst, tp_inst); + }, + onChangeMonthYear: function(year, month, dp_inst) { + // Update the time as well : this prevents the time from disappearing from the $input field. + tp_inst._updateDateTime(dp_inst); + if ($.isFunction(o.onChangeMonthYear)) + o.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst); + }, + onClose: function(dateText, dp_inst) { + if (tp_inst.timeDefined === true && $input.val() != '') + tp_inst._updateDateTime(dp_inst); + if ($.isFunction(o.onClose)) + o.onClose.call($input[0], dateText, dp_inst, tp_inst); + }, + timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker'); + }); + tp_inst.amNames = $.map(tp_inst._defaults.amNames, function(val) { return val.toUpperCase(); }); + tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function(val) { return val.toUpperCase(); }); + + if (tp_inst._defaults.timezoneList === null) { + var timezoneList = []; + for (var i = -11; i <= 12; i++) + timezoneList.push((i >= 0 ? '+' : '-') + ('0' + Math.abs(i).toString()).slice(-2) + '00'); + if (tp_inst._defaults.timezoneIso8609) + timezoneList = $.map(timezoneList, function(val) { + return val == '+0000' ? 'Z' : (val.substring(0, 3) + ':' + val.substring(3)); + }); + tp_inst._defaults.timezoneList = timezoneList; + } + + tp_inst.hour = tp_inst._defaults.hour; + tp_inst.minute = tp_inst._defaults.minute; + tp_inst.second = tp_inst._defaults.second; + tp_inst.millisec = tp_inst._defaults.millisec; + tp_inst.ampm = ''; + tp_inst.$input = $input; + + if (o.altField) + tp_inst.$altInput = $(o.altField) + .css({ cursor: 'pointer' }) + .focus(function(){ $input.trigger("focus"); }); + + if(tp_inst._defaults.minDate==0 || tp_inst._defaults.minDateTime==0) + { + tp_inst._defaults.minDate=new Date(); + } + if(tp_inst._defaults.maxDate==0 || tp_inst._defaults.maxDateTime==0) + { + tp_inst._defaults.maxDate=new Date(); + } + + // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime.. + if(tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) + tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime()); + if(tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) + tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime()); + if(tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) + tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime()); + if(tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) + tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime()); + return tp_inst; + }, + + //######################################################################## + // add our sliders to the calendar + //######################################################################## + _addTimePicker: function(dp_inst) { + var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ? + this.$input.val() + ' ' + this.$altInput.val() : + this.$input.val(); + + this.timeDefined = this._parseTime(currDT); + this._limitMinMaxDateTime(dp_inst, false); + this._injectTimePicker(); + }, + + //######################################################################## + // parse the time string from input value or _setTime + //######################################################################## + _parseTime: function(timeString, withDate) { + var regstr = this._defaults.timeFormat.toString() + .replace(/h{1,2}/ig, '(\\d?\\d)') + .replace(/m{1,2}/ig, '(\\d?\\d)') + .replace(/s{1,2}/ig, '(\\d?\\d)') + .replace(/l{1}/ig, '(\\d?\\d?\\d)') + .replace(/t{1,2}/ig, this._getPatternAmpm()) + .replace(/z{1}/ig, '(z|[-+]\\d\\d:?\\d\\d)?') + .replace(/\s/g, '\\s?') + this._defaults.timeSuffix + '$', + order = this._getFormatPositions(), + ampm = '', + treg; + + if (!this.inst) this.inst = $.datepicker._getInst(this.$input[0]); + + if (withDate || !this._defaults.timeOnly) { + // the time should come after x number of characters and a space. + // x = at least the length of text specified by the date format + var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat'); + // escape special regex characters in the seperator + var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g"); + regstr = '^.{' + dp_dateFormat.length + ',}?' + this._defaults.separator.replace(specials, "\\$&") + regstr; + } + + treg = timeString.match(new RegExp(regstr, 'i')); + + if (treg) { + if (order.t !== -1) { + if (treg[order.t] === undefined || treg[order.t].length === 0) { + ampm = ''; + this.ampm = ''; + } else { + ampm = $.inArray(treg[order.t].toUpperCase(), this.amNames) !== -1 ? 'AM' : 'PM'; + this.ampm = this._defaults[ampm == 'AM' ? 'amNames' : 'pmNames'][0]; + } + } + + if (order.h !== -1) { + if (ampm == 'AM' && treg[order.h] == '12') + this.hour = 0; // 12am = 0 hour + else if (ampm == 'PM' && treg[order.h] != '12') + this.hour = (parseFloat(treg[order.h]) + 12).toFixed(0); // 12pm = 12 hour, any other pm = hour + 12 + else this.hour = Number(treg[order.h]); + } + + if (order.m !== -1) this.minute = Number(treg[order.m]); + if (order.s !== -1) this.second = Number(treg[order.s]); + if (order.l !== -1) this.millisec = Number(treg[order.l]); + if (order.z !== -1 && treg[order.z] !== undefined) { + var tz = treg[order.z].toUpperCase(); + switch (tz.length) { + case 1: // Z + tz = this._defaults.timezoneIso8609 ? 'Z' : '+0000'; + break; + case 5: // +hhmm + if (this._defaults.timezoneIso8609) + tz = tz.substring(1) == '0000' + ? 'Z' + : tz.substring(0, 3) + ':' + tz.substring(3); + break; + case 6: // +hh:mm + if (!this._defaults.timezoneIso8609) + tz = tz == 'Z' || tz.substring(1) == '00:00' + ? '+0000' + : tz.replace(/:/, ''); + else if (tz.substring(1) == '00:00') + tz = 'Z'; + break; + } + this.timezone = tz; + } + + return true; + + } + return false; + }, + + //######################################################################## + // pattern for standard and localized AM/PM markers + //######################################################################## + _getPatternAmpm: function() { + var markers = [], + o = this._defaults; + if (o.amNames) + $.merge(markers, o.amNames); + if (o.pmNames) + $.merge(markers, o.pmNames); + markers = $.map(markers, function(val) { return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&'); }); + return '(' + markers.join('|') + ')?'; + }, + + //######################################################################## + // figure out position of time elements.. cause js cant do named captures + //######################################################################## + _getFormatPositions: function() { + var finds = this._defaults.timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|t{1,2}|z)/g), + orders = { h: -1, m: -1, s: -1, l: -1, t: -1, z: -1 }; + + if (finds) + for (var i = 0; i < finds.length; i++) + if (orders[finds[i].toString().charAt(0)] == -1) + orders[finds[i].toString().charAt(0)] = i + 1; + + return orders; + }, + + //######################################################################## + // generate and inject html for timepicker into ui datepicker + //######################################################################## + _injectTimePicker: function() { + var $dp = this.inst.dpDiv, + o = this._defaults, + tp_inst = this, + // Added by Peter Medeiros: + // - Figure out what the hour/minute/second max should be based on the step values. + // - Example: if stepMinute is 15, then minMax is 45. + hourMax = parseInt((o.hourMax - ((o.hourMax - o.hourMin) % o.stepHour)) ,10), + minMax = parseInt((o.minuteMax - ((o.minuteMax - o.minuteMin) % o.stepMinute)) ,10), + secMax = parseInt((o.secondMax - ((o.secondMax - o.secondMin) % o.stepSecond)) ,10), + millisecMax = parseInt((o.millisecMax - ((o.millisecMax - o.millisecMin) % o.stepMillisec)) ,10), + dp_id = this.inst.id.toString().replace(/([^A-Za-z0-9_])/g, ''); + + // Prevent displaying twice + //if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0) { + if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0 && o.showTimepicker) { + var noDisplay = ' style="display:none;"', + html = '<div class="ui-timepicker-div" id="ui-timepicker-div-' + dp_id + '"><dl>' + + '<dt class="ui_tpicker_time_label" id="ui_tpicker_time_label_' + dp_id + '"' + + ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' + + '<dd class="ui_tpicker_time" id="ui_tpicker_time_' + dp_id + '"' + + ((o.showTime) ? '' : noDisplay) + '></dd>' + + '<dt class="ui_tpicker_hour_label" id="ui_tpicker_hour_label_' + dp_id + '"' + + ((o.showHour) ? '' : noDisplay) + '>' + o.hourText + '</dt>', + hourGridSize = 0, + minuteGridSize = 0, + secondGridSize = 0, + millisecGridSize = 0, + size = null; + + // Hours + html += '<dd class="ui_tpicker_hour"><div id="ui_tpicker_hour_' + dp_id + '"' + + ((o.showHour) ? '' : noDisplay) + '></div>'; + if (o.showHour && o.hourGrid > 0) { + html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>'; + + for (var h = o.hourMin; h <= hourMax; h += parseInt(o.hourGrid,10)) { + hourGridSize++; + var tmph = (o.ampm && h > 12) ? h-12 : h; + if (tmph < 10) tmph = '0' + tmph; + if (o.ampm) { + if (h == 0) tmph = 12 +'a'; + else if (h < 12) tmph += 'a'; + else tmph += 'p'; + } + html += '<td>' + tmph + '</td>'; + } + + html += '</tr></table></div>'; + } + html += '</dd>'; + + // Minutes + html += '<dt class="ui_tpicker_minute_label" id="ui_tpicker_minute_label_' + dp_id + '"' + + ((o.showMinute) ? '' : noDisplay) + '>' + o.minuteText + '</dt>'+ + '<dd class="ui_tpicker_minute"><div id="ui_tpicker_minute_' + dp_id + '"' + + ((o.showMinute) ? '' : noDisplay) + '></div>'; + + if (o.showMinute && o.minuteGrid > 0) { + html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>'; + + for (var m = o.minuteMin; m <= minMax; m += parseInt(o.minuteGrid,10)) { + minuteGridSize++; + html += '<td>' + ((m < 10) ? '0' : '') + m + '</td>'; + } + + html += '</tr></table></div>'; + } + html += '</dd>'; + + // Seconds + html += '<dt class="ui_tpicker_second_label" id="ui_tpicker_second_label_' + dp_id + '"' + + ((o.showSecond) ? '' : noDisplay) + '>' + o.secondText + '</dt>'+ + '<dd class="ui_tpicker_second"><div id="ui_tpicker_second_' + dp_id + '"'+ + ((o.showSecond) ? '' : noDisplay) + '></div>'; + + if (o.showSecond && o.secondGrid > 0) { + html += '<div style="padding-left: 1px"><table><tr>'; + + for (var s = o.secondMin; s <= secMax; s += parseInt(o.secondGrid,10)) { + secondGridSize++; + html += '<td>' + ((s < 10) ? '0' : '') + s + '</td>'; + } + + html += '</tr></table></div>'; + } + html += '</dd>'; + + // Milliseconds + html += '<dt class="ui_tpicker_millisec_label" id="ui_tpicker_millisec_label_' + dp_id + '"' + + ((o.showMillisec) ? '' : noDisplay) + '>' + o.millisecText + '</dt>'+ + '<dd class="ui_tpicker_millisec"><div id="ui_tpicker_millisec_' + dp_id + '"'+ + ((o.showMillisec) ? '' : noDisplay) + '></div>'; + + if (o.showMillisec && o.millisecGrid > 0) { + html += '<div style="padding-left: 1px"><table><tr>'; + + for (var l = o.millisecMin; l <= millisecMax; l += parseInt(o.millisecGrid,10)) { + millisecGridSize++; + html += '<td>' + ((l < 10) ? '0' : '') + l + '</td>'; + } + + html += '</tr></table></div>'; + } + html += '</dd>'; + + // Timezone + html += '<dt class="ui_tpicker_timezone_label" id="ui_tpicker_timezone_label_' + dp_id + '"' + + ((o.showTimezone) ? '' : noDisplay) + '>' + o.timezoneText + '</dt>'; + html += '<dd class="ui_tpicker_timezone" id="ui_tpicker_timezone_' + dp_id + '"' + + ((o.showTimezone) ? '' : noDisplay) + '></dd>'; + + html += '</dl></div>'; + $tp = $(html); + + // if we only want time picker... + if (o.timeOnly === true) { + $tp.prepend( + '<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' + + '<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' + + '</div>'); + $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide(); + } + + this.hour_slider = $tp.find('#ui_tpicker_hour_'+ dp_id).slider({ + orientation: "horizontal", + value: this.hour, + min: o.hourMin, + max: hourMax, + step: o.stepHour, + slide: function(event, ui) { + tp_inst.hour_slider.slider( "option", "value", ui.value); + tp_inst._onTimeChange(); + } + }); + + + // Updated by Peter Medeiros: + // - Pass in Event and UI instance into slide function + this.minute_slider = $tp.find('#ui_tpicker_minute_'+ dp_id).slider({ + orientation: "horizontal", + value: this.minute, + min: o.minuteMin, + max: minMax, + step: o.stepMinute, + slide: function(event, ui) { + tp_inst.minute_slider.slider( "option", "value", ui.value); + tp_inst._onTimeChange(); + } + }); + + this.second_slider = $tp.find('#ui_tpicker_second_'+ dp_id).slider({ + orientation: "horizontal", + value: this.second, + min: o.secondMin, + max: secMax, + step: o.stepSecond, + slide: function(event, ui) { + tp_inst.second_slider.slider( "option", "value", ui.value); + tp_inst._onTimeChange(); + } + }); + + this.millisec_slider = $tp.find('#ui_tpicker_millisec_'+ dp_id).slider({ + orientation: "horizontal", + value: this.millisec, + min: o.millisecMin, + max: millisecMax, + step: o.stepMillisec, + slide: function(event, ui) { + tp_inst.millisec_slider.slider( "option", "value", ui.value); + tp_inst._onTimeChange(); + } + }); + + this.timezone_select = $tp.find('#ui_tpicker_timezone_'+ dp_id).append('<select></select>').find("select"); + $.fn.append.apply(this.timezone_select, + $.map(o.timezoneList, function(val, idx) { + return $("<option />") + .val(typeof val == "object" ? val.value : val) + .text(typeof val == "object" ? val.label : val); + }) + ); + this.timezone_select.val((typeof this.timezone != "undefined" && this.timezone != null && this.timezone != "") ? this.timezone : o.timezone); + this.timezone_select.change(function() { + tp_inst._onTimeChange(); + }); + + // Add grid functionality + if (o.showHour && o.hourGrid > 0) { + size = 100 * hourGridSize * o.hourGrid / (hourMax - o.hourMin); + + $tp.find(".ui_tpicker_hour table").css({ + width: size + "%", + marginLeft: (size / (-2 * hourGridSize)) + "%", + borderCollapse: 'collapse' + }).find("td").each( function(index) { + $(this).click(function() { + var h = $(this).html(); + if(o.ampm) { + var ap = h.substring(2).toLowerCase(), + aph = parseInt(h.substring(0,2), 10); + if (ap == 'a') { + if (aph == 12) h = 0; + else h = aph; + } else if (aph == 12) h = 12; + else h = aph + 12; + } + tp_inst.hour_slider.slider("option", "value", h); + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }).css({ + cursor: 'pointer', + width: (100 / hourGridSize) + '%', + textAlign: 'center', + overflow: 'hidden' + }); + }); + } + + if (o.showMinute && o.minuteGrid > 0) { + size = 100 * minuteGridSize * o.minuteGrid / (minMax - o.minuteMin); + $tp.find(".ui_tpicker_minute table").css({ + width: size + "%", + marginLeft: (size / (-2 * minuteGridSize)) + "%", + borderCollapse: 'collapse' + }).find("td").each(function(index) { + $(this).click(function() { + tp_inst.minute_slider.slider("option", "value", $(this).html()); + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }).css({ + cursor: 'pointer', + width: (100 / minuteGridSize) + '%', + textAlign: 'center', + overflow: 'hidden' + }); + }); + } + + if (o.showSecond && o.secondGrid > 0) { + $tp.find(".ui_tpicker_second table").css({ + width: size + "%", + marginLeft: (size / (-2 * secondGridSize)) + "%", + borderCollapse: 'collapse' + }).find("td").each(function(index) { + $(this).click(function() { + tp_inst.second_slider.slider("option", "value", $(this).html()); + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }).css({ + cursor: 'pointer', + width: (100 / secondGridSize) + '%', + textAlign: 'center', + overflow: 'hidden' + }); + }); + } + + if (o.showMillisec && o.millisecGrid > 0) { + $tp.find(".ui_tpicker_millisec table").css({ + width: size + "%", + marginLeft: (size / (-2 * millisecGridSize)) + "%", + borderCollapse: 'collapse' + }).find("td").each(function(index) { + $(this).click(function() { + tp_inst.millisec_slider.slider("option", "value", $(this).html()); + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }).css({ + cursor: 'pointer', + width: (100 / millisecGridSize) + '%', + textAlign: 'center', + overflow: 'hidden' + }); + }); + } + + var $buttonPanel = $dp.find('.ui-datepicker-buttonpane'); + if ($buttonPanel.length) $buttonPanel.before($tp); + else $dp.append($tp); + + this.$timeObj = $tp.find('#ui_tpicker_time_'+ dp_id); + + if (this.inst !== null) { + var timeDefined = this.timeDefined; + this._onTimeChange(); + this.timeDefined = timeDefined; + } + + //Emulate datepicker onSelect behavior. Call on slidestop. + var onSelectDelegate = function() { + tp_inst._onSelectHandler(); + }; + this.hour_slider.bind('slidestop',onSelectDelegate); + this.minute_slider.bind('slidestop',onSelectDelegate); + this.second_slider.bind('slidestop',onSelectDelegate); + this.millisec_slider.bind('slidestop',onSelectDelegate); + + // slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/ + if (this._defaults.addSliderAccess){ + var sliderAccessArgs = this._defaults.sliderAccessArgs; + setTimeout(function(){ // fix for inline mode + if($tp.find('.ui-slider-access').length == 0){ + $tp.find('.ui-slider:visible').sliderAccess(sliderAccessArgs); + + // fix any grids since sliders are shorter + var sliderAccessWidth = $tp.find('.ui-slider-access:eq(0)').outerWidth(true); + if(sliderAccessWidth){ + $tp.find('table:visible').each(function(){ + var $g = $(this), + oldWidth = $g.outerWidth(), + oldMarginLeft = $g.css('marginLeft').toString().replace('%',''), + newWidth = oldWidth - sliderAccessWidth, + newMarginLeft = ((oldMarginLeft * newWidth)/oldWidth) + '%'; + + $g.css({ width: newWidth, marginLeft: newMarginLeft }); + }); + } + } + },0); + } + // end slideAccess integration + + } + }, + + //######################################################################## + // This function tries to limit the ability to go outside the + // min/max date range + //######################################################################## + _limitMinMaxDateTime: function(dp_inst, adjustSliders){ + var o = this._defaults, + dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay); + + if(!this._defaults.showTimepicker) return; // No time so nothing to check here + + if($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date){ + var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'), + minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0); + + if(this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null){ + this.hourMinOriginal = o.hourMin; + this.minuteMinOriginal = o.minuteMin; + this.secondMinOriginal = o.secondMin; + this.millisecMinOriginal = o.millisecMin; + } + + if(dp_inst.settings.timeOnly || minDateTimeDate.getTime() == dp_date.getTime()) { + this._defaults.hourMin = minDateTime.getHours(); + if (this.hour <= this._defaults.hourMin) { + this.hour = this._defaults.hourMin; + this._defaults.minuteMin = minDateTime.getMinutes(); + if (this.minute <= this._defaults.minuteMin) { + this.minute = this._defaults.minuteMin; + this._defaults.secondMin = minDateTime.getSeconds(); + } else if (this.second <= this._defaults.secondMin){ + this.second = this._defaults.secondMin; + this._defaults.millisecMin = minDateTime.getMilliseconds(); + } else { + if(this.millisec < this._defaults.millisecMin) + this.millisec = this._defaults.millisecMin; + this._defaults.millisecMin = this.millisecMinOriginal; + } + } else { + this._defaults.minuteMin = this.minuteMinOriginal; + this._defaults.secondMin = this.secondMinOriginal; + this._defaults.millisecMin = this.millisecMinOriginal; + } + }else{ + this._defaults.hourMin = this.hourMinOriginal; + this._defaults.minuteMin = this.minuteMinOriginal; + this._defaults.secondMin = this.secondMinOriginal; + this._defaults.millisecMin = this.millisecMinOriginal; + } + } + + if($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date){ + var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'), + maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0); + + if(this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null){ + this.hourMaxOriginal = o.hourMax; + this.minuteMaxOriginal = o.minuteMax; + this.secondMaxOriginal = o.secondMax; + this.millisecMaxOriginal = o.millisecMax; + } + + if(dp_inst.settings.timeOnly || maxDateTimeDate.getTime() == dp_date.getTime()){ + this._defaults.hourMax = maxDateTime.getHours(); + if (this.hour >= this._defaults.hourMax) { + this.hour = this._defaults.hourMax; + this._defaults.minuteMax = maxDateTime.getMinutes(); + if (this.minute >= this._defaults.minuteMax) { + this.minute = this._defaults.minuteMax; + this._defaults.secondMax = maxDateTime.getSeconds(); + } else if (this.second >= this._defaults.secondMax) { + this.second = this._defaults.secondMax; + this._defaults.millisecMax = maxDateTime.getMilliseconds(); + } else { + if(this.millisec > this._defaults.millisecMax) this.millisec = this._defaults.millisecMax; + this._defaults.millisecMax = this.millisecMaxOriginal; + } + } else { + this._defaults.minuteMax = this.minuteMaxOriginal; + this._defaults.secondMax = this.secondMaxOriginal; + this._defaults.millisecMax = this.millisecMaxOriginal; + } + }else{ + this._defaults.hourMax = this.hourMaxOriginal; + this._defaults.minuteMax = this.minuteMaxOriginal; + this._defaults.secondMax = this.secondMaxOriginal; + this._defaults.millisecMax = this.millisecMaxOriginal; + } + } + + if(adjustSliders !== undefined && adjustSliders === true){ + var hourMax = parseInt((this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)) ,10), + minMax = parseInt((this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)) ,10), + secMax = parseInt((this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)) ,10), + millisecMax = parseInt((this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)) ,10); + + if(this.hour_slider) + this.hour_slider.slider("option", { min: this._defaults.hourMin, max: hourMax }).slider('value', this.hour); + if(this.minute_slider) + this.minute_slider.slider("option", { min: this._defaults.minuteMin, max: minMax }).slider('value', this.minute); + if(this.second_slider) + this.second_slider.slider("option", { min: this._defaults.secondMin, max: secMax }).slider('value', this.second); + if(this.millisec_slider) + this.millisec_slider.slider("option", { min: this._defaults.millisecMin, max: millisecMax }).slider('value', this.millisec); + } + + }, + + + //######################################################################## + // when a slider moves, set the internal time... + // on time change is also called when the time is updated in the text field + //######################################################################## + _onTimeChange: function() { + var hour = (this.hour_slider) ? this.hour_slider.slider('value') : false, + minute = (this.minute_slider) ? this.minute_slider.slider('value') : false, + second = (this.second_slider) ? this.second_slider.slider('value') : false, + millisec = (this.millisec_slider) ? this.millisec_slider.slider('value') : false, + timezone = (this.timezone_select) ? this.timezone_select.val() : false, + o = this._defaults; + + if (typeof(hour) == 'object') hour = false; + if (typeof(minute) == 'object') minute = false; + if (typeof(second) == 'object') second = false; + if (typeof(millisec) == 'object') millisec = false; + if (typeof(timezone) == 'object') timezone = false; + + if (hour !== false) hour = parseInt(hour,10); + if (minute !== false) minute = parseInt(minute,10); + if (second !== false) second = parseInt(second,10); + if (millisec !== false) millisec = parseInt(millisec,10); + + var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0]; + + // If the update was done in the input field, the input field should not be updated. + // If the update was done using the sliders, update the input field. + var hasChanged = (hour != this.hour || minute != this.minute + || second != this.second || millisec != this.millisec + || (this.ampm.length > 0 + && (hour < 12) != ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1)) + || timezone != this.timezone); + + if (hasChanged) { + + if (hour !== false)this.hour = hour; + if (minute !== false) this.minute = minute; + if (second !== false) this.second = second; + if (millisec !== false) this.millisec = millisec; + if (timezone !== false) this.timezone = timezone; + + if (!this.inst) this.inst = $.datepicker._getInst(this.$input[0]); + + this._limitMinMaxDateTime(this.inst, true); + } + if (o.ampm) this.ampm = ampm; + + //this._formatTime(); + this.formattedTime = $.datepicker.formatTime(this._defaults.timeFormat, this, this._defaults); + if (this.$timeObj) this.$timeObj.text(this.formattedTime + o.timeSuffix); + this.timeDefined = true; + if (hasChanged) this._updateDateTime(); + }, + + //######################################################################## + // call custom onSelect. + // bind to sliders slidestop, and grid click. + //######################################################################## + _onSelectHandler: function() { + var onSelect = this._defaults.onSelect; + var inputEl = this.$input ? this.$input[0] : null; + if (onSelect && inputEl) { + onSelect.apply(inputEl, [this.formattedDateTime, this]); + } + }, + + //######################################################################## + // left for any backwards compatibility + //######################################################################## + _formatTime: function(time, format) { + time = time || { hour: this.hour, minute: this.minute, second: this.second, millisec: this.millisec, ampm: this.ampm, timezone: this.timezone }; + var tmptime = (format || this._defaults.timeFormat).toString(); + + tmptime = $.datepicker.formatTime(tmptime, time, this._defaults); + + if (arguments.length) return tmptime; + else this.formattedTime = tmptime; + }, + + //######################################################################## + // update our input with the new date time.. + //######################################################################## + _updateDateTime: function(dp_inst) { + dp_inst = this.inst || dp_inst; + var dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)), + dateFmt = $.datepicker._get(dp_inst, 'dateFormat'), + formatCfg = $.datepicker._getFormatConfig(dp_inst), + timeAvailable = dt !== null && this.timeDefined; + this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg); + var formattedDateTime = this.formattedDate; + if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0)) + return; + + if (this._defaults.timeOnly === true) { + formattedDateTime = this.formattedTime; + } else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) { + formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix; + } + + this.formattedDateTime = formattedDateTime; + + if(!this._defaults.showTimepicker) { + this.$input.val(this.formattedDate); + } else if (this.$altInput && this._defaults.altFieldTimeOnly === true) { + this.$altInput.val(this.formattedTime); + this.$input.val(this.formattedDate); + } else if(this.$altInput) { + this.$altInput.val(formattedDateTime); + this.$input.val(formattedDateTime); + } else { + this.$input.val(formattedDateTime); + } + + this.$input.trigger("change"); + } + +}); + +$.fn.extend({ + //######################################################################## + // shorthand just to use timepicker.. + //######################################################################## + timepicker: function(o) { + o = o || {}; + var tmp_args = arguments; + + if (typeof o == 'object') tmp_args[0] = $.extend(o, { timeOnly: true }); + + return $(this).each(function() { + $.fn.datetimepicker.apply($(this), tmp_args); + }); + }, + + //######################################################################## + // extend timepicker to datepicker + //######################################################################## + datetimepicker: function(o) { + o = o || {}; + tmp_args = arguments; + + if (typeof(o) == 'string'){ + if(o == 'getDate') + return $.fn.datepicker.apply($(this[0]), tmp_args); + else + return this.each(function() { + var $t = $(this); + $t.datepicker.apply($t, tmp_args); + }); + } + else + return this.each(function() { + var $t = $(this); + $t.datepicker($.timepicker._newInst($t, o)._defaults); + }); + } +}); + +//######################################################################## +// format the time all pretty... +// format = string format of the time +// time = a {}, not a Date() for timezones +// options = essentially the regional[].. amNames, pmNames, ampm +//######################################################################## +$.datepicker.formatTime = function(format, time, options) { + options = options || {}; + options = $.extend($.timepicker._defaults, options); + time = $.extend({hour:0, minute:0, second:0, millisec:0, timezone:'+0000'}, time); + + var tmptime = format; + var ampmName = options['amNames'][0]; + + var hour = parseInt(time.hour, 10); + if (options.ampm) { + if (hour > 11){ + ampmName = options['pmNames'][0]; + if(hour > 12) + hour = hour % 12; + } + if (hour === 0) + hour = 12; + } + tmptime = tmptime.replace(/(?:hh?|mm?|ss?|[tT]{1,2}|[lz])/g, function(match) { + switch (match.toLowerCase()) { + case 'hh': return ('0' + hour).slice(-2); + case 'h': return hour; + case 'mm': return ('0' + time.minute).slice(-2); + case 'm': return time.minute; + case 'ss': return ('0' + time.second).slice(-2); + case 's': return time.second; + case 'l': return ('00' + time.millisec).slice(-3); + case 'z': return time.timezone; + case 't': case 'tt': + if (options.ampm) { + if (match.length == 1) + ampmName = ampmName.charAt(0); + return match.charAt(0) == 'T' ? ampmName.toUpperCase() : ampmName.toLowerCase(); + } + return ''; + } + }); + + tmptime = $.trim(tmptime); + return tmptime; +}; + +//######################################################################## +// the bad hack :/ override datepicker so it doesnt close on select +// inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378 +//######################################################################## +$.datepicker._base_selectDate = $.datepicker._selectDate; +$.datepicker._selectDate = function (id, dateStr) { + var inst = this._getInst($(id)[0]), + tp_inst = this._get(inst, 'timepicker'); + + if (tp_inst) { + tp_inst._limitMinMaxDateTime(inst, true); + inst.inline = inst.stay_open = true; + //This way the onSelect handler called from calendarpicker get the full dateTime + this._base_selectDate(id, dateStr); + inst.inline = inst.stay_open = false; + this._notifyChange(inst); + this._updateDatepicker(inst); + } + else this._base_selectDate(id, dateStr); +}; + +//############################################################################################# +// second bad hack :/ override datepicker so it triggers an event when changing the input field +// and does not redraw the datepicker on every selectDate event +//############################################################################################# +$.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker; +$.datepicker._updateDatepicker = function(inst) { + + // don't popup the datepicker if there is another instance already opened + var input = inst.input[0]; + if($.datepicker._curInst && + $.datepicker._curInst != inst && + $.datepicker._datepickerShowing && + $.datepicker._lastInput != input) { + return; + } + + if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) { + + this._base_updateDatepicker(inst); + + // Reload the time control when changing something in the input text field. + var tp_inst = this._get(inst, 'timepicker'); + if(tp_inst) tp_inst._addTimePicker(inst); + } +}; + +//####################################################################################### +// third bad hack :/ override datepicker so it allows spaces and colon in the input field +//####################################################################################### +$.datepicker._base_doKeyPress = $.datepicker._doKeyPress; +$.datepicker._doKeyPress = function(event) { + var inst = $.datepicker._getInst(event.target), + tp_inst = $.datepicker._get(inst, 'timepicker'); + + if (tp_inst) { + if ($.datepicker._get(inst, 'constrainInput')) { + var ampm = tp_inst._defaults.ampm, + dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')), + datetimeChars = tp_inst._defaults.timeFormat.toString() + .replace(/[hms]/g, '') + .replace(/TT/g, ampm ? 'APM' : '') + .replace(/Tt/g, ampm ? 'AaPpMm' : '') + .replace(/tT/g, ampm ? 'AaPpMm' : '') + .replace(/T/g, ampm ? 'AP' : '') + .replace(/tt/g, ampm ? 'apm' : '') + .replace(/t/g, ampm ? 'ap' : '') + + " " + + tp_inst._defaults.separator + + tp_inst._defaults.timeSuffix + + (tp_inst._defaults.showTimezone ? tp_inst._defaults.timezoneList.join('') : '') + + (tp_inst._defaults.amNames.join('')) + + (tp_inst._defaults.pmNames.join('')) + + dateChars, + chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode); + return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1); + } + } + + return $.datepicker._base_doKeyPress(event); +}; + +//####################################################################################### +// Override key up event to sync manual input changes. +//####################################################################################### +$.datepicker._base_doKeyUp = $.datepicker._doKeyUp; +$.datepicker._doKeyUp = function (event) { + var inst = $.datepicker._getInst(event.target), + tp_inst = $.datepicker._get(inst, 'timepicker'); + + if (tp_inst) { + if (tp_inst._defaults.timeOnly && (inst.input.val() != inst.lastVal)) { + try { + $.datepicker._updateDatepicker(inst); + } + catch (err) { + $.datepicker.log(err); + } + } + } + + return $.datepicker._base_doKeyUp(event); +}; + +//####################################################################################### +// override "Today" button to also grab the time. +//####################################################################################### +$.datepicker._base_gotoToday = $.datepicker._gotoToday; +$.datepicker._gotoToday = function(id) { + var inst = this._getInst($(id)[0]), + $dp = inst.dpDiv; + this._base_gotoToday(id); + var now = new Date(); + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst && tp_inst._defaults.showTimezone && tp_inst.timezone_select) { + var tzoffset = now.getTimezoneOffset(); // If +0100, returns -60 + var tzsign = tzoffset > 0 ? '-' : '+'; + tzoffset = Math.abs(tzoffset); + var tzmin = tzoffset % 60; + tzoffset = tzsign + ('0' + (tzoffset - tzmin) / 60).slice(-2) + ('0' + tzmin).slice(-2); + if (tp_inst._defaults.timezoneIso8609) + tzoffset = tzoffset.substring(0, 3) + ':' + tzoffset.substring(3); + tp_inst.timezone_select.val(tzoffset); + } + this._setTime(inst, now); + $( '.ui-datepicker-today', $dp).click(); +}; + +//####################################################################################### +// Disable & enable the Time in the datetimepicker +//####################################################################################### +$.datepicker._disableTimepickerDatepicker = function(target, date, withDate) { + var inst = this._getInst(target), + tp_inst = this._get(inst, 'timepicker'); + $(target).datepicker('getDate'); // Init selected[Year|Month|Day] + if (tp_inst) { + tp_inst._defaults.showTimepicker = false; + tp_inst._updateDateTime(inst); + } +}; + +$.datepicker._enableTimepickerDatepicker = function(target, date, withDate) { + var inst = this._getInst(target), + tp_inst = this._get(inst, 'timepicker'); + $(target).datepicker('getDate'); // Init selected[Year|Month|Day] + if (tp_inst) { + tp_inst._defaults.showTimepicker = true; + tp_inst._addTimePicker(inst); // Could be disabled on page load + tp_inst._updateDateTime(inst); + } +}; + +//####################################################################################### +// Create our own set time function +//####################################################################################### +$.datepicker._setTime = function(inst, date) { + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + var defaults = tp_inst._defaults, + // calling _setTime with no date sets time to defaults + hour = date ? date.getHours() : defaults.hour, + minute = date ? date.getMinutes() : defaults.minute, + second = date ? date.getSeconds() : defaults.second, + millisec = date ? date.getMilliseconds() : defaults.millisec; + + //check if within min/max times.. + if ((hour < defaults.hourMin || hour > defaults.hourMax) || (minute < defaults.minuteMin || minute > defaults.minuteMax) || (second < defaults.secondMin || second > defaults.secondMax) || (millisec < defaults.millisecMin || millisec > defaults.millisecMax)) { + hour = defaults.hourMin; + minute = defaults.minuteMin; + second = defaults.secondMin; + millisec = defaults.millisecMin; + } + + tp_inst.hour = hour; + tp_inst.minute = minute; + tp_inst.second = second; + tp_inst.millisec = millisec; + + if (tp_inst.hour_slider) tp_inst.hour_slider.slider('value', hour); + if (tp_inst.minute_slider) tp_inst.minute_slider.slider('value', minute); + if (tp_inst.second_slider) tp_inst.second_slider.slider('value', second); + if (tp_inst.millisec_slider) tp_inst.millisec_slider.slider('value', millisec); + + tp_inst._onTimeChange(); + tp_inst._updateDateTime(inst); + } +}; + +//####################################################################################### +// Create new public method to set only time, callable as $().datepicker('setTime', date) +//####################################################################################### +$.datepicker._setTimeDatepicker = function(target, date, withDate) { + var inst = this._getInst(target), + tp_inst = this._get(inst, 'timepicker'); + + if (tp_inst) { + this._setDateFromField(inst); + var tp_date; + if (date) { + if (typeof date == "string") { + tp_inst._parseTime(date, withDate); + tp_date = new Date(); + tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); + } + else tp_date = new Date(date.getTime()); + if (tp_date.toString() == 'Invalid Date') tp_date = undefined; + this._setTime(inst, tp_date); + } + } + +}; + +//####################################################################################### +// override setDate() to allow setting time too within Date object +//####################################################################################### +$.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker; +$.datepicker._setDateDatepicker = function(target, date) { + var inst = this._getInst(target), + tp_date = (date instanceof Date) ? new Date(date.getTime()) : date; + + this._updateDatepicker(inst); + this._base_setDateDatepicker.apply(this, arguments); + this._setTimeDatepicker(target, tp_date, true); +}; + +//####################################################################################### +// override getDate() to allow getting time too within Date object +//####################################################################################### +$.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker; +$.datepicker._getDateDatepicker = function(target, noDefault) { + var inst = this._getInst(target), + tp_inst = this._get(inst, 'timepicker'); + + if (tp_inst) { + this._setDateFromField(inst, noDefault); + var date = this._getDate(inst); + if (date && tp_inst._parseTime($(target).val(), tp_inst.timeOnly)) date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); + return date; + } + return this._base_getDateDatepicker(target, noDefault); +}; + +//####################################################################################### +// override parseDate() because UI 1.8.14 throws an error about "Extra characters" +// An option in datapicker to ignore extra format characters would be nicer. +//####################################################################################### +$.datepicker._base_parseDate = $.datepicker.parseDate; +$.datepicker.parseDate = function(format, value, settings) { + var date; + try { + date = this._base_parseDate(format, value, settings); + } catch (err) { + if (err.indexOf(":") >= 0) { + // Hack! The error message ends with a colon, a space, and + // the "extra" characters. We rely on that instead of + // attempting to perfectly reproduce the parsing algorithm. + date = this._base_parseDate(format, value.substring(0,value.length-(err.length-err.indexOf(':')-2)), settings); + } else { + // The underlying error was not related to the time + throw err; + } + } + return date; +}; + +//####################################################################################### +// override formatDate to set date with time to the input +//####################################################################################### +$.datepicker._base_formatDate = $.datepicker._formatDate; +$.datepicker._formatDate = function(inst, day, month, year){ + var tp_inst = this._get(inst, 'timepicker'); + if(tp_inst) { + tp_inst._updateDateTime(inst); + return tp_inst.$input.val(); + } + return this._base_formatDate(inst); +}; + +//####################################################################################### +// override options setter to add time to maxDate(Time) and minDate(Time). MaxDate +//####################################################################################### +$.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker; +$.datepicker._optionDatepicker = function(target, name, value) { + var inst = this._getInst(target), + tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + var min = null, max = null, onselect = null; + if (typeof name == 'string') { // if min/max was set with the string + if (name === 'minDate' || name === 'minDateTime' ) + min = value; + else if (name === 'maxDate' || name === 'maxDateTime') + max = value; + else if (name === 'onSelect') + onselect = value; + } else if (typeof name == 'object') { //if min/max was set with the JSON + if (name.minDate) + min = name.minDate; + else if (name.minDateTime) + min = name.minDateTime; + else if (name.maxDate) + max = name.maxDate; + else if (name.maxDateTime) + max = name.maxDateTime; + } + if(min) { //if min was set + if (min == 0) + min = new Date(); + else + min = new Date(min); + + tp_inst._defaults.minDate = min; + tp_inst._defaults.minDateTime = min; + } else if (max) { //if max was set + if(max==0) + max=new Date(); + else + max= new Date(max); + tp_inst._defaults.maxDate = max; + tp_inst._defaults.maxDateTime = max; + } else if (onselect) + tp_inst._defaults.onSelect = onselect; + } + if (value === undefined) + return this._base_optionDatepicker(target, name); + return this._base_optionDatepicker(target, name, value); +}; + +//####################################################################################### +// jQuery extend now ignores nulls! +//####################################################################################### +function extendRemove(target, props) { + $.extend(target, props); + for (var name in props) + if (props[name] === null || props[name] === undefined) + target[name] = props[name]; + return target; +}; + +$.timepicker = new Timepicker(); // singleton instance +$.timepicker.version = "1.0.0"; + +})(jQuery); diff --git a/rt/share/html/NoAuth/js/ui.timepickr.js b/rt/share/html/NoAuth/js/ui.timepickr.js deleted file mode 100644 index 3b2040a21..000000000 --- a/rt/share/html/NoAuth/js/ui.timepickr.js +++ /dev/null @@ -1,941 +0,0 @@ -/* - jQuery utils - @VERSION - http://code.google.com/p/jquery-utils/ - - (c) Maxime Haineault <haineault@gmail.com> - http://haineault.com - - MIT License (http://www.opensource.org/licenses/mit-license.php - -*/ - -(function($){ - $.extend($.expr[':'], { - // case insensitive version of :contains - icontains: function(a,i,m){return (a.textContent||a.innerText||jQuery(a).text()||"").toLowerCase().indexOf(m[3].toLowerCase())>=0;} - }); - - $.iterators = { - getText: function() { return $(this).text(); }, - parseInt: function(v){ return parseInt(v, 10); } - }; - - $.extend({ - - // Returns a range object - // Author: Matthias Miller - // Site: http://blog.outofhanwell.com/2006/03/29/javascript-range-function/ - range: function() { - if (!arguments.length) { return []; } - var min, max, step; - if (arguments.length == 1) { - min = 0; - max = arguments[0]-1; - step = 1; - } - else { - // default step to 1 if it's zero or undefined - min = arguments[0]; - max = arguments[1]-1; - step = arguments[2] || 1; - } - // convert negative steps to positive and reverse min/max - if (step < 0 && min >= max) { - step *= -1; - var tmp = min; - min = max; - max = tmp; - min += ((max-min) % step); - } - var a = []; - for (var i = min; i <= max; i += step) { a.push(i); } - return a; - }, - - // Taken from ui.core.js. - // Why are you keeping this gem for yourself guys ? :| - keyCode: { - BACKSPACE: 8, CAPS_LOCK: 20, COMMA: 188, CONTROL: 17, DELETE: 46, DOWN: 40, - END: 35, ENTER: 13, ESCAPE: 27, HOME: 36, INSERT: 45, LEFT: 37, - NUMPAD_ADD: 107, NUMPAD_DECIMAL: 110, NUMPAD_DIVIDE: 111, NUMPAD_ENTER: 108, - NUMPAD_MULTIPLY: 106, NUMPAD_SUBTRACT: 109, PAGE_DOWN: 34, PAGE_UP: 33, - PERIOD: 190, RIGHT: 39, SHIFT: 16, SPACE: 32, TAB: 9, UP: 38 - }, - - // Takes a keyboard event and return true if the keycode match the specified keycode - keyIs: function(k, e) { - return parseInt($.keyCode[k.toUpperCase()], 10) == parseInt((typeof(e) == 'number' )? e: e.keyCode, 10); - }, - - // Returns the key of an array - keys: function(arr) { - var o = []; - for (k in arr) { o.push(k); } - return o; - }, - - // Redirect to a specified url - redirect: function(url) { - window.location.href = url; - return url; - }, - - // Stop event shorthand - stop: function(e, preventDefault, stopPropagation) { - if (preventDefault) { e.preventDefault(); } - if (stopPropagation) { e.stopPropagation(); } - return preventDefault && false || true; - }, - - // Returns the basename of a path - basename: function(path) { - var t = path.split('/'); - return t[t.length] === '' && s || t.slice(0, t.length).join('/'); - }, - - // Returns the filename of a path - filename: function(path) { - return path.split('/').pop(); - }, - - // Returns a formated file size - filesizeformat: function(bytes, suffixes){ - var b = parseInt(bytes, 10); - var s = suffixes || ['byte', 'bytes', 'KB', 'MB', 'GB']; - if (isNaN(b) || b === 0) { return '0 ' + s[0]; } - if (b == 1) { return '1 ' + s[0]; } - if (b < 1024) { return b.toFixed(2) + ' ' + s[1]; } - if (b < 1048576) { return (b / 1024).toFixed(2) + ' ' + s[2]; } - if (b < 1073741824) { return (b / 1048576).toFixed(2) + ' '+ s[3]; } - else { return (b / 1073741824).toFixed(2) + ' '+ s[4]; } - }, - - fileExtension: function(s) { - var tokens = s.split('.'); - return tokens[tokens.length-1] || false; - }, - - // Returns true if an object is a String - isString: function(o) { - return typeof(o) == 'string' && true || false; - }, - - // Returns true if an object is a RegExp - isRegExp: function(o) { - return o && o.constructor.toString().indexOf('RegExp()') != -1 || false; - }, - - isObject: function(o) { - return (typeof(o) == 'object'); - }, - - // Convert input to currency (two decimal fixed number) - toCurrency: function(i) { - i = parseFloat(i, 10).toFixed(2); - return (i=='NaN') ? '0.00' : i; - }, - - /*-------------------------------------------------------------------- - * javascript method: "pxToEm" - * by: - Scott Jehl (scott@filamentgroup.com) - Maggie Wachs (maggie@filamentgroup.com) - http://www.filamentgroup.com - * - * Copyright (c) 2008 Filament Group - * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses. - * - * Description: pxToEm converts a pixel value to ems depending on inherited font size. - * Article: http://www.filamentgroup.com/lab/retaining_scalable_interfaces_with_pixel_to_em_conversion/ - * Demo: http://www.filamentgroup.com/examples/pxToEm/ - * - * Options: - scope: string or jQuery selector for font-size scoping - reverse: Boolean, true reverses the conversion to em-px - * Dependencies: jQuery library - * Usage Example: myPixelValue.pxToEm(); or myPixelValue.pxToEm({'scope':'#navigation', reverse: true}); - * - * Version: 2.1, 18.12.2008 - * Changelog: - * 08.02.2007 initial Version 1.0 - * 08.01.2008 - fixed font-size calculation for IE - * 18.12.2008 - removed native object prototyping to stay in jQuery's spirit, jsLinted (Maxime Haineault <haineault@gmail.com>) - --------------------------------------------------------------------*/ - - pxToEm: function(i, settings){ - //set defaults - settings = jQuery.extend({ - scope: 'body', - reverse: false - }, settings); - - var pxVal = (i === '') ? 0 : parseFloat(i); - var scopeVal; - var getWindowWidth = function(){ - var de = document.documentElement; - return self.innerWidth || (de && de.clientWidth) || document.body.clientWidth; - }; - - /* When a percentage-based font-size is set on the body, IE returns that percent of the window width as the font-size. - For example, if the body font-size is 62.5% and the window width is 1000px, IE will return 625px as the font-size. - When this happens, we calculate the correct body font-size (%) and multiply it by 16 (the standard browser font size) - to get an accurate em value. */ - - if (settings.scope == 'body' && $.browser.msie && (parseFloat($('body').css('font-size')) / getWindowWidth()).toFixed(1) > 0.0) { - var calcFontSize = function(){ - return (parseFloat($('body').css('font-size'))/getWindowWidth()).toFixed(3) * 16; - }; - scopeVal = calcFontSize(); - } - else { scopeVal = parseFloat(jQuery(settings.scope).css("font-size")); } - - var result = (settings.reverse === true) ? (pxVal * scopeVal).toFixed(2) + 'px' : (pxVal / scopeVal).toFixed(2) + 'em'; - return result; - } - }); - - $.extend($.fn, { - type: function() { - try { return $(this).get(0).nodeName.toLowerCase(); } - catch(e) { return false; } - }, - // Select a text range in a textarea - selectRange: function(start, end){ - // use only the first one since only one input can be focused - if ($(this).get(0).createTextRange) { - var range = $(this).get(0).createTextRange(); - range.collapse(true); - range.moveEnd('character', end); - range.moveStart('character', start); - range.select(); - } - else if ($(this).get(0).setSelectionRange) { - $(this).bind('focus', function(e){ - e.preventDefault(); - }).get(0).setSelectionRange(start, end); - } - return $(this); - }, - - /*-------------------------------------------------------------------- - * JQuery Plugin: "EqualHeights" - * by: Scott Jehl, Todd Parker, Maggie Costello Wachs (http://www.filamentgroup.com) - * - * Copyright (c) 2008 Filament Group - * Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php) - * - * Description: Compares the heights or widths of the top-level children of a provided element - and sets their min-height to the tallest height (or width to widest width). Sets in em units - by default if pxToEm() method is available. - * Dependencies: jQuery library, pxToEm method (article: - http://www.filamentgroup.com/lab/retaining_scalable_interfaces_with_pixel_to_em_conversion/) - * Usage Example: $(element).equalHeights(); - Optional: to set min-height in px, pass a true argument: $(element).equalHeights(true); - * Version: 2.1, 18.12.2008 - * - * Note: Changed pxToEm call to call $.pxToEm instead, jsLinted (Maxime Haineault <haineault@gmail.com>) - --------------------------------------------------------------------*/ - - equalHeights: function(px){ - $(this).each(function(){ - var currentTallest = 0; - $(this).children().each(function(i){ - if ($(this).height() > currentTallest) { currentTallest = $(this).height(); } - }); - if (!px || !$.pxToEm) { currentTallest = $.pxToEm(currentTallest); } //use ems unless px is specified - // for ie6, set height since min-height isn't supported - if ($.browser.msie && $.browser.version == 6.0) { $(this).children().css({'height': currentTallest}); } - $(this).children().css({'min-height': currentTallest}); - }); - return this; - }, - - // Copyright (c) 2009 James Padolsey - // http://james.padolsey.com/javascript/jquery-delay-plugin/ - delay: function(time, callback){ - jQuery.fx.step.delay = function(){}; - return this.animate({delay:1}, time, callback); - } - }); -})(jQuery); - -/* - jQuery strings - 0.4 - http://code.google.com/p/jquery-utils/ - - (c) Maxime Haineault <haineault@gmail.com> - http://haineault.com - - MIT License (http://www.opensource.org/licenses/mit-license.php) - - Implementation of Python3K advanced string formatting - http://www.python.org/dev/peps/pep-3101/ - - Documentation: http://code.google.com/p/jquery-utils/wiki/StringFormat - -*/ -(function($){ - var strings = { - strConversion: { - // tries to translate any objects type into string gracefully - __repr: function(i){ - switch(this.__getType(i)) { - case 'array':case 'date':case 'number': - return i.toString(); - case 'object': // Thanks to Richard Paul Lewis for the fix - var o = []; - var l = i.length; - for(var x=0;x<l;x++) { - o.push(x+': '+this.__repr(i[x])); - } - return o.join(', '); - case 'string': - return i; - default: - return i; - } - }, - // like typeof but less vague - __getType: function(i) { - if (!i || !i.constructor) { return typeof(i); } - var match = i.constructor.toString().match(/Array|Number|String|Object|Date/); - return match && match[0].toLowerCase() || typeof(i); - }, - // Jonas Raoni Soares Silva (http://jsfromhell.com/string/pad) - __pad: function(str, l, s, t){ - var p = s || ' '; - var o = str; - if (l - str.length > 0) { - o = new Array(Math.ceil(l / p.length)).join(p).substr(0, t = !t ? l : t == 1 ? 0 : Math.ceil(l / 2)) + str + p.substr(0, l - t); - } - return o; - }, - __getInput: function(arg, args) { - var key = arg.getKey(); - switch(this.__getType(args)){ - case 'object': // Thanks to Jonathan Works for the patch - var keys = key.split('.'); - var obj = args; - for(var subkey = 0; subkey < keys.length; subkey++){ - obj = obj[keys[subkey]]; - } - if (typeof(obj) != 'undefined') { - if (strings.strConversion.__getType(obj) == 'array') { - return arg.getFormat().match(/\.\*/) && obj[1] || obj; - } - return obj; - } - else { - // TODO: try by numerical index - } - break; - case 'array': - key = parseInt(key, 10); - if (arg.getFormat().match(/\.\*/) && typeof args[key+1] != 'undefined') { return args[key+1]; } - else if (typeof args[key] != 'undefined') { return args[key]; } - else { return key; } - break; - } - return '{'+key+'}'; - }, - __formatToken: function(token, args) { - var arg = new Argument(token, args); - return strings.strConversion[arg.getFormat().slice(-1)](this.__getInput(arg, args), arg); - }, - - // Signed integer decimal. - d: function(input, arg){ - var o = parseInt(input, 10); // enforce base 10 - var p = arg.getPaddingLength(); - if (p) { return this.__pad(o.toString(), p, arg.getPaddingString(), 0); } - else { return o; } - }, - // Signed integer decimal. - i: function(input, args){ - return this.d(input, args); - }, - // Unsigned octal - o: function(input, arg){ - var o = input.toString(8); - if (arg.isAlternate()) { o = this.__pad(o, o.length+1, '0', 0); } - return this.__pad(o, arg.getPaddingLength(), arg.getPaddingString(), 0); - }, - // Unsigned decimal - u: function(input, args) { - return Math.abs(this.d(input, args)); - }, - // Unsigned hexadecimal (lowercase) - x: function(input, arg){ - var o = parseInt(input, 10).toString(16); - o = this.__pad(o, arg.getPaddingLength(), arg.getPaddingString(),0); - return arg.isAlternate() ? '0x'+o : o; - }, - // Unsigned hexadecimal (uppercase) - X: function(input, arg){ - return this.x(input, arg).toUpperCase(); - }, - // Floating point exponential format (lowercase) - e: function(input, arg){ - return parseFloat(input, 10).toExponential(arg.getPrecision()); - }, - // Floating point exponential format (uppercase) - E: function(input, arg){ - return this.e(input, arg).toUpperCase(); - }, - // Floating point decimal format - f: function(input, arg){ - return this.__pad(parseFloat(input, 10).toFixed(arg.getPrecision()), arg.getPaddingLength(), arg.getPaddingString(),0); - }, - // Floating point decimal format (alias) - F: function(input, args){ - return this.f(input, args); - }, - // Floating point format. Uses exponential format if exponent is greater than -4 or less than precision, decimal format otherwise - g: function(input, arg){ - var o = parseFloat(input, 10); - return (o.toString().length > 6) ? Math.round(o.toExponential(arg.getPrecision())): o; - }, - // Floating point format. Uses exponential format if exponent is greater than -4 or less than precision, decimal format otherwise - G: function(input, args){ - return this.g(input, args); - }, - // Single character (accepts integer or single character string). - c: function(input, args) { - var match = input.match(/\w|\d/); - return match && match[0] || ''; - }, - // String (converts any JavaScript object to anotated format) - r: function(input, args) { - return this.__repr(input); - }, - // String (converts any JavaScript object using object.toString()) - s: function(input, args) { - return input.toString && input.toString() || ''+input; - } - }, - - format: function(str, args) { - var end = 0; - var start = 0; - var match = false; - var buffer = []; - var token = ''; - var tmp = (str||'').split(''); - for(start=0; start < tmp.length; start++) { - if (tmp[start] == '{' && tmp[start+1] !='{') { - end = str.indexOf('}', start); - token = tmp.slice(start+1, end).join(''); - if (tmp[start-1] != '{' && tmp[end+1] != '}') { - var tokenArgs = (typeof arguments[1] != 'object')? arguments2Array(arguments, 2): args || []; - buffer.push(strings.strConversion.__formatToken(token, tokenArgs)); - } - else { - buffer.push(token); - } - } - else if (start > end || buffer.length < 1) { buffer.push(tmp[start]); } - } - return (buffer.length > 1)? buffer.join(''): buffer[0]; - }, - - calc: function(str, args) { - return eval(format(str, args)); - }, - - repeat: function(s, n) { - return new Array(n+1).join(s); - }, - - UTF8encode: function(s) { - return unescape(encodeURIComponent(s)); - }, - - UTF8decode: function(s) { - return decodeURIComponent(escape(s)); - }, - - tpl: function() { - var out = ''; - var render = true; - // Set - // $.tpl('ui.test', ['<span>', helloWorld ,'</span>']); - if (arguments.length == 2 && $.isArray(arguments[1])) { - this[arguments[0]] = arguments[1].join(''); - return $(this[arguments[0]]); - } - // $.tpl('ui.test', '<span>hello world</span>'); - if (arguments.length == 2 && $.isString(arguments[1])) { - this[arguments[0]] = arguments[1]; - return $(this[arguments[0]]); - } - // Call - // $.tpl('ui.test'); - if (arguments.length == 1) { - return $(this[arguments[0]]); - } - // $.tpl('ui.test', false); - if (arguments.length == 2 && arguments[1] == false) { - return this[arguments[0]]; - } - // $.tpl('ui.test', {value:blah}); - if (arguments.length == 2 && $.isObject(arguments[1])) { - return $($.format(this[arguments[0]], arguments[1])); - } - // $.tpl('ui.test', {value:blah}, false); - if (arguments.length == 3 && $.isObject(arguments[1])) { - return (arguments[2] == true) - ? $.format(this[arguments[0]], arguments[1]) - : $($.format(this[arguments[0]], arguments[1])); - } - } - }; - - var Argument = function(arg, args) { - this.__arg = arg; - this.__args = args; - this.__max_precision = parseFloat('1.'+ (new Array(32)).join('1'), 10).toString().length-3; - this.__def_precision = 6; - this.getString = function(){ - return this.__arg; - }; - this.getKey = function(){ - return this.__arg.split(':')[0]; - }; - this.getFormat = function(){ - var match = this.getString().split(':'); - return (match && match[1])? match[1]: 's'; - }; - this.getPrecision = function(){ - var match = this.getFormat().match(/\.(\d+|\*)/g); - if (!match) { return this.__def_precision; } - else { - match = match[0].slice(1); - if (match != '*') { return parseInt(match, 10); } - else if(strings.strConversion.__getType(this.__args) == 'array') { - return this.__args[1] && this.__args[0] || this.__def_precision; - } - else if(strings.strConversion.__getType(this.__args) == 'object') { - return this.__args[this.getKey()] && this.__args[this.getKey()][0] || this.__def_precision; - } - else { return this.__def_precision; } - } - }; - this.getPaddingLength = function(){ - var match = false; - if (this.isAlternate()) { - match = this.getString().match(/0?#0?(\d+)/); - if (match && match[1]) { return parseInt(match[1], 10); } - } - match = this.getString().match(/(0|\.)(\d+|\*)/g); - return match && parseInt(match[0].slice(1), 10) || 0; - }; - this.getPaddingString = function(){ - var o = ''; - if (this.isAlternate()) { o = ' '; } - // 0 take precedence on alternate format - if (this.getFormat().match(/#0|0#|^0|\.\d+/)) { o = '0'; } - return o; - }; - this.getFlags = function(){ - var match = this.getString().matc(/^(0|\#|\-|\+|\s)+/); - return match && match[0].split('') || []; - }; - this.isAlternate = function() { - return !!this.getFormat().match(/^0?#/); - }; - }; - - var arguments2Array = function(args, shift) { - var o = []; - for (l=args.length, x=(shift || 0)-1; x<l;x++) { o.push(args[x]); } - return o; - }; - $.extend(strings); -})(jQuery); - -/* - jQuery ui.timepickr - @VERSION - http://code.google.com/p/jquery-utils/ - - (c) Maxime Haineault <haineault@gmail.com> - http://haineault.com - - MIT License (http://www.opensource.org/licenses/mit-license.php - - Note: if you want the original experimental plugin checkout the rev 224 - - Dependencies - ------------ - - jquery.utils.js - - jquery.strings.js - - jquery.ui.js - -*/ - -(function($) { - -$.tpl('timepickr.menu', '<div class="ui-helper-reset ui-timepickr ui-widget" />'); -$.tpl('timepickr.row', '<ol class="ui-timepickr-row ui-helper-clearfix" />'); -$.tpl('timepickr.button', '<li class="{className:s}"><span class="ui-state-default">{label:s}</span></li>'); - -$.widget('ui.timepickr', { - plugins: {}, - _create: function() { - this._dom = { - menu: $.tpl('timepickr.menu'), - row: $.tpl('timepickr.menu') - }; - this._trigger('initialize'); - this._trigger('initialized'); - }, - - _trigger: function(type, e, ui) { - var ui = ui || this; - $.ui.plugin.call(this, type, [e, ui]); - return $.Widget.prototype._trigger.call(this, type, e, ui); - }, - - _createButton: function(i, format, className) { - var o = format && $.format(format, i) || i; - var cn = className && 'ui-timepickr-button '+ className || 'ui-timepickr-button'; - return $.tpl('timepickr.button', {className: cn, label: o}).data('id', i) - .bind('mouseover', function(){ - $(this).siblings().find('span') - .removeClass('ui-state-hover').end().end() - .find('span').addClass('ui-state-hover'); - }); - - }, - - _addRow: function(range, format, className, insertAfter) { - var ui = this; - var btn = false; - var row = $.tpl('timepickr.row').bind('mouseover', function(){ - $(this).next().show(); - }); - $.each(range, function(idx, val){ - ui._createButton(val, format || false).appendTo(row); - }); - if (className) { - $(row).addClass(className); - } - if (this.options.corners) { - row.find('span').addClass('ui-corner-'+ this.options.corners); - } - if (insertAfter) { - row.insertAfter(insertAfter); - } - else { - ui._dom.menu.append(row); - } - return row; - }, - - _setVal: function(val) { - val = val || this._getVal(); - if (!(val.h==='' && val.m==='')) { - this.element.data('timepickr.initialValue', val); - this.element.val(this._formatVal(val)); - } - if(this._dom.menu.is(':hidden')) { - this.element.trigger('change'); - } - }, - - _getVal: function() { - var ols = this._dom.menu.find('ol'); - function get(unit) { - var u = ols.filter('.'+unit).find('.ui-state-hover:first').text(); - return u || ols.filter('.'+unit+'li:first span').text(); - } - return { - h: get('hours'), - m: get('minutes'), - s: get('seconds'), - a: get('prefix'), - z: get('suffix'), - f: this.options['format'+ this.c], - c: this.c - }; - }, - - _formatVal: function(ival) { - var val = ival || this._getVal(); - val.c = this.options.convention; - val.f = val.c === 12 && this.options.format12 || this.options.format24; - return (new Time(val)).getTime(); - }, - - blur: function() { - return this.element.blur(); - }, - - focus: function() { - return this.element.focus(); - }, - show: function() { - this._trigger('show'); - this.element.trigger(this.options.trigger); - }, - hide: function() { - this._trigger('hide'); - this._dom.menu.hide(); - } - -}); - -// These properties are shared accross every instances of timepickr -$.extend($.ui.timepickr.prototype, { - version: '@VERSION', - //eventPrefix: '', - //getter: '', - options: { - convention: 24, // 24, 12 - trigger: 'mouseover', - format12: '{h:02.d}:{m:02.d} {z:s}', - format24: '{h:02.d}:{m:02.d}', - hours: true, - prefix: ['am', 'pm'], - suffix: ['am', 'pm'], - prefixVal: false, - suffixVal: true, - rangeHour12: $.range(1, 13), - rangeHour24: [$.range(0, 12), $.range(12, 24)], - rangeMin: $.range(0, 60, 15), - rangeSec: $.range(0, 60, 15), - corners: 'all', - // plugins - core: true, - minutes: true, - seconds: false, - val: false, - updateLive: true, - resetOnBlur: true, - keyboardnav: true, - handle: false, - handleEvent: 'click' - } -}); - -$.ui.plugin.add('timepickr', 'core', { - initialized: function(e, ui) { - var menu = ui._dom.menu; - var pos = ui.element.position(); - - menu.insertAfter(ui.element).css('left', pos.left); - - if (!$.boxModel) { // IE alignement fix - menu.css('margin-top', ui.element.height() + 8); - } - - ui.element - .bind(ui.options.trigger, function() { - ui._dom.menu.show(); - ui._dom.menu.find('ol:first').show(); - ui._trigger('focus'); - if (ui.options.trigger != 'focus') { - ui.element.focus(); - } - ui._trigger('focus'); - }) - .bind('blur', function() { - ui.hide(); - ui._trigger('blur'); - }); - - menu.find('li').bind('mouseover.timepickr', function() { - ui._trigger('refresh'); - }); - }, - refresh: function(e, ui) { - // Realign each menu layers - ui._dom.menu.find('ol').each(function(){ - var p = $(this).prev('ol'); - try { // .. to not fuckup IE - $(this).css('left', p.position().left + p.find('.ui-state-hover').position().left); - } catch(e) {}; - }); - } -}); - -$.ui.plugin.add('timepickr', 'hours', { - initialize: function(e, ui) { - if (ui.options.convention === 24) { - // prefix is required in 24h mode - ui._dom.prefix = ui._addRow(ui.options.prefix, false, 'prefix'); - - // split-range - if ($.isArray(ui.options.rangeHour24[0])) { - var range = []; - $.merge(range, ui.options.rangeHour24[0]); - $.merge(range, ui.options.rangeHour24[1]); - ui._dom.hours = ui._addRow(range, '{0:0.2d}', 'hours'); - ui._dom.hours.find('li').slice(ui.options.rangeHour24[0].length, -1).hide(); - var lis = ui._dom.hours.find('li'); - - var show = [ - function() { - lis.slice(ui.options.rangeHour24[0].length).hide().end() - .slice(0, ui.options.rangeHour24[0].length).show() - .filter(':visible:first').trigger('mouseover'); - - }, - function() { - lis.slice(0, ui.options.rangeHour24[0].length).hide().end() - .slice(ui.options.rangeHour24[0].length).show() - .filter(':visible:first').trigger('mouseover'); - } - ]; - - ui._dom.prefix.find('li').bind('mouseover.timepickr', function(){ - var index = ui._dom.menu.find('.prefix li').index(this); - show[index].call(); - }); - } - else { - ui._dom.hours = ui._addRow(ui.options.rangeHour24, '{0:0.2d}', 'hours'); - ui._dom.hours.find('li').slice(12, -1).hide(); - } - } - else { - ui._dom.hours = ui._addRow(ui.options.rangeHour12, '{0:0.2d}', 'hours'); - // suffix is required in 12h mode - ui._dom.suffix = ui._addRow(ui.options.suffix, false, 'suffix'); - } - }}); - -$.ui.plugin.add('timepickr', 'minutes', { - initialize: function(e, ui) { - var p = ui._dom.hours && ui._dom.hours || false; - ui._dom.minutes = ui._addRow(ui.options.rangeMin, '{0:0.2d}', 'minutes', p); - } -}); - -$.ui.plugin.add('timepickr', 'seconds', { - initialize: function(e, ui) { - var p = ui._dom.minutes && ui._dom.minutes || false; - ui._dom.seconds = ui._addRow(ui.options.rangeSec, '{0:0.2d}', 'seconds', p); - } -}); - -$.ui.plugin.add('timepickr', 'val', { - initialized: function(e, ui) { - ui._setVal(ui.options.val); - } -}); - -$.ui.plugin.add('timepickr', 'updateLive', { - refresh: function(e, ui) { - ui._setVal(); - } -}); - -$.ui.plugin.add('timepickr', 'resetOnBlur', { - initialized: function(e, ui) { - ui.element.data('timepickr.initialValue', ui._getVal()); - ui._dom.menu.find('li > span').bind('mousedown.timepickr', function(){ - ui.element.data('timepickr.initialValue', ui._getVal()); - }); - }, - blur: function(e, ui) { - ui._setVal(ui.element.data('timepickr.initialValue')); - } -}); - -$.ui.plugin.add('timepickr', 'handle', { - initialized: function(e, ui) { - $(ui.options.handle).bind(ui.options.handleEvent + '.timepickr', function(){ - ui.show(); - }); - } -}); - -$.ui.plugin.add('timepickr', 'keyboardnav', { - initialized: function(e, ui) { - ui.element - .bind('keydown', function(e) { - if ($.keyIs('enter', e)) { - ui._setVal(); - ui.blur(); - } - else if ($.keyIs('escape', e)) { - ui.blur(); - } - }); - } -}); - -var Time = function() { // arguments: h, m, s, c, z, f || time string - if (!(this instanceof arguments.callee)) { - throw Error("Constructor called as a function"); - } - // arguments as literal object - if (arguments.length == 1 && $.isObject(arguments[0])) { - this.h = arguments[0].h || 0; - this.m = arguments[0].m || 0; - this.s = arguments[0].s || 0; - this.c = arguments[0].c && ($.inArray(arguments[0].c, [12, 24]) >= 0) && arguments[0].c || 24; - this.f = arguments[0].f || ((this.c == 12) && '{h:02.d}:{m:02.d} {z:02.d}' || '{h:02.d}:{m:02.d}'); - this.z = arguments[0].z || 'am'; - } - // arguments as string - else if (arguments.length < 4 && $.isString(arguments[1])) { - this.c = arguments[2] && ($.inArray(arguments[0], [12, 24]) >= 0) && arguments[0] || 24; - this.f = arguments[3] || ((this.c == 12) && '{h:02.d}:{m:02.d} {z:02.d}' || '{h:02.d}:{m:02.d}'); - this.z = arguments[4] || 'am'; - - this.h = arguments[1] || 0; // parse - this.m = arguments[1] || 0; // parse - this.s = arguments[1] || 0; // parse - } - // no arguments (now) - else if (arguments.length === 0) { - // now - } - // standards arguments - else { - this.h = arguments[0] || 0; - this.m = arguments[1] || 0; - this.s = arguments[2] || 0; - this.c = arguments[3] && ($.inArray(arguments[3], [12, 24]) >= 0) && arguments[3] || 24; - this.f = this.f || ((this.c == 12) && '{h:02.d}:{m:02.d} {z:02.d}' || '{h:02.d}:{m:02.d}'); - this.z = 'am'; - } - return this; -}; - -Time.prototype.get = function(p, f, u) { return u && this.h || $.format(f, this.h); }; -Time.prototype.getHours = function(unformated) { return this.get('h', '{0:02.d}', unformated); }; -Time.prototype.getMinutes = function(unformated) { return this.get('m', '{0:02.d}', unformated); }; -Time.prototype.getSeconds = function(unformated) { return this.get('s', '{0:02.d}', unformated); }; -Time.prototype.setFormat = function(format) { return this.f = format; }; -Time.prototype.getObject = function() { return { h: this.h, m: this.m, s: this.s, c: this.c, f: this.f, z: this.z }; }; -Time.prototype.getTime = function() { return $.format(this.f, {h: this.h, m: this.m, suffix: this.z}); }; // Thanks to Jackson for the fix. -Time.prototype.parse = function(str) { - // 12h formats - if (this.c === 12) { - // Supported formats: (can't find any *official* standards for 12h..) - // - [hh]:[mm]:[ss] [zz] | [hh]:[mm] [zz] | [hh] [zz] - // - [hh]:[mm]:[ss] [z.z.] | [hh]:[mm] [z.z.] | [hh] [z.z.] - this.tokens = str.split(/\s|:/); - this.h = this.tokens[0] || 0; - this.m = this.tokens[1] || 0; - this.s = this.tokens[2] || 0; - this.z = this.tokens[3] || ''; - return this.getObject(); - } - // 24h formats - else { - // Supported formats: - // - ISO 8601: [hh][mm][ss] | [hh][mm] | [hh] - // - ISO 8601 extended: [hh]:[mm]:[ss] | [hh]:[mm] | [hh] - this.tokens = /:/.test(str) && str.split(/:/) || str.match(/[0-9]{2}/g); - this.h = this.tokens[0] || 0; - this.m = this.tokens[1] || 0; - this.s = this.tokens[2] || 0; - this.z = this.tokens[3] || ''; - return this.getObject(); - } -}; - -})(jQuery); diff --git a/rt/share/html/NoAuth/js/util.js b/rt/share/html/NoAuth/js/util.js index 5bfce411d..fe5c0a3ff 100644 --- a/rt/share/html/NoAuth/js/util.js +++ b/rt/share/html/NoAuth/js/util.js @@ -222,35 +222,47 @@ function doOnLoad( js ) { } jQuery(function() { - jQuery(".ui-datepicker:not(.withtime)").datepicker( { - dateFormat: 'yy-mm-dd', - constrainInput: false - } ); - - jQuery(".ui-datepicker.withtime").datepicker( { + var opts = { dateFormat: 'yy-mm-dd', constrainInput: false, - onSelect: function( dateText, inst ) { - // trigger timepicker to get time - var button = document.createElement('input'); - button.setAttribute('type', 'button'); - jQuery(button).width('5em'); - jQuery(button).insertAfter(this); - jQuery(button).timepickr({val: '00:00'}); - var date_input = this; - - jQuery(button).blur( function() { - var time = jQuery(button).val(); - if ( ! time.match(/\d\d:\d\d/) ) { - time = '00:00'; - } - jQuery(date_input).val( dateText + ' ' + time + ':00' ); - jQuery(button).remove(); - } ); - - jQuery(button).focus(); - } - } ); + showButtonPanel: true, + changeMonth: true, + changeYear: true, + showOtherMonths: true, + selectOtherMonths: true + }; + jQuery(".ui-datepicker:not(.withtime)").datepicker(opts); + jQuery(".ui-datepicker.withtime").datetimepicker( jQuery.extend({}, opts, { + stepHour: 1, + // We fake this by snapping below for the minute slider + //stepMinute: 5, + hourGrid: 6, + minuteGrid: 15, + showSecond: false, + timeFormat: 'hh:mm:ss' + }) ).each(function(index, el) { + var tp = jQuery.datepicker._get( jQuery.datepicker._getInst(el), 'timepicker'); + if (!tp) return; + + // Hook after _injectTimePicker so we can modify the minute_slider + // right after it's first created + tp._base_injectTimePicker = tp._injectTimePicker; + tp._injectTimePicker = function() { + this._base_injectTimePicker.apply(this, arguments); + + // Now that we have minute_slider, modify it to be stepped for mouse movements + var slider = jQuery.data(this.minute_slider[0], "slider"); + slider._base_normValueFromMouse = slider._normValueFromMouse; + slider._normValueFromMouse = function() { + var value = this._base_normValueFromMouse.apply(this, arguments); + var old_step = this.options.step; + this.options.step = 5; + var aligned = this._trimAlignValue( value ); + this.options.step = old_step; + return aligned; + }; + }; + }); }); function textToHTML(value) { diff --git a/rt/share/html/Prefs/Other.html b/rt/share/html/Prefs/Other.html index 9f7e04a9c..b5d3edd95 100644 --- a/rt/share/html/Prefs/Other.html +++ b/rt/share/html/Prefs/Other.html @@ -53,6 +53,7 @@ % foreach my $section( RT->Config->Sections ) { <&|/Widgets/TitleBox, title => loc( $section ) &> % foreach my $option( RT->Config->Options( Section => $section ) ) { +% next if $option eq 'EmailFrequency' && !RT->Config->Get('RecordOutgoingEmail'); % my $meta = RT->Config->Meta( $option ); <& $meta->{'Widget'}, Default => 1, diff --git a/rt/share/html/REST/1.0/Forms/ticket/default b/rt/share/html/REST/1.0/Forms/ticket/default index 9a2212b55..016a50c73 100755 --- a/rt/share/html/REST/1.0/Forms/ticket/default +++ b/rt/share/html/REST/1.0/Forms/ticket/default @@ -167,6 +167,17 @@ else { elsif (lc $k eq 'text') { $text = delete $data{$k}; } + elsif ( lc $k ne 'id' ) { + $e = 1; + push @$o, $k; + push(@comments, "# $k: Unknown field"); + } + } + + if ( $e ) { + unshift @comments, "# Could not create ticket."; + $k = \%data; + goto DONE; } # people fields allow multiple values @@ -292,8 +303,10 @@ else { elsif (exists $simple{$key}) { $key = $simple{$key}; $set = "Set$key"; + my $current = $ticket->$key; + $current = '' unless defined $current; - next if (($val eq ($ticket->$key||''))|| ($ticket->$key =~ /^\d+$/ && $val =~ /^\d+$/ && $val == $ticket->$key)); + next if ($val eq $current) or ($current =~ /^\d+$/ && $val =~ /^\d+$/ && $val == $current); ($n, $s) = $ticket->$set("$val"); } elsif (exists $dates{$key}) { @@ -331,13 +344,6 @@ else { } } foreach $p (keys %new) { - # XXX: This is a stupid test. - unless ($p =~ /^[\w.+-]+\@([\w.-]+\.)*\w+.?$/) { - $s = 0; - $n = "$p is not a valid email address."; - push @msgs, [ $s, $n ]; - next; - } unless ($ticket->IsWatcher(Type => $type, Email => $p)) { ($s, $n) = $ticket->AddWatcher(Type => $type, Email => $p); diff --git a/rt/share/html/Search/Chart.html b/rt/share/html/Search/Chart.html index 070ce7cf7..571c3d3a0 100644 --- a/rt/share/html/Search/Chart.html +++ b/rt/share/html/Search/Chart.html @@ -98,14 +98,14 @@ my %query; for(@session_fields) { $query{$_} = $current->{$_} unless defined $query{$_}; - $query{$_} = $m->request_args->{$_} unless defined $query{$_}; + $query{$_} = $DECODED_ARGS->{$_} unless defined $query{$_}; } - if ($m->request_args->{'SavedSearchLoadSubmit'}) { - $query{'SavedChartSearchId'} = $m->request_args->{'SavedSearchLoad'}; + if ($DECODED_ARGS->{'SavedSearchLoadSubmit'}) { + $query{'SavedChartSearchId'} = $DECODED_ARGS->{'SavedSearchLoad'}; } - if ($m->request_args->{'SavedSearchSave'}) { + if ($DECODED_ARGS->{'SavedSearchSave'}) { $query{'SavedChartSearchId'} = $saved_search->{'SearchId'}; } diff --git a/rt/share/html/Search/Elements/SelectPersonType b/rt/share/html/Search/Elements/SelectPersonType index d07e49c22..bc2911196 100644 --- a/rt/share/html/Search/Elements/SelectPersonType +++ b/rt/share/html/Search/Elements/SelectPersonType @@ -62,7 +62,7 @@ <%INIT> my @types; -if ($Scope =~ 'queue') { +if ($Scope =~ /queue/) { @types = qw(Cc AdminCc); } elsif ($Suffix eq 'Group') { diff --git a/rt/share/html/Search/Results.html b/rt/share/html/Search/Results.html index 171b38d92..4fee86506 100755 --- a/rt/share/html/Search/Results.html +++ b/rt/share/html/Search/Results.html @@ -151,6 +151,7 @@ if ($ARGS{'TicketsRefreshInterval'}) { my $refresh = $session{'tickets_refresh_interval'} || RT->Config->Get('SearchResultsRefreshInterval', $session{'CurrentUser'} ); +# Check $m->request_args, not $DECODED_ARGS, to avoid creating a new CSRF token on each refresh if (RT->Config->Get('RestrictReferrer') and $refresh and not $m->request_args->{CSRF_Token}) { my $token = RT::Interface::Web::StoreRequestToken( $session{'CurrentSearchHash'} ); $m->notes->{RefreshURL} = RT->Config->Get('WebURL') diff --git a/rt/share/html/Ticket/Attachment/dhandler b/rt/share/html/Ticket/Attachment/dhandler index 75efeffbc..eb291e4f5 100755 --- a/rt/share/html/Ticket/Attachment/dhandler +++ b/rt/share/html/Ticket/Attachment/dhandler @@ -48,7 +48,7 @@ <%perl> my ($ticket, $trans,$attach, $filename); my $arg = $m->dhandler_arg; # get rest of path - if ($arg =~ '^(\d+)/(\d+)') { + if ($arg =~ m{^(\d+)/(\d+)}) { $trans = $1; $attach = $2; } @@ -79,7 +79,12 @@ my $enc = $AttachmentObj->OriginalEncoding || 'utf-8'; my $iana = Encode::find_encoding( $enc ); $iana = $iana? $iana->mime_name : $enc; - $content_type .= ";charset=$iana"; + + require MIME::Types; + my $mimetype = MIME::Types->new->type($content_type); + unless ( $mimetype && $mimetype->isBinary ) { + $content_type .= ";charset=$iana"; + } $r->subprocess_env('no-gzip' => 1); # disable mod_deflate $r->content_type( $content_type ); diff --git a/rt/share/html/Ticket/Elements/ShowMembers b/rt/share/html/Ticket/Elements/ShowMembers index c17c6e7b8..1ffbda2a1 100755 --- a/rt/share/html/Ticket/Elements/ShowMembers +++ b/rt/share/html/Ticket/Elements/ShowMembers @@ -48,8 +48,9 @@ <ul> % while (my $link = $members->Next) { <li><& /Elements/ShowLink, URI => $link->BaseURI &><br /> +% next if $link->BaseObj and $checked->{$link->BaseObj->id}; % if ($depth < 8) { -<& /Ticket/Elements/ShowMembers, Ticket => $link->BaseObj, depth => ($depth+1) &> +<& /Ticket/Elements/ShowMembers, Ticket => $link->BaseObj, depth => ($depth+1), checked => $checked &> % } </li> % } @@ -61,9 +62,13 @@ return unless $Ticket; my $members = $Ticket->Members; return unless $members->Count; +return if $checked->{$Ticket->id}; + +$checked->{$Ticket->id} = 1; </%INIT> <%ARGS> $Ticket => undef $depth => 1 +$checked => {} </%ARGS> diff --git a/rt/share/html/Ticket/Elements/ShowTransactionAttachments b/rt/share/html/Ticket/Elements/ShowTransactionAttachments index 877201f55..95a23411b 100644 --- a/rt/share/html/Ticket/Elements/ShowTransactionAttachments +++ b/rt/share/html/Ticket/Elements/ShowTransactionAttachments @@ -144,6 +144,8 @@ my $render_attachment = sub { my $message = shift; my $name = defined $message->Filename && length $message->Filename ? $message->Filename : ''; + my $content_type = lc $message->ContentType; + # if it has a content-disposition: attachment, don't show inline my $disposition = $message->GetHeader('Content-Disposition'); @@ -154,7 +156,7 @@ my $render_attachment = sub { } # If it's text - if ( $message->ContentType =~ m{^(text|message)}i ) { + if ( $content_type =~ m{^(text|message)/} ) { my $max_size = RT->Config->Get( 'MaxInlineBody', $session{'CurrentUser'} ); if ( $disposition ne 'inline' ) { $m->out('<p>'. loc( 'Message body is not shown because sender requested not to inline it.' ) .'</p>'); @@ -175,16 +177,16 @@ my $render_attachment = sub { !$ParentObj # or its parent isn't a multipart alternative - || ( $ParentObj->ContentType !~ m{^multipart/alternative$}i ) + || ( $ParentObj->ContentType !~ m{^multipart/(?:alternative|related)$}i ) # or it's of our prefered alterative type || ( ( RT->Config->Get('PreferRichText') - && ( $message->ContentType =~ m{^text/(?:html|enriched)$} ) + && ( $content_type =~ m{^text/(?:html|enriched)$} ) ) || ( !RT->Config->Get('PreferRichText') - && ( $message->ContentType !~ m{^text/(?:html|enriched)$} ) + && ( $content_type !~ m{^text/(?:html|enriched)$} ) ) ) ) { @@ -198,7 +200,6 @@ my $render_attachment = sub { $content = $message->Content; } - my $content_type = lc $message->ContentType; $RT::Logger->debug( "Rendering attachment #". $message->id ." of '$content_type' type" @@ -231,9 +232,8 @@ my $render_attachment = sub { $m->out( $content ); } - # if it's a text/plain show the body - elsif ( $message->ContentType =~ m{^(text|message)}i ) { - + # It's a text type we don't have special handling for + else { unless ( length $name ) { eval { require Text::Quoted; $content = Text::Quoted::extract($content); }; if ($@) { $RT::Logger->warning( "Text::Quoted failed: $@" ) } @@ -250,7 +250,7 @@ my $render_attachment = sub { } # if it's an image, show it as an image - elsif ( RT->Config->Get('ShowTransactionImages') and $message->ContentType =~ /^image\//i ) { + elsif ( RT->Config->Get('ShowTransactionImages') and $content_type =~ m{^image/} ) { if ( $disposition ne 'inline' ) { $m->out('<p>'. loc( 'Message body is not shown because sender requested not to inline it.' ) .'</p>'); return; diff --git a/rt/share/html/m/_elements/raw_style b/rt/share/html/m/_elements/raw_style index 8c1997743..a34982958 100644 --- a/rt/share/html/m/_elements/raw_style +++ b/rt/share/html/m/_elements/raw_style @@ -33,6 +33,7 @@ div.buttons { background-color: #ccc; -moz-border-radius: 0.25em; -webkit-border-radius: 0.25em; + border-radius: 0.25em; -webkit-box-shadow: #333 0px 0px 5px; -moz-box-shadow: #333 0px 0px 5px; box-shadow: #333 0px 0px 5px; @@ -85,6 +86,7 @@ ul.menu li#active a div.titlebox, #bpscredits, .ticket_menu{ -moz-border-radius: 1em; -webkit-border-radius: 1em; + border-radius: 1em; margin: 0.5em; background-color: #fff; padding-top: 1em; @@ -336,6 +338,7 @@ input[type=submit], input[type=button], button, #paging a { padding-right: 0.6em; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; + border-radius: 0.5em; background-color: #006699; color: #fff; } @@ -353,6 +356,7 @@ form { border-bottom: 1px solid black; -moz-border-radius-bottomleft: 1em; -webkit-border-bottom-left-radius: 1em; + border-bottom-left-radius: 1em; padding: 0.5em; background-color: #fff; } diff --git a/rt/share/html/m/_elements/wrapper b/rt/share/html/m/_elements/wrapper index 794385db4..1891079bd 100644 --- a/rt/share/html/m/_elements/wrapper +++ b/rt/share/html/m/_elements/wrapper @@ -3,7 +3,7 @@ $title => '' $show_home_button => 1 </%args> <%init> -if ($m->request_args->{'NotMobile'}) { +if ($DECODED_ARGS->{'NotMobile'}) { $session{'NotMobile'} = 1; RT::Interface::Web::Redirect(RT->Config->Get('WebURL')); $m->abort(); |