#! /bin/sh
# From configure.ac Revision.
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.68 for RT rt-4.0.19.
+# Generated by GNU Autoconf 2.68 for RT rt-4.0.20.
#
# Report bugs to <rt-bugs@bestpractical.com>.
#
# Identity of this package.
PACKAGE_NAME='RT'
PACKAGE_TARNAME='rt'
-PACKAGE_VERSION='rt-4.0.19'
-PACKAGE_STRING='RT rt-4.0.19'
+PACKAGE_VERSION='rt-4.0.20'
+PACKAGE_STRING='RT rt-4.0.20'
PACKAGE_BUGREPORT='rt-bugs@bestpractical.com'
PACKAGE_URL=''
# 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.19 to adapt to many kinds of systems.
+\`configure' configures RT rt-4.0.20 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of RT rt-4.0.19:";;
+ short | recursive ) echo "Configuration of RT rt-4.0.20:";;
esac
cat <<\_ACEOF
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-RT configure rt-4.0.19
+RT configure rt-4.0.20
generated by GNU Autoconf 2.68
Copyright (C) 2010 Free Software Foundation, Inc.
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.19, which was
+It was created by RT $as_me rt-4.0.20, which was
generated by GNU Autoconf 2.68. Invocation command line was
$ $0 $@
rt_version_minor=0
-rt_version_patch=19
+rt_version_patch=20
test "x$rt_version_major" = 'x' && rt_version_major=0
test "x$rt_version_minor" = 'x' && rt_version_minor=0
fi
-ac_config_files="$ac_config_files etc/upgrade/3.8-ical-extension etc/upgrade/split-out-cf-categories etc/upgrade/generate-rtaddressregexp etc/upgrade/upgrade-articles etc/upgrade/vulnerable-passwords sbin/rt-attributes-viewer sbin/rt-preferences-viewer sbin/rt-session-viewer sbin/rt-dump-metadata sbin/rt-setup-database sbin/rt-test-dependencies sbin/rt-email-digest sbin/rt-email-dashboards sbin/rt-clean-sessions sbin/rt-shredder sbin/rt-validator sbin/rt-validate-aliases sbin/rt-email-group-admin sbin/rt-server sbin/rt-server.fcgi sbin/standalone_httpd sbin/rt-setup-fulltext-index sbin/rt-fulltext-indexer bin/rt-crontool bin/rt-mailgate bin/rt"
+ac_config_files="$ac_config_files etc/upgrade/3.8-ical-extension etc/upgrade/4.0-customfield-checkbox-extension etc/upgrade/split-out-cf-categories etc/upgrade/generate-rtaddressregexp etc/upgrade/upgrade-articles etc/upgrade/vulnerable-passwords sbin/rt-attributes-viewer sbin/rt-preferences-viewer sbin/rt-session-viewer sbin/rt-dump-metadata sbin/rt-setup-database sbin/rt-test-dependencies sbin/rt-email-digest sbin/rt-email-dashboards sbin/rt-clean-sessions sbin/rt-shredder sbin/rt-validator sbin/rt-validate-aliases sbin/rt-email-group-admin sbin/rt-server sbin/rt-server.fcgi sbin/standalone_httpd sbin/rt-setup-fulltext-index sbin/rt-fulltext-indexer bin/rt-crontool bin/rt-mailgate bin/rt"
ac_config_files="$ac_config_files Makefile etc/RT_Config.pm lib/RT/Generated.pm t/data/configs/apache2.2+mod_perl.conf t/data/configs/apache2.2+fastcgi.conf"
# 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.19, which was
+This file was extended by RT $as_me rt-4.0.20, which was
generated by GNU Autoconf 2.68. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
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.19
+RT config.status rt-4.0.20
configured by $0, generated by GNU Autoconf 2.68,
with options \\"\$ac_cs_config\\"
do
case $ac_config_target in
"etc/upgrade/3.8-ical-extension") CONFIG_FILES="$CONFIG_FILES etc/upgrade/3.8-ical-extension" ;;
+ "etc/upgrade/4.0-customfield-checkbox-extension") CONFIG_FILES="$CONFIG_FILES etc/upgrade/4.0-customfield-checkbox-extension" ;;
"etc/upgrade/split-out-cf-categories") CONFIG_FILES="$CONFIG_FILES etc/upgrade/split-out-cf-categories" ;;
"etc/upgrade/generate-rtaddressregexp") CONFIG_FILES="$CONFIG_FILES etc/upgrade/generate-rtaddressregexp" ;;
"etc/upgrade/upgrade-articles") CONFIG_FILES="$CONFIG_FILES etc/upgrade/upgrade-articles" ;;
case $ac_file$ac_mode in
"etc/upgrade/3.8-ical-extension":F) chmod ug+x $ac_file
;;
+ "etc/upgrade/4.0-customfield-checkbox-extension":F) chmod ug+x $ac_file
+ ;;
"etc/upgrade/split-out-cf-categories":F) chmod ug+x $ac_file
;;
"etc/upgrade/generate-rtaddressregexp":F) chmod ug+x $ac_file
dnl Binaries that should be +x
AC_CONFIG_FILES([
etc/upgrade/3.8-ical-extension
+ etc/upgrade/4.0-customfield-checkbox-extension
etc/upgrade/split-out-cf-categories
etc/upgrade/generate-rtaddressregexp
etc/upgrade/upgrade-articles
set to `perl-script`. F<docs/web_deployment.pod> contains the
new configuration.
+RT::Extension::CustomField::Checkbox has been integrated into core, so you
+MUST uninstall it before upgrading. In addition, you must run
+etc/upgrade/4.0-customfield-checkbox-extension script to convert old data.
=head2 RT_SiteConfig.pm
called when needed. They should return the modified string. Note that
subroutine B<must escape> HTML.
-=item handler
+=item handle
A subroutine reference; modify it only if you have to. This can be used
to add pre- or post-processing around all actions.
+=item cache
+
+An undefined variable that should be replaced with a subroutine
+reference. This subroutine will be called twice, once with the arguments
+fetch => content_ref and once with store => content_ref. In the fetch
+case, if a cached copy is found, return the cached content, otherwise
+return a false value. When passed store, you should populate your cache
+with the content. The return value is ignored in this case.
+
=back
=head2 Actions' arguments
=item C<$EmailSubjectTagRegex>
This regexp controls what subject tags RT recognizes as its own. If
-you're not dealing with historical C<$rtname> values, you'll likely
-never have to change this configuration.
+you're not dealing with historical C<$rtname> values, or historical
+queue-specific subject tags, you'll likely never have to change this
+configuration.
Be B<very careful> with it. Note that it overrides C<$rtname> for
subject token matching and that you should use only "non-capturing"
s!(?<=Your ticket has been (?:approved|rejected) by { eval { )\$Approval->OwnerObj->Name!\$Approver->Name!
)
{
+ $template->SetType('Perl');
$template->SetContent($content);
}
}
my @tmp_inc;
my $added;
for (@INC) {
- if ( Cwd::realpath($_) eq $RT::LocalLibPath) {
+ my $realpath = Cwd::realpath($_);
+ next unless defined $realpath;
+ if ( $realpath eq $RT::LocalLibPath) {
push @tmp_inc, $_, @lib_dirs;
$added = 1;
} else {
my $entry = ${$pack}{$k};
next unless $entry;
- # get entry for type we are looking for
- # XXX skip references to scalars or other references.
- # Otherwie 5.10 goes boom. maybe we should skip any
- # reference
- next if ref($entry) eq 'SCALAR' || ref($entry) eq 'REF';
+ # Inlined constants are simplified in the symbol table --
+ # namely, when possible, you only get a reference back in
+ # $entry, rather than a full GLOB. In 5.10, scalar
+ # constants began being inlined this way; starting in 5.20,
+ # list constants are also inlined. Notably, ref(GLOB) is
+ # undef, but inlined constants are currently either REF,
+ # SCALAR, or ARRAY.
+ next if ref($entry);
my $ref_type = ref($ref);
sub AddDays {
my $self = shift;
- my $days = shift || 1;
+ my $days = shift;
+ $days = 1 unless defined $days;
return $self->AddSeconds( $days * $DAY );
}
use warnings;
use strict;
-our $VERSION = '4.0.19';
+our $VERSION = '4.0.20';
return (0, 'no nobody user', "Couldn't find Nobody user in the DB '". $self->DSN ."'");
}
- return $RT::Handle->dbh;
+ return 1;
}
sub CheckCompatibility {
);
# Slurp in stuff to insert from the datafile. Possible things to go in here:-
- our (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
+ our (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
@Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
- local (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
+ local (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
@Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
local $@;
$RT::Logger->debug("Creating groups...");
foreach my $item (@Groups) {
my $new_entry = RT::Group->new( RT->SystemUser );
+ $item->{Domain} ||= 'UserDefined';
my $member_of = delete $item->{'MemberOf'};
+ my $members = delete $item->{'Members'};
my ( $return, $msg ) = $new_entry->_Create(%$item);
unless ( $return ) {
$RT::Logger->error( $msg );
}
}
}
+ push @Members, map { +{Group => $new_entry->id,
+ Class => "RT::User", Name => $_} }
+ @{ $members->{Users} || [] };
+ push @Members, map { +{Group => $new_entry->id,
+ Class => "RT::Group", Name => $_} }
+ @{ $members->{Groups} || [] };
}
$RT::Logger->debug("done.");
}
}
$RT::Logger->debug("done.");
}
+ if ( @Members ) {
+ $RT::Logger->debug("Adding users and groups to groups...");
+ for my $item (@Members) {
+ my $group = RT::Group->new(RT->SystemUser);
+ $group->LoadUserDefinedGroup( delete $item->{Group} );
+ unless ($group->Id) {
+ RT->Logger->error("Unable to find group '$group' to add members to");
+ next;
+ }
+
+ my $class = delete $item->{Class} || 'RT::User';
+ my $member = $class->new( RT->SystemUser );
+ $item->{Domain} = 'UserDefined' if $member->isa("RT::Group");
+ $member->LoadByCols( %$item );
+ unless ($member->Id) {
+ RT->Logger->error("Unable to find $class '".($item->{id} || $item->{Name})."' to add to ".$group->Name);
+ next;
+ }
+
+ my ( $return, $msg) = $group->AddMember( $member->PrincipalObj->Id );
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ } else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ }
if ( @Queues ) {
$RT::Logger->debug("Creating queues...");
for my $item (@Queues) {
Path => $tmp_fn,
Type => $info->{'Content-Type'} || guess_media_type($tmp_fn),
Filename => $file,
- Disposition => "attachment",
+ Disposition => $info->{'Content-Disposition'} || "attachment",
);
$new_entity->bodyhandle->{'_dirty_hack_to_save_a_ref_tmp_fh'} = $tmp_fh;
$i++;
sub ComponentPathIsSafe {
my $self = shift;
my $path = shift;
- return $path !~ m{(?:^|/)\.} and $path !~ m{\0};
+ return($path !~ m{(?:^|/)\.} and $path !~ m{\0});
}
=head2 PathIsSafe
# CGI.pm normalizes .. out of paths so when you requested
# /NoAuth/../Ticket/Display.html we saw Ticket/Display.html
# PSGI doesn't normalize .. so we have to deal ourselves.
- if ( $req->path_info =~ m{/\.} ) {
+ if ( $req->path_info =~ m{(^|/)\.\.?(/|$)} ) {
$RT::Logger->crit("Invalid request for ".$req->path_info." aborting");
my $res = Plack::Response->new(400);
return $self->_psgi_response_cb($res->finalize,sub { $self->CleanupRequest });
return 0;
}
-=head3 inactive
+=head3 Inactive
Returns an array of all inactive statuses for this lifecycle.
return $self->Valid('inactive');
}
-=head3 is_inactive
+=head3 IsInactive
Takes a value and returns true if value is a valid inactive status.
Otherwise, returns false.
my $query = $args{'Object'}->_AsInsertQuery;
$query .= "\n" unless $query =~ /\n$/;
- return print $fh $query or return (0, "Couldn't write to filehandle");
- return 1;
+ return 1 if print $fh $query;
+ return (0, "Couldn't write to filehandle");
}
1;
RT::StyleGuide - RT Style Guide
+=head1 CAVEATS
+
+This file is somewhat out of date; L<hacking> takes precedence over it.
+
=head1 INTRODUCTION
All code and documentation that is submitted to be included in the RT
1.1.0 First development release of RT 1.2 (or 2.0)
2.0.0 First release of RT 2
-Versions can be modified with a hyphen followed by some text, for
-special versions, or to give extra information. Examples:
-
- 2.0.0-pre1 Notes that this is not final, but preview
-
-In perl 5.6.0, you can have versions like C<v2.0.0>, but this is not
-allowed in previous versions of perl. So to convert a tuple version
-string to a string to use with $VERSION, use a regular integer for
-the revision, and three digits for version and subversion. Examples:
+Versions may end in "rc" and a number if they are release candidates:
- 1.1.6 -> 1.001006
- 2.0.0 -> 2.000000
-
-This way, perl can use the version strings in greater-than and
-less-than comparisons.
+ 2.0.0rc1 First release candiate for real 2.0.0
=head2 Comments
is seldom a need to do it, and you shouldn't.
-=head2 Exporting
-
-Do not export anything from a module by default. Feel free to put
-anything you want to in @EXPORT_OK, so users of your modules can
-explicitly ask for symbols (e.g., "use Something::Something qw(getFoo
-setFoo)"), but do not export them by default.
-
-
=head2 Pass by Reference
Arrays and hashes should be passed to and from functions by reference
We need to talk about Class::ReturnValue here.
-=head2 Garbage Collection
-
-Perl does pretty good garbage collection for you. It will automatically
-clean up lexical variables that have gone out of scope and objects whose
-references have gone away. Normally you don't need to worry about
-cleaning up after yourself, if using lexicals.
-
-However, some glue code, code compiled in C and linked to Perl, might
-not automatically clean up for you. In such cases, clean up for
-yourself. If there is a method in that glue to dispose or destruct,
-then use it as appropriate.
-
-Also, if you have a long-running function that has a large data
-structure in it, it is polite to free up the memory as soon as you are
-done with it, if possible.
-
- my $huge_data_structure = get_huge_data_structure();
- do_something_with($huge_data_structure);
- undef $huge_data_structure;
-
-=head2 DESTROY
-
-All object classes must provide a DESTROY method. If it won't do
-anything, provide it anyway:
-
- sub DESTROY { }
-
-
-
-=head2 die() and exit()
-
-Don't do it. Do not die() or exit() from a web template or module. Do
-not call C<kill 9, $$>. Don't do it.
-
-In command-line programs, do as you please.
-
-
-=head2 shift and @_
-
-Do not use @_. Use shift. shift may take more lines, but Jesse thinks it
-leads to cleaner code.
-
- my $var = shift; # right
- my($var) = @_; # ick. no
- sub foo { uc $_[0] } # icky. sometimes ok.
-
-
- my($var1, $var2) = (shift, shift); # Um, no.
-
- my $var1 = shift; # right
- my $var2 = shift;
-
=head2 Method parameters
If a method takes exactly one mandatory argument, the argument should be
using a C<%args> hash to store them:
my $self = shift;
- my %args = ( Name => undef,
- Description => undef,
- @_ );
+ my %args = (
+ Name => undef,
+ Description => undef,
+ @_
+ );
You may specify defaults to those named parameters instead of using
C<undef> above, as long as it is documented as such.
It is worth noting that the existing RT codebase had not followed this
-style perfectly; we are trying to fix it without breaking exsiting APIs.
+style perfectly; we are trying to fix it without breaking existing APIs.
=head2 Tests
=over 4
-=item RT the name
-
-"RT" is the name of the project. "RT" is, optionally, the
-specific name for the actual file distribution. That's it.
-
-While we sometimes use "RT2" or "RT3", that's shortand that's really
-not recommended. The name of the project is "RT".
-
-To specify a major version, use "RT 3.0".
-To specify a specific release, use "RT 3.0.12"
-
=item function vs. sub(routine) vs. method
Just because it is the Perl Way (not necessarily right for all
map() and grep()).
for (@list) {
- print; # OK; everyone knows this one
- print uc; # wrong; few people know this
- print uc $_; # better
+ print; # OK; everyone knows this one
+ print uc; # wrong; few people know this
+ print uc $_; # better
}
Note that the special variable C<_> I<should> be used when possible.
careful that the last-tested file is what you think it is, though.
if (-d $file) { # $file is a directory
- # ...
+ # ...
} elsif (-l _) { # $file is a symlink
- # ...
+ # ...
}
Package names begin with a capital letter in each word, followed by
RT::Display::Provider # good
RT::CustomField # not so good, but OK
-Plugin modules should begin with "RTx::", followed by the name
+Plugin modules should begin with "RT::Extension::", followed by the name
of the plugin.
=head1 Code formatting
-Use perltidy. Anything we say here is wrong if it conflicts with what
-perltidy does. Your perltidyrc should read:
-
--lp -vt=2 -vtc=2 -nsfs -bar
+When in doubt, use perltidy; RT includes a F<.perltidyrc>.
=head2 Indents and Blank Space
-All indents should be tabs. Set your tab stops whatever you want them
-to be; I use 8 spaces per tabs.
+All indents should be four spaces; hard tabs are forbidden.
No space before a semicolon that closes a statement.
# this is my function!
sub foo {
- my $val = shift;
- my $obj = new Constructor;
- my($var1, $var2);
-
- $obj->SetFoo($val);
- $var1 = $obj->Foo();
+ my $val = shift;
+ my $obj = new Constructor;
+ my($var1, $var2);
+ $obj->SetFoo($val);
+ $var1 = $obj->Foo();
- return($val);
+ return($val);
}
print 1;
Examples:
@list = qw(
- bar
- baz
+ bar
+ baz
); # right
if ($foo && $bar && $baz
- && $buz && $xyzzy
- ) {
- print $foo;
+ && $buz && $xyzzy) {
+ print $foo;
}
Whether or not there is space following a closing parenthesis is
keyword at the end.
for (@list) {
- print;
- smell();
+ print;
+ smell();
}
-Generally, we prefer "uncuddled elses":
+Generally, we prefer "cuddled elses":
if ($foo) {
- print;
- }
- else {
- die;
- }
-
-_If_ the if statement is very brief, sometimes "cuddling" the else makes code more readable. Feel free to cuddle them in that case:
-
-
- if ($foo) {
- print;
+ print;
} else {
- die;
+ die;
}
=head2 Operators
matter which you use. Use whichever is most readable and aesthetically
pleasing to you at the time, and be consistent within your block of code.
-Break long lines AFTER operators, except for "and", "or", "&&", "||".
+Break long lines AFTER operators, except for ".", "and", "or", "&&", "||".
Try to keep the two parts to a binary operator (an operator that
has two operands) together when possible.
- print "foo" . "bar" . "baz"
- . "buz"; # wrong
-
print "foo" . "bar" . "baz" .
- "buz"; # right
+ "buz"; # wrong
+
+ print "foo" . "bar" . "baz"
+ . "buz"; # right
print $foo unless $x == 3 && $y ==
- 4 && $z == 5; # wrong
+ 4 && $z == 5; # wrong
print $foo unless $x == 3 && $y == 4
- && $z == 5; # right
+ && $z == 5; # right
=head2 Other
Use here-docs instead of repeated print statements.
- print <<EOT;
+ print <<EOT;
This is a whole bunch of text.
I like it. I don't need to worry about messing
with lots of print statements and lining them up.
The string Foo
Bar
Baz
-
+
Should become <&|/l&>Foo Bar Baz</&>
titleright => loc("RT [_1] for [_2]",$RT::VERSION, RT->Config->Get('rtname')),
title => loc('Login'),
&>
-
-=item Library code
+=item Library code
=item Finish it up
After the code is done (possibly going through multiple code reviews),
-if you do not have repository access, submit it to rt-<major-version>-bugs@fsck.com as a unified diff. From that point on, it'll be handled by someone with repository access.
+if you do not have repository access, submit it to rt-bugs@fsck.com as a
+unified diff. From that point on, it'll be handled by someone with
+repository access.
=back
=head1 BUG REPORTS, PATCHES
-Use rt-<major-version>-bugs@fsck.com for I<any> bug that is not
-being fixed immediately. If it is not in RT, there
-is a good chance it will not be dealt with.
+Use rt-bugs@bestpractical.com for I<any> bug that is not being fixed
+immediately. If it is not in RT, there is a good chance it will not be
+dealt with.
-Send patches to rt-<major-version>-bugs@fsck.com, too. Use C<diff
--u> for patches.
+Send patches to rt-bugs@bestpractical.com, too. Use C<diff -u> for
+patches.
=head1 SCHEMA DESIGN
Talk about adding a new translation
Talk more about logging
-
-=head1 CHANGES
-
- Adapted from Slash Styleguide by jesse - 20 Dec, 2002
-
-
-=head1 VERSION
-
-0.1
TYPE => 'STRING',
SOURCE => $args{Content},
);
+ my ($ok) = $template->compile;
+ unless ($ok) {
+ $RT::Logger->error("Template parsing error in @{[$self->Name]} (#@{[$self->id]}): $Text::Template::ERROR");
+ return ( undef, $self->loc('Template parsing error: [_1]', $Text::Template::ERROR) );
+ }
+
my $is_broken = 0;
my $retval = $template->fill_in(
HASH => $args{TemplateArgs},
return $obj;
}
+
+sub load_or_create_group {
+ my $self = shift;
+ my $name = shift;
+ my %args = (@_);
+
+ my $group = RT::Group->new( RT->SystemUser );
+ $group->LoadUserDefinedGroup( $name );
+ unless ( $group->id ) {
+ my ($id, $msg) = $group->CreateUserDefinedGroup(
+ Name => $name,
+ );
+ die "$msg" unless $id;
+ }
+
+ if ( $args{Members} ) {
+ my $cur = $group->MembersObj;
+ while ( my $entry = $cur->Next ) {
+ my ($status, $msg) = $entry->Delete;
+ die "$msg" unless $status;
+ }
+
+ foreach my $new ( @{ $args{Members} } ) {
+ my ($status, $msg) = $group->AddMember(
+ ref($new)? $new->id : $new,
+ );
+ die "$msg" unless $status;
+ }
+ }
+
+ return $group;
+}
+
=head2 load_or_create_queue
=cut
$self->run_and_capture(%args);
}
+sub run_validator {
+ my $self = shift;
+ my %args = (check => 1, resolve => 0, force => 1, timeout => 0, @_ );
+
+ my $validator_path = "$RT::SbinPath/rt-validator";
+
+ my $cmd = $validator_path;
+ die "Couldn't find $cmd command" unless -f $cmd;
+
+ my $timeout = delete $args{timeout};
+
+ while( my ($k,$v) = each %args ) {
+ next unless $v;
+ $cmd .= " --$k '$v'";
+ }
+ $cmd .= ' 2>&1';
+
+ require IPC::Open2;
+ my ($child_out, $child_in);
+ my $pid = IPC::Open2::open2($child_out, $child_in, $cmd);
+ close $child_in;
+
+ local $SIG{ALRM} = sub { kill KILL => $pid; die "Timeout!" };
+
+ alarm $timeout if $timeout;
+ my $result = eval { local $/; <$child_out> };
+ warn $@ if $@;
+ close $child_out;
+ waitpid $pid, 0;
+ alarm 0;
+
+ DBIx::SearchBuilder::Record::Cachable->FlushCache
+ if $args{'resolve'};
+
+ return ($?, $result);
+}
+
sub run_and_capture {
my $self = shift;
my %args = @_;
my $hash = MIME::Base64::decode_base64($stored);
# Decoding yields 30 byes; first 4 are the salt, the rest are substr(SHA256,0,26)
my $salt = substr($hash, 0, 4, "");
- return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5($value)), 0, 26) eq $hash;
+ return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5(encode_utf8($value))), 0, 26) eq $hash;
} elsif (length $stored == 32) {
# Hex nonsalted-md5
return 0 unless Digest::MD5::md5_hex(encode_utf8($value)) eq $stored;
}
}
+=head2 DeletePreferences NAME/OBJ VALUE
+
+Delete user preferences associated with given object or name.
+
+=cut
+
+sub DeletePreferences {
+ my $self = shift;
+ my $name = _PrefName( shift );
+
+ return (0, $self->loc("No permission to set preferences"))
+ unless $self->CurrentUserCanModify('Preferences');
+
+ my $attr = RT::Attribute->new( $self->CurrentUser );
+ $attr->LoadByNameAndObject( Object => $self, Name => $name );
+ if ( $attr->Id ) {
+ return $attr->Delete;
+ }
+
+ return (0, $self->loc("Preferences were not found"));
+}
+
=head2 Stylesheet
Returns a list of valid stylesheets take from preferences.
}
-=head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1 }
+=head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1, IncludeUnprivileged => 0 }
+
+Return members who belong to any of the groups passed in the groups whose IDs
+are included in the Groups arrayref.
+
+If IncludeSubgroupMembers is true (default) then members of any group that's a
+member of one of the passed groups are returned. If it's cleared then only
+direct member users are returned.
+
+If IncludeUnprivileged is false (default) then only privileged members are
+returned; otherwise either privileged or unprivileged group members may be
+returned.
=cut
-# XXX: should be generalized
sub WhoBelongToGroups {
my $self = shift;
my %args = ( Groups => undef,
IncludeSubgroupMembers => 1,
+ IncludeUnprivileged => 0,
@_ );
- # Unprivileged users can't be granted real system rights.
- # is this really the right thing to be saying?
- $self->LimitToPrivileged();
-
+ if (!$args{'IncludeUnprivileged'}) {
+ $self->LimitToPrivileged();
+ }
my $group_members = $self->_JoinGroupMembers( %args );
foreach my $groupid (@{$args{'Groups'}}) {
# we must disconnect DB before fork
if ($RT::Handle) {
+ $RT::Handle->dbh->disconnect if $RT::Handle->dbh;
$RT::Handle->dbh(undef);
undef $RT::Handle;
}
# we must disconnect DB before fork
if ($RT::Handle) {
+ $RT::Handle->dbh->disconnect if $RT::Handle->dbh;
$RT::Handle->dbh(undef);
undef $RT::Handle;
}
default => $DEFAULT{'table'},
silent => !$OPT{'ask'},
);
- my $url = $OPT{'url'} || prompt(
+
+ my $url = 'sphinx://localhost:3312/rt';
+ my $version = ($dbh->selectrow_array("show variables like 'version'"))[1];
+ $url = 'sphinx://127.0.0.1:3312/rt'
+ if $version and $version =~ /^(\d+\.\d+)/ and $1 >= 5.5;
+
+ $url = $OPT{'url'} || prompt(
message => "Enter URL of the sphinx search server; this should be of the form\n"
. "sphinx://<server>:<port>/<index name>",
- default => 'sphinx://localhost:3312/rt',
+ default => $url,
silent => !$OPT{'ask'},
);
my $maxmatches = $OPT{'maxmatches'} || prompt(
use warnings;
no warnings qw(numeric redefine);
use Getopt::Long;
+use Cwd qw(abs_path);
my %args;
my %deps;
my @orig_argv = @ARGV;
+# Save our path because installers or tests can change cwd
+my $script_path = abs_path($0);
+
GetOptions(
\%args, 'v|verbose',
'install!', 'with-MYSQL',
}
if ( $args{'install'} && keys %Missing_By_Type ) {
- exec($0, @orig_argv, '--no-install');
+ exec($script_path, @orig_argv, '--no-install');
}
else {
conclude(%Missing_By_Type);
bind_values => [ $type ],
action => sub {
my $id = shift;
- return unless my $a = prompt_action( ['Delete', 'create'], $msg );
+ return unless my $a = prompt_action( ['Create', 'delete'], $msg );
if ( $a eq 'd' ) {
delete_record( $table, $id );
my $token = shift || join ':', caller;
return '' unless $opt{'resolve'};
- return '' if $opt{'force'};
+ return lc substr $actions->[0], 0, 1 if $opt{'force'};
return $cached_answer{ $token } if exists $cached_answer{ $token };
print $msg, "\n";
# we must disconnect DB before fork
if ($RT::Handle) {
+ $RT::Handle->dbh->disconnect if $RT::Handle->dbh;
$RT::Handle->dbh(undef);
undef $RT::Handle;
}
);
my @panes;
-for my $pane (keys %pane_name) {
+for my $pane (sort keys %pane_name) {
my $sel = $m->comp(
'/Widgets/SelectionBox:new',
Action => 'Queries.html',
while ($Format =~ /($RE{delimited}{-delim=>qq{\'"}}|[{}\w.]+)/go) {
my $col = $1;
+ my $colref = { original_string => $col };
if ($col =~ /^$RE{quoted}$/o) {
substr($col,0,1) = "";
$col =~ s/\\(.)/$1/g;
}
- my $colref = { };
-
$m->callback(CallbackName => 'PreColumn', Column => $colref, col => \$col);
while ( $col =~ s{/(STYLE|CLASS|TITLE|ALIGN|SPAN|ATTRIBUTE):([^/]*)}{}i ) {
%#
%# END BPS TAGGED BLOCK }}}
% if ( $Multiple ) {
-<textarea cols="<% $Cols %>" rows="<% $Rows %>" name="<% $name %>-Values" id="<% $name %>-Values" class="CF-<%$CustomField->id%>-Edit"><% $Default || '' %></textarea>
+<textarea \
+% if ( defined $Cols ) {
+cols="<% $Cols %>" \
+% }
+% if ( defined $Rows ) {
+rows="<% $Rows %>" \
+% }
+name="<% $name %>-Values" id="<% $name %>-Values" class="CF-<%$CustomField->id%>-Edit"><% $Default || '' %></textarea>
<script type="text/javascript">
var id = <% "$name-Values" |n,j%>;
<%INIT>
my $DateObj = RT::Date->new ( $session{'CurrentUser'} );
-$DateObj->Set( Format => 'ISO', Value => $Default );
+$DateObj->Set( Format => $Format, Value => $Default );
</%INIT>
<%ARGS>
$Object => undef
$Default => undef
$Values => undef
$MaxValues => 1
+$Format => 'ISO'
</%ARGS>
%# END BPS TAGGED BLOCK }}}
% my $name = $NamePrefix . $CustomField->Id . '-Value';
% if ($Multiple) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$name%>s" id="<%$name%>s" wrap="off" class="CF-<%$CustomField->id%>-Edit"><% defined($Default) ? $Default : '' %></textarea>
+<textarea \
+% if ( defined $Cols ) {
+cols="<% $Cols %>" \
+% }
+% if ( defined $Rows ) {
+rows="<% $Rows %>" \
+% }
+name="<%$name%>s" id="<%$name%>s" wrap="off" class="CF-<%$CustomField->id%>-Edit"><% defined($Default) ? $Default : '' %></textarea>
% } else {
-<input name="<%$name%>" id="<%$name%>" size="<%$Cols%>" class="CF-<%$CustomField->id%>-Edit" value="<% defined($Default) ? $Default : ''%>" />
+<input type="text" name="<%$name%>" id="<%$name%>" \
+% if ( defined $Cols ) {
+size="<% $Cols %>" \
+% }
+class="CF-<%$CustomField->id%>-Edit" value="<% defined($Default) ? $Default : ''%>" />
% }
<%INIT>
if ( $Multiple and $Values ) {
% if ( $RenderType eq 'List' ) {
<fieldset class="cfedit">
-<div name="<%$id%>-Values" id="<%$id%>-Values">
+<div data-name="<%$id%>-Values" id="<%$id%>-Values">
% if ( $checktype eq 'radio' ) {
<div class="none">
<input class="none" type="<% $checktype %>" name="<% $name %>" id="<% $name %>-none" value="" <% keys %default ? '' : ' checked="checked"' |n%> />
% while ( my $value = $CFVs->Next ) {
% my $content = $value->Name;
% my $labelid = "$name-". $value->id;
-<div name="<% $value->Category %>">
+<div data-name="<% $value->Category || '' %>">
<input type="<% $checktype %>" name="<% $name %>" id="<% $labelid %>" value="<% $content %>" <% $default{ lc $content }? ' checked="checked"' : '' |n%> />
<label for="<% $labelid %>"><% $content %></label><br />
</div>
%#
%# END BPS TAGGED BLOCK }}}
% while ($Values and my $value = $Values->Next ) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $value->Content %></textarea><br />
+<textarea \
+% if ( defined $Cols ) {
+cols="<% $Cols %>" \
+% }
+% if ( defined $Rows ) {
+rows="<% $Rows %>" \
+% }
+name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $value->Content %></textarea><br />
% }
% if (!$MaxValues or !$Values or $Values->Count < $MaxValues) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% defined($Default) ? $Default : '' %></textarea>
+<textarea \
+% if ( defined $Cols ) {
+cols="<% $Cols %>" \
+% }
+% if ( defined $Rows ) {
+rows="<% $Rows %>" \
+% }
+name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% defined($Default) ? $Default : '' %></textarea>
% }
<%INIT>
# XXX - MultiValue textarea is for now outlawed.
%#
%# END BPS TAGGED BLOCK }}}
% while ($Values and my $value = $Values->Next ) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $value->Content %></textarea><br />
+<textarea \
+% if ( defined $Cols ) {
+cols="<% $Cols %>" \
+% }
+% if ( defined $Rows ) {
+rows="<% $Rows %>" \
+% }
+name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $value->Content %></textarea><br />
% }
% if (!$MaxValues or !$Values or $Values->Count < $MaxValues) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $Default %></textarea>
+<textarea \
+% if ( defined $Cols ) {
+cols="<% $Cols %>" \
+% }
+% if ( defined $Rows ) {
+rows="<% $Rows %>" \
+% }
+name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $Default %></textarea>
% }
<%INIT>
# XXX - MultiValue textarea is for now outlawed.
}
};
+my $cache; # only defined via callback
+
# Hook to add more Clicky types
# XXX Have to have Page argument, as Mason gets caller wrong in Callback?
# This happens as we are in <%ONCE> block
types => \@types,
actions => \%actions,
handle => \$handle,
+ cache => \$cache,
);
</%ARGS>
<%INIT>
return unless defined $$content;
+if ( defined $cache ) {
+ my $cached_content = $cache->(fetch => $content);
+ if ( $cached_content ) {
+ RT->Logger->debug("Found MakeClicky cache");
+ $$content = $cached_content;
+ return;
+ }
+}
+
unless ( $regexp ) {
RT::Interface::Web::EscapeUTF8( $content ) unless $html;
return;
($pos == length $$content) || $html;
pos($$content) = 0;
+$cache->(store => $content) if defined $cache;
</%INIT>
-<%doc>
-
-MakeClicky detects various formats of data in headers and email
-messages, and extends them with supporting links. By default, RT
-provides two formats:
-
- * 'httpurl': detects http:// and https:// URLs and adds '[Open URL]'
- link after the URL.
-
- * 'httpurl_overwrite': also detects URLs as 'httpurl' format, but
- replace URL with link.
-
-To extend this with your own types of data, use the callback.
-It will be provided with:
-
- * 'types': An array reference of hash references. Modify this array
- reference to add your own types; the first matching type will be
- used. Each hashref should contain:
- - 'name': The name of the data format; this is used in the
- configuration file to enable the format.
- - 'regex': A regular expression to match against
- - 'action': The name of the action to run (see "actions", below)
-
- * 'actions': A hash reference of 'actions'. Modify this hash
- reference to change or add action types. Values are subroutine
- references which will get called when needed. They should return
- the modified string. Note that subroutine must escape HTML.
-
- * 'handler': A reference to a subroutine reference; modify it if you
- have to. This can be used to add pre- or post-processing around
- all actions.
-
-Read more about writing new actions in docs/extending/clickable_links.pod
-
-</%doc>
my %allowed_components = map {$_ => 1} @{RT->Config->Get('HomepageComponents')};
my $user = $session{'CurrentUser'}->UserObj;
-$Portlets ||= $session{'my_rt_portlets'};
+$Portlets ||= $user->Preferences('HomepageSettings');
unless ( $Portlets ) {
- my ($default_portlets) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
- $Portlets = $session{'my_rt_portlets'} = $user->Preferences(
- HomepageSettings => $default_portlets? $default_portlets->Content: {},
- );
+ my ($defaults) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
+ $Portlets = $defaults ? $defaults->Content : {};
}
$m->callback( CallbackName => 'MassagePortlets', Portlets => $Portlets );
% if ($URI->IsLocal) {
% my $member = $URI->Object;
% my $has_name = UNIVERSAL::can($member, 'Name') || (UNIVERSAL::can($member, '_Accessible') && $member->_Accessible('Name', 'read'));
-% if (UNIVERSAL::isa($member, "RT::Ticket")) {
+% if (UNIVERSAL::isa($member, "RT::Ticket") and $member->CurrentUserHasRight('ShowTicket')) {
% my $inactive = $member->QueueObj->IsInactiveStatus($member->Status);
<span class="<% $inactive ? 'ticket-inactive' : '' %>">
%#
%# END BPS TAGGED BLOCK }}}
<form action="<% RT->Config->Get('WebPath') %><% $SendTo %>" id="simple-search">
- <input size="12" name="q" autocomplete="off" accesskey="0" class="field" value="<&|/l&>Search</&>..." onfocus="if (this.value=='<&|/l&>Search</&>...') this.value=''" />
+ <input size="12" name="q" autocomplete="off" accesskey="0" class="field" value="<% $Placeholder %>..." onfocus="if (this.value==(<% $Placeholder, |n,j %>+'...')) this.value=''" />
</form>
<%ARGS>
$SendTo => '/Search/Simple.html'
+$Placeholder => loc('Search')
</%ARGS>
);
my $sth = $dbh->prepare('select * from Users');
+ $sth->execute();
};
unless ( $@ ) {
}
else {
jQuery(element).find('div').hide().find('input').attr('disabled', 'disabled');
- jQuery(element).find('div[name=]').show().find('input').attr('disabled', '');
+ jQuery(element).find('div[data-name=]').show().find('input').attr('disabled', '');
jQuery(element).find('div.none').show().find('input').attr('disabled','');
for ( var j = 0; j < vals.length; j++ ) {
- jQuery(element).find('div[name^=' + vals[j] + ']').show().find('input').attr('disabled', '');
+ jQuery(element).find('div[data-name^=' + vals[j] + ']').show().find('input').attr('disabled', '');
}
}
}
$ARGS{'SummaryRows'} ||= $user->Preferences('SummaryRows', RT->Config->Get('DefaultSummaryRows'));
if ($ARGS{Reset}) {
- my ($ok, $msg) = $user->SetPreferences('HomepageSettings', {});
- push @results, $ok ? loc('Preferences saved.') : $msg;
- delete $session{'my_rt_portlets'};
+ for my $pref_name ('HomepageSettings', 'SummaryRows') {
+ next unless $user->Preferences($pref_name);
+ my ($ok, $msg) = $user->DeletePreferences($pref_name);
+ push @results, $msg unless $ok;
+ }
+ push @results, loc('Preferences saved.') unless @results;
}
-unless (exists $session{'my_rt_portlets'}) {
- my ($default_portlets) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
- my $portlets = $default_portlets ? $default_portlets->Content : {};
-
- $session{'my_rt_portlets'} = $user->Preferences('HomepageSettings', $portlets);
+my $portlets = $user->Preferences('HomepageSettings');
+unless ($portlets) {
+ my ($defaults) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
+ $portlets = $defaults ? $defaults->Content : {};
}
-my $portlets = $session{'my_rt_portlets'};
my %seen;
my @items = map ["component-$_", loc($_)], grep !$seen{$_}++, @{RT->Config->Get('HomepageComponents')};
my ( $conf, $pane ) = @_;
my ($ok, $msg) = $user->SetPreferences( 'HomepageSettings', $conf );
push @results, $ok ? loc('Preferences saved for [_1].', $pane) : $msg;
- delete $session{'my_rt_portlets'};
}
);
</form>
+<&|/Widgets/TitleBox, title => loc("Reset") &>
+<form method="post" name="ResetSearchOptions" action="Search.html">
+<input type="hidden" name="Reset" value="1" />
+<input type="hidden" name="name" value="<%$ARGS{name}%>" class="hidden" />
+<input type="submit" class="button" name="ResetSearchOptions" value="<% loc('Reset to default') %>">
+</form>
+</&>
+
<%INIT>
my @actions;
my $title = loc("Customize").' ';
my $search = $class->new ($session{'CurrentUser'});
$search->LoadById ($id);
+
+# If we are resetting prefs, do so before attempting to load them
+if ($ARGS{'Reset'}) {
+ my ($ok, $msg) = $session{'CurrentUser'}->UserObj->DeletePreferences($ARGS{name});
+ push @actions, $ok ? loc('Preferences reset.') : $msg;
+}
+
$title .= loc (RT::SavedSearch->EscapeDescription($search->Description), loc ('"N"'));
my $user = $session{'CurrentUser'}->UserObj;
my $SearchArg = $user->Preferences($search, $search->Content);
% } elsif ($cf->Type eq 'Text') {
<td><& /Elements/EditCustomFieldText, @add &></td>
<td> </td>
+% } elsif ($cf->Type eq 'Date') {
+<td><& /Elements/EditCustomFieldDate, @add, Default => undef &></td>
+<td><& /Elements/EditCustomFieldDate, @del, Default => undef &></td>
+% } elsif ($cf->Type eq 'DateTime') {
+% # Pass datemanip format to prevent another tz date conversion
+<td><& /Elements/EditCustomFieldDateTime, @add, Default => undef, Format => 'datemanip' &></td>
+<td><& /Elements/EditCustomFieldDateTime, @del, Default => undef, Format => 'datemanip' &></td>
% } else {
% $RT::Logger->crit("Unknown CustomField type: " . $cf->Type);
% }
unless ( $cf->SingleValue );
my $current_values = $Ticket->CustomFieldValues($cfid);
+
+ if ( $cf->Type eq 'DateTime' || $cf->Type eq 'Date' ){
+ # Clear out empty string submissions to avoid
+ # Not set changed to Not set
+ @values = grep length, @values;
+ }
+
foreach my $value (@values) {
+
+ # Convert for timezone. Without converstion,
+ # HasEntry and DeleteCustomFieldValue fail because
+ # the value in the DB is converted.
+ if ( $op eq 'del'
+ && ($cf->Type eq 'DateTime' || $cf->Type eq 'Date') ){
+ my $DateObj = RT::Date->new( $session{'CurrentUser'} );
+ $DateObj->Set( Format => 'unknown',
+ Value => $value );
+ $value = $cf->Type eq 'DateTime' ? $DateObj->ISO
+ : $DateObj->ISO(Time => 0, Seconds => 0);
+ }
+
if ( $op eq 'del' && $current_values->HasEntry($value) ) {
my ( $id, $msg ) = $Ticket->DeleteCustomFieldValue(
Field => $cfid,
# Cleanup WebUI
delete $session{'Attachments'};
+
+ $Tickets->RedoSearch();
}
my $TxnCFs = RT::CustomFields->new( $session{CurrentUser} );
<%args>
$Query => "id > 0"
$PrimaryGroupBy => 'Queue'
-$ChartStyle => 'bars'
+$ChartStyle => 'bar'
</%args>
<%init>
my $chart_class;
%# END BPS TAGGED BLOCK }}}
<%args>
$PrimaryGroupBy => 'Queue'
-$ChartStyle => 'bars'
+$ChartStyle => 'bar'
$Description => undef
</%args>
<%init>
)
); # loc_qw
+# This callback will only run once and will be removed in 4.4
+# If you want to add a new item to @fields, use the Default callback below.
$m->callback( CallbackOnce => 1, CallbackName => 'SetFieldsOnce', Fields => \@fields );
my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
foreach my $field (@seen) {
next unless $field;
my $row = "";
- if ( $field->{'output'} ) {
- $row = join '', @{$field->{'output'}};
+ if ( $field->{'original_string'} ) {
+ $row = $field->{'original_string'};
}
else {
$row .= $field->{'Prefix'} if defined $field->{'Prefix'};
$row .= "__$field->{'Column'}__"
unless ( $field->{'Column'} eq "<blank>" );
$row .= $field->{'Suffix'} if defined $field->{'Suffix'};
+ $row =~ s!([\\'])!\\$1!g;
+ $row = "'$row'";
}
- $row =~ s!([\\'])!\\$1!g;
- $row = "'$row'";
push( @format_string, $row );
}
<%args>
$Query => "id > 0"
$PrimaryGroupBy => 'Queue'
-$ChartStyle => 'bars'
+$ChartStyle => 'bar'
</%args>
<%init>
use RT::Report::Tickets;
}
$Page = 1 unless $Page && $Page > 0;
-my ($title, $ticketcount);
$session{'i'}++;
$session{'tickets'} = RT::Tickets->new($session{'CurrentUser'}) ;
my ($ok, $msg) = $Query ? $session{'tickets'}->FromSQL($Query) : (1, "Vacuously OK");
};
+my ($title, $ticketcount) = (loc("Found tickets"), 0);
if ( $session{'tickets'}->Query()) {
$ticketcount = $session{tickets}->CountAll();
$title = loc('Found [quant,_1,ticket]', $ticketcount);
-} else {
- $title = loc("Find tickets");
}
my $QueryString = "?".$m->comp('/Elements/QueryString',
use warnings;
use strict;
-use RT::Test tests => 173;
+use RT::Test tests => 175;
use RT::User;
use Test::Warn;
$date->Unix(0);
$date->AddDays(31);
is($date->ISO, '1970-02-01 00:00:00', "added one month");
+
+ $date->Unix(0);
+ $date->AddDays(0);
+ is($date->ISO, '1970-01-01 00:00:00', "added no days");
+
+ $date->Unix(0);
+ $date->AddDays();
+ is($date->ISO, '1970-01-02 00:00:00', "added one day with no argument");
}
{
use RT::Test;
use Digest::MD5;
+use Encode 'encode_utf8';
+use utf8;
my $default = "sha512";
$root->_Set( Field => "Password", Value => $trunc);
ok($root->IsPassword("secret"), "Unsalted MD5 base64 works");
like($root->__Value("Password"), qr/^\!$default\!/, "And is now upgraded to salted $default");
+
+# Non-ASCII salted truncated SHA-256
+my $non_ascii_trunc = MIME::Base64::encode_base64(
+ "salt" . substr(Digest::SHA::sha256("salt".Digest::MD5::md5(encode_utf8("áěšý"))),0,26),
+ ""
+);
+$root->_Set( Field => "Password", Value => $non_ascii_trunc);
+ok($root->IsPassword("áěšý"), "Unsalted MD5 base64 works");
+like($root->__Value("Password"), qr/^\!$default\!/, "And is now upgraded to salted $default");
command => $RT::SbinPath .'/rt-setup-fulltext-index',
dba => $ENV{'RT_DBA_USER'},
'dba-password' => $ENV{'RT_DBA_PASSWORD'},
- url => "sphinx://localhost:$port/rt",
+ url => "sphinx://127.0.0.1:$port/rt",
);
ok(!$exit_code, "setted up index");
diag "output: $output" if $ENV{'TEST_VERBOSE'};
use Test::More;
eval "use Test::Pod 1.14";
plan skip_all => "Test::Pod 1.14 required for testing POD" if $@;
-all_pod_files_ok();
+all_pod_files_ok( all_pod_files("lib","docs","etc","bin","sbin"));
use strict;
use warnings;
-use RT::Test tests => 60;
-
-sub load_or_create_group {
- my $name = shift;
- my %args = (@_);
-
- my $group = RT::Group->new( RT->SystemUser );
- $group->LoadUserDefinedGroup( $name );
- unless ( $group->id ) {
- my ($id, $msg) = $group->CreateUserDefinedGroup(
- Name => $name,
- );
- die "$msg" unless $id;
- }
-
- if ( $args{Members} ) {
- my $cur = $group->MembersObj;
- while ( my $entry = $cur->Next ) {
- my ($status, $msg) = $entry->Delete;
- die "$msg" unless $status;
- }
-
- foreach my $new ( @{ $args{Members} } ) {
- my ($status, $msg) = $group->AddMember(
- ref($new)? $new->id : $new,
- );
- die "$msg" unless $status;
- }
- }
-
- return $group;
-}
-
-my $validator_path = "$RT::SbinPath/rt-validator";
-sub run_validator {
- my %args = (check => 1, resolve => 0, force => 1, @_ );
-
- my $cmd = $validator_path;
- die "Couldn't find $cmd command" unless -f $cmd;
-
- while( my ($k,$v) = each %args ) {
- next unless $v;
- $cmd .= " --$k '$v'";
- }
- $cmd .= ' 2>&1';
-
- require IPC::Open2;
- my ($child_out, $child_in);
- my $pid = IPC::Open2::open2($child_out, $child_in, $cmd);
- close $child_in;
-
- my $result = do { local $/; <$child_out> };
- close $child_out;
- waitpid $pid, 0;
-
- DBIx::SearchBuilder::Record::Cachable->FlushCache
- if $args{'resolve'};
-
- return ($?, $result);
-}
+use RT::Test tests => 63;
{
- my ($ecode, $res) = run_validator();
+ my ($ecode, $res) = RT::Test->run_validator();
is $res, '', 'empty result';
}
{
- my $group = load_or_create_group('test', Members => [] );
+ my $group = RT::Test->load_or_create_group('test', Members => [] );
ok $group, "loaded or created a group";
- my ($ecode, $res) = run_validator();
+ my ($ecode, $res) = RT::Test->run_validator();
is $res, '', 'empty result';
}
# G1 -> G2
{
- my $group1 = load_or_create_group( 'test1', Members => [] );
+ my $group1 = RT::Test->load_or_create_group( 'test1', Members => [] );
ok $group1, "loaded or created a group";
- my $group2 = load_or_create_group( 'test2', Members => [ $group1 ]);
+ my $group2 = RT::Test->load_or_create_group( 'test2', Members => [ $group1 ]);
ok $group2, "loaded or created a group";
ok $group2->HasMember( $group1->id ), "has member";
ok $group2->HasMemberRecursively( $group1->id ), "has member";
- my ($ecode, $res) = run_validator();
+ my ($ecode, $res) = RT::Test->run_validator();
is $res, '', 'empty result';
$RT::Handle->dbh->do("DELETE FROM CachedGroupMembers");
DBIx::SearchBuilder::Record::Cachable->FlushCache;
ok !$group2->HasMemberRecursively( $group1->id ), "has no member, broken DB";
- ($ecode, $res) = run_validator(resolve => 1);
+ ($ecode, $res) = RT::Test->run_validator(resolve => 1);
ok $group2->HasMember( $group1->id ), "has member";
ok $group2->HasMemberRecursively( $group1->id ), "has member";
- ($ecode, $res) = run_validator();
+ ($ecode, $res) = RT::Test->run_validator();
is $res, '', 'empty result';
}
for (1..5) {
my $child = @groups? $groups[-1]: undef;
- my $group = load_or_create_group( 'test'. $_, Members => [ $child? ($child): () ] );
+ my $group = RT::Test->load_or_create_group( 'test'. $_, Members => [ $child? ($child): () ] );
ok $group, "loaded or created a group";
ok $group->HasMember( $child->id ), "has member"
push @groups, $group;
}
- my ($ecode, $res) = run_validator();
+ my ($ecode, $res) = RT::Test->run_validator();
is $res, '', 'empty result';
$RT::Handle->dbh->do("DELETE FROM CachedGroupMembers");
ok !$groups[1]->HasMemberRecursively( $groups[0]->id ), "has no member, broken DB";
- ($ecode, $res) = run_validator(resolve => 1);
+ ($ecode, $res) = RT::Test->run_validator(resolve => 1);
for ( my $i = 1; $i < @groups; $i++ ) {
ok $groups[$i]->HasMember( $groups[$i-1]->id ), "has member";
foreach 0..$i-1;
}
- ($ecode, $res) = run_validator();
+ ($ecode, $res) = RT::Test->run_validator();
is $res, '', 'empty result';
}
{
my @groups;
for (2..5) {
- my $group = load_or_create_group( 'test'. $_, Members => [] );
+ my $group = RT::Test->load_or_create_group( 'test'. $_, Members => [] );
ok $group, "loaded or created a group";
push @groups, $group;
}
- my $parent = load_or_create_group( 'test1', Members => \@groups );
+ my $parent = RT::Test->load_or_create_group( 'test1', Members => \@groups );
ok $parent, "loaded or created a group";
- my ($ecode, $res) = run_validator();
+ my ($ecode, $res) = RT::Test->run_validator();
is $res, '', 'empty result';
}
# G1 <- (G2, G3, G4) <- G5
{
- my $gchild = load_or_create_group( 'test5', Members => [] );
+ my $gchild = RT::Test->load_or_create_group( 'test5', Members => [] );
ok $gchild, "loaded or created a group";
my @groups;
for (2..4) {
- my $group = load_or_create_group( 'test'. $_, Members => [ $gchild ] );
+ my $group = RT::Test->load_or_create_group( 'test'. $_, Members => [ $gchild ] );
ok $group, "loaded or created a group";
push @groups, $group;
}
- my $parent = load_or_create_group( 'test1', Members => \@groups );
+ my $parent = RT::Test->load_or_create_group( 'test1', Members => \@groups );
ok $parent, "loaded or created a group";
- my ($ecode, $res) = run_validator();
+ my ($ecode, $res) = RT::Test->run_validator();
is $res, '', 'empty result';
}
+# group without principal record and cgm records
+# was causing infinite loop as principal was not created
+{
+ my $group = RT::Test->load_or_create_group('Test');
+ ok $group && $group->id, 'loaded or created group';
+
+ my $dbh = $group->_Handle->dbh;
+ $dbh->do('DELETE FROM Principals WHERE id = ?', {RaiseError => 1}, $group->id);
+ $dbh->do('DELETE FROM CachedGroupMembers WHERE GroupId = ?', {RaiseError => 1}, $group->id);
+ DBIx::SearchBuilder::Record::Cachable->FlushCache;
+
+ my ($ecode, $res) = RT::Test->run_validator(resolve => 1, timeout => 30);
+ ok $res;
+
+ ($ecode, $res) = RT::Test->run_validator();
+ is $res, '', 'empty result';
+}
use strict;
use warnings;
-use RT::Test tests => 22;
+use RT::Test tests => undef;
my ($baseurl, $agent) = RT::Test->started_ok;
+ok($agent->login);
$agent->get("$baseurl/NoAuth/../Elements/HeaderJavascript");
is($agent->status, 400);
$agent->warning_like(qr/Invalid request.*aborting/,);
};
+# Do not reject a simple /. in the URL, for downloading uploaded
+# dotfiles, for example.
+$agent->get("$baseurl/Ticket/Attachment/28/9/.bashrc");
+is($agent->status, 200); # Even for a file not found, we return 200
+$agent->content_contains("Bad attachment id");
+
# do not reject these URLs, even though they contain /. outside the path
$agent->get("$baseurl/index.html?ignored=%2F%2E");
is($agent->status, 200);
$agent->get("$baseurl/index.html#/.");
is($agent->status, 200);
+undef $agent;
+done_testing;