diff options
55 files changed, 491 insertions, 358 deletions
diff --git a/rt/configure b/rt/configure index 3abb324ba..60d9fb1ea 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.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>. # @@ -560,8 +560,8 @@ MAKEFLAGS= # 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='' @@ -1311,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.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]... @@ -1372,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.19:";; + short | recursive ) echo "Configuration of RT rt-4.0.20:";; esac cat <<\_ACEOF @@ -1496,7 +1496,7 @@ fi 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. @@ -1597,7 +1597,7 @@ 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.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 $@ @@ -1954,7 +1954,7 @@ rt_version_major=4 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 @@ -3923,7 +3923,7 @@ RT_LOG_PATH_R=${exp_logfiledir} 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" @@ -4482,7 +4482,7 @@ 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.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 @@ -4535,7 +4535,7 @@ _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.19 +RT config.status rt-4.0.20 configured by $0, generated by GNU Autoconf 2.68, with options \\"\$ac_cs_config\\" @@ -4647,6 +4647,7 @@ for ac_config_target in $ac_config_targets 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" ;; @@ -5099,6 +5100,8 @@ which seems to be undefined. Please make sure it is defined" >&2;} 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 diff --git a/rt/configure.ac b/rt/configure.ac index 47ec7c954..e9f7d5fd6 100644 --- a/rt/configure.ac +++ b/rt/configure.ac @@ -408,6 +408,7 @@ dnl Configure the output files, and generate them. 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 diff --git a/rt/docs/UPGRADING-4.0 b/rt/docs/UPGRADING-4.0 index 63dd2eecc..766964f42 100644 --- a/rt/docs/UPGRADING-4.0 +++ b/rt/docs/UPGRADING-4.0 @@ -32,6 +32,9 @@ If you deploy RT with mod_perl, Apache will no longer start with C<SetHandler> 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 diff --git a/rt/docs/extending/clickable_links.pod b/rt/docs/extending/clickable_links.pod index 91e9eec22..dd80ff10f 100644 --- a/rt/docs/extending/clickable_links.pod +++ b/rt/docs/extending/clickable_links.pod @@ -89,11 +89,20 @@ add action types. Values are subroutine references which will get 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 diff --git a/rt/etc/RT_Config.pm.in b/rt/etc/RT_Config.pm.in index a52965a9d..dace2d74c 100644 --- a/rt/etc/RT_Config.pm.in +++ b/rt/etc/RT_Config.pm.in @@ -296,8 +296,9 @@ Set(@LogToSyslogConf, ()); =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" diff --git a/rt/etc/upgrade/3.8.9/content b/rt/etc/upgrade/3.8.9/content index 898c19ebf..d7d64f599 100644 --- a/rt/etc/upgrade/3.8.9/content +++ b/rt/etc/upgrade/3.8.9/content @@ -56,6 +56,7 @@ s!(?<=Your ticket has been (?:approved|rejected) by { eval { )\$Approval->OwnerObj->Name!\$Approver->Name! ) { + $template->SetType('Perl'); $template->SetContent($content); } } diff --git a/rt/lib/RT.pm b/rt/lib/RT.pm index 0f0c79a55..e71d6c926 100644 --- a/rt/lib/RT.pm +++ b/rt/lib/RT.pm @@ -707,7 +707,9 @@ sub InitPluginPaths { 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 { diff --git a/rt/lib/RT/Config.pm b/rt/lib/RT/Config.pm index fee6c5106..62aae1c35 100644 --- a/rt/lib/RT/Config.pm +++ b/rt/lib/RT/Config.pm @@ -1219,11 +1219,14 @@ sub SetFromConfig { 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); diff --git a/rt/lib/RT/Date.pm b/rt/lib/RT/Date.pm index db56cfeb9..52bdc01df 100644 --- a/rt/lib/RT/Date.pm +++ b/rt/lib/RT/Date.pm @@ -509,7 +509,8 @@ Returns new unix time. sub AddDays { my $self = shift; - my $days = shift || 1; + my $days = shift; + $days = 1 unless defined $days; return $self->AddSeconds( $days * $DAY ); } diff --git a/rt/lib/RT/Generated.pm b/rt/lib/RT/Generated.pm index 5edd7e3f8..eee892dd9 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.19'; +our $VERSION = '4.0.20'; diff --git a/rt/lib/RT/Handle.pm b/rt/lib/RT/Handle.pm index 4ea1576dc..e6ecdda77 100644 --- a/rt/lib/RT/Handle.pm +++ b/rt/lib/RT/Handle.pm @@ -246,7 +246,7 @@ sub CheckIntegrity { return (0, 'no nobody user', "Couldn't find Nobody user in the DB '". $self->DSN ."'"); } - return $RT::Handle->dbh; + return 1; } sub CheckCompatibility { @@ -768,9 +768,9 @@ sub InsertData { ); # 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 $@; @@ -790,7 +790,9 @@ sub InsertData { $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 ); @@ -829,6 +831,12 @@ sub InsertData { } } } + 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."); } @@ -848,6 +856,33 @@ sub InsertData { } $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) { diff --git a/rt/lib/RT/Interface/REST.pm b/rt/lib/RT/Interface/REST.pm index 17fe44669..06d7f83d2 100644 --- a/rt/lib/RT/Interface/REST.pm +++ b/rt/lib/RT/Interface/REST.pm @@ -328,7 +328,7 @@ sub process_attachments { 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++; diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm index 409cbdc45..59d315431 100644 --- a/rt/lib/RT/Interface/Web.pm +++ b/rt/lib/RT/Interface/Web.pm @@ -962,7 +962,7 @@ not contain a slash-dot C</.>, and does not contain any nulls. sub ComponentPathIsSafe { my $self = shift; my $path = shift; - return $path !~ m{(?:^|/)\.} and $path !~ m{\0}; + return($path !~ m{(?:^|/)\.} and $path !~ m{\0}); } =head2 PathIsSafe diff --git a/rt/lib/RT/Interface/Web/Handler.pm b/rt/lib/RT/Interface/Web/Handler.pm index 37031b18d..07e770724 100644 --- a/rt/lib/RT/Interface/Web/Handler.pm +++ b/rt/lib/RT/Interface/Web/Handler.pm @@ -278,7 +278,7 @@ sub PSGIApp { # 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 }); diff --git a/rt/lib/RT/Lifecycle.pm b/rt/lib/RT/Lifecycle.pm index accef228f..bdb2ba6d8 100644 --- a/rt/lib/RT/Lifecycle.pm +++ b/rt/lib/RT/Lifecycle.pm @@ -298,7 +298,7 @@ sub IsActive { return 0; } -=head3 inactive +=head3 Inactive Returns an array of all inactive statuses for this lifecycle. @@ -309,7 +309,7 @@ sub Inactive { 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. diff --git a/rt/lib/RT/Shredder/Plugin/SQLDump.pm b/rt/lib/RT/Shredder/Plugin/SQLDump.pm index cc0d4cc08..d12cf0b5c 100644 --- a/rt/lib/RT/Shredder/Plugin/SQLDump.pm +++ b/rt/lib/RT/Shredder/Plugin/SQLDump.pm @@ -89,8 +89,8 @@ sub Run 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; diff --git a/rt/lib/RT/StyleGuide.pod b/rt/lib/RT/StyleGuide.pod index d958c87d4..8fdfc7b1e 100644 --- a/rt/lib/RT/StyleGuide.pod +++ b/rt/lib/RT/StyleGuide.pod @@ -2,6 +2,10 @@ 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 @@ -92,21 +96,9 @@ versions. Examples: 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 @@ -152,14 +144,6 @@ local() may also be used on elements of arrays and hashes, though there 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 @@ -185,58 +169,6 @@ Although, usually, this is better (faster, easier to read, etc.): 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 @@ -249,15 +181,17 @@ In all other cases, the method needs to take named parameters, usually 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 @@ -332,17 +266,6 @@ document, too. =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 @@ -435,9 +358,9 @@ clear what is going on, or when it is required (such as with 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. @@ -448,9 +371,9 @@ C<_> for subsequent uses, is a performance hit. You should be 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 @@ -461,20 +384,16 @@ lower case letters (for the most part). Multiple words should be StudlyCapped. 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. @@ -507,15 +426,14 @@ An example: # 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; @@ -555,14 +473,13 @@ the opening statement, or the opening parenthesis, whichever works best. 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 @@ -620,26 +537,16 @@ opening curly on the first line, and the ending curly lined up with the 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 @@ -678,21 +585,21 @@ normally, you should, if there is any question at all -- then it doesn't 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 @@ -722,7 +629,7 @@ When making compound statements, put the primary action first. 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. @@ -754,7 +661,7 @@ grep the codebase for strings to be localized The string Foo Bar Baz - + Should become <&|/l&>Foo Bar Baz</&> @@ -788,9 +695,8 @@ should become <& /Elements/TitleBoxStart, titleright => loc("RT [_1] for [_2]",$RT::VERSION, RT->Config->Get('rtname')), title => loc('Login'), &> - -=item Library code +=item Library code @@ -855,19 +761,21 @@ guide contained in this document. =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 @@ -919,12 +827,3 @@ Talk about mason 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 diff --git a/rt/lib/RT/Template.pm b/rt/lib/RT/Template.pm index d15c1cdcb..050799714 100755 --- a/rt/lib/RT/Template.pm +++ b/rt/lib/RT/Template.pm @@ -470,6 +470,12 @@ sub _ParseContentPerl { 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}, diff --git a/rt/lib/RT/Test.pm b/rt/lib/RT/Test.pm index 2a1f52b90..b15c03d23 100644 --- a/rt/lib/RT/Test.pm +++ b/rt/lib/RT/Test.pm @@ -709,6 +709,39 @@ sub load_or_create_user { 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 @@ -997,6 +1030,43 @@ sub run_mailgate { $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 = @_; diff --git a/rt/lib/RT/User.pm b/rt/lib/RT/User.pm index 018ac8a62..af4a6ad99 100755 --- a/rt/lib/RT/User.pm +++ b/rt/lib/RT/User.pm @@ -957,7 +957,7 @@ sub IsPassword { 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; @@ -1390,6 +1390,28 @@ sub SetPreferences { } } +=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. diff --git a/rt/lib/RT/Users.pm b/rt/lib/RT/Users.pm index 1c75f4250..f377d470c 100755 --- a/rt/lib/RT/Users.pm +++ b/rt/lib/RT/Users.pm @@ -543,21 +543,31 @@ sub WhoHaveGroupRight } -=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'}}) { diff --git a/rt/sbin/rt-server.fcgi.in b/rt/sbin/rt-server.fcgi.in index 0d11f0124..5bd8f3ed2 100644 --- a/rt/sbin/rt-server.fcgi.in +++ b/rt/sbin/rt-server.fcgi.in @@ -138,6 +138,7 @@ EOF # we must disconnect DB before fork if ($RT::Handle) { + $RT::Handle->dbh->disconnect if $RT::Handle->dbh; $RT::Handle->dbh(undef); undef $RT::Handle; } diff --git a/rt/sbin/rt-server.in b/rt/sbin/rt-server.in index 0d11f0124..5bd8f3ed2 100644 --- a/rt/sbin/rt-server.in +++ b/rt/sbin/rt-server.in @@ -138,6 +138,7 @@ EOF # we must disconnect DB before fork if ($RT::Handle) { + $RT::Handle->dbh->disconnect if $RT::Handle->dbh; $RT::Handle->dbh(undef); undef $RT::Handle; } diff --git a/rt/sbin/rt-setup-fulltext-index.in b/rt/sbin/rt-setup-fulltext-index.in index ade728f8f..7a1ede816 100644 --- a/rt/sbin/rt-setup-fulltext-index.in +++ b/rt/sbin/rt-setup-fulltext-index.in @@ -146,10 +146,16 @@ if ( $DB{'type'} eq 'mysql' ) { 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( diff --git a/rt/sbin/rt-test-dependencies.in b/rt/sbin/rt-test-dependencies.in index 868105431..66215ad29 100644 --- a/rt/sbin/rt-test-dependencies.in +++ b/rt/sbin/rt-test-dependencies.in @@ -55,9 +55,13 @@ use strict; 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', @@ -417,7 +421,7 @@ foreach my $type (sort grep $args{$_}, keys %args) { } 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); diff --git a/rt/sbin/rt-validator.in b/rt/sbin/rt-validator.in index 128e60af0..f0f1c59db 100644 --- a/rt/sbin/rt-validator.in +++ b/rt/sbin/rt-validator.in @@ -222,7 +222,7 @@ foreach my $table ( qw(Users Groups) ) { 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 ); @@ -1104,7 +1104,7 @@ sub prompt_action { 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"; diff --git a/rt/sbin/standalone_httpd.in b/rt/sbin/standalone_httpd.in index 0d11f0124..5bd8f3ed2 100644 --- a/rt/sbin/standalone_httpd.in +++ b/rt/sbin/standalone_httpd.in @@ -138,6 +138,7 @@ EOF # we must disconnect DB before fork if ($RT::Handle) { + $RT::Handle->dbh->disconnect if $RT::Handle->dbh; $RT::Handle->dbh(undef); undef $RT::Handle; } diff --git a/rt/share/html/Dashboards/Queries.html b/rt/share/html/Dashboards/Queries.html index 61f6195e5..c489f1c18 100644 --- a/rt/share/html/Dashboards/Queries.html +++ b/rt/share/html/Dashboards/Queries.html @@ -190,7 +190,7 @@ $m->callback( ); 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', diff --git a/rt/share/html/Elements/CollectionAsTable/ParseFormat b/rt/share/html/Elements/CollectionAsTable/ParseFormat index e56e9ce0f..5d55ffbeb 100644 --- a/rt/share/html/Elements/CollectionAsTable/ParseFormat +++ b/rt/share/html/Elements/CollectionAsTable/ParseFormat @@ -55,6 +55,7 @@ my @Columns; 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) = ""; @@ -62,8 +63,6 @@ while ($Format =~ /($RE{delimited}{-delim=>qq{\'"}}|[{}\w.]+)/go) { $col =~ s/\\(.)/$1/g; } - my $colref = { }; - $m->callback(CallbackName => 'PreColumn', Column => $colref, col => \$col); while ( $col =~ s{/(STYLE|CLASS|TITLE|ALIGN|SPAN|ATTRIBUTE):([^/]*)}{}i ) { diff --git a/rt/share/html/Elements/EditCustomFieldAutocomplete b/rt/share/html/Elements/EditCustomFieldAutocomplete index 7bfe91114..8eb7b427a 100644 --- a/rt/share/html/Elements/EditCustomFieldAutocomplete +++ b/rt/share/html/Elements/EditCustomFieldAutocomplete @@ -46,7 +46,14 @@ %# %# 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%>; diff --git a/rt/share/html/Elements/EditCustomFieldDateTime b/rt/share/html/Elements/EditCustomFieldDateTime index 25d1ce160..edf125e80 100644 --- a/rt/share/html/Elements/EditCustomFieldDateTime +++ b/rt/share/html/Elements/EditCustomFieldDateTime @@ -50,7 +50,7 @@ <%INIT> my $DateObj = RT::Date->new ( $session{'CurrentUser'} ); -$DateObj->Set( Format => 'ISO', Value => $Default ); +$DateObj->Set( Format => $Format, Value => $Default ); </%INIT> <%ARGS> $Object => undef @@ -59,4 +59,5 @@ $NamePrefix => undef $Default => undef $Values => undef $MaxValues => 1 +$Format => 'ISO' </%ARGS> diff --git a/rt/share/html/Elements/EditCustomFieldFreeform b/rt/share/html/Elements/EditCustomFieldFreeform index 67248738b..f0f8ee6de 100644 --- a/rt/share/html/Elements/EditCustomFieldFreeform +++ b/rt/share/html/Elements/EditCustomFieldFreeform @@ -47,9 +47,20 @@ %# 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 ) { diff --git a/rt/share/html/Elements/EditCustomFieldSelect b/rt/share/html/Elements/EditCustomFieldSelect index 87400ab2e..b343d8266 100644 --- a/rt/share/html/Elements/EditCustomFieldSelect +++ b/rt/share/html/Elements/EditCustomFieldSelect @@ -104,7 +104,7 @@ jQuery( function () { % 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%> /> @@ -115,7 +115,7 @@ jQuery( function () { % 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> diff --git a/rt/share/html/Elements/EditCustomFieldText b/rt/share/html/Elements/EditCustomFieldText index 866460472..ca7a266c3 100644 --- a/rt/share/html/Elements/EditCustomFieldText +++ b/rt/share/html/Elements/EditCustomFieldText @@ -46,10 +46,24 @@ %# %# 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. diff --git a/rt/share/html/Elements/EditCustomFieldWikitext b/rt/share/html/Elements/EditCustomFieldWikitext index 1a36ae333..d4b79cd38 100644 --- a/rt/share/html/Elements/EditCustomFieldWikitext +++ b/rt/share/html/Elements/EditCustomFieldWikitext @@ -46,10 +46,24 @@ %# %# 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. diff --git a/rt/share/html/Elements/MakeClicky b/rt/share/html/Elements/MakeClicky index e22e75fbb..8efe78c4a 100644 --- a/rt/share/html/Elements/MakeClicky +++ b/rt/share/html/Elements/MakeClicky @@ -96,6 +96,8 @@ my $handle = sub { } }; +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 @@ -104,6 +106,7 @@ $m->callback( types => \@types, actions => \%actions, handle => \$handle, + cache => \$cache, ); @@ -131,6 +134,15 @@ $html => undef </%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; @@ -165,40 +177,6 @@ substr( $$content, $pos ) = $escaper->( substr( $$content, $pos ) ) unless ($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> diff --git a/rt/share/html/Elements/MyRT b/rt/share/html/Elements/MyRT index c59ec1c32..84db949f7 100644 --- a/rt/share/html/Elements/MyRT +++ b/rt/share/html/Elements/MyRT @@ -67,12 +67,10 @@ 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 ); diff --git a/rt/share/html/Elements/ShowLink b/rt/share/html/Elements/ShowLink index cccc3d824..35b26381a 100644 --- a/rt/share/html/Elements/ShowLink +++ b/rt/share/html/Elements/ShowLink @@ -49,7 +49,7 @@ % 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' : '' %>"> diff --git a/rt/share/html/Elements/SimpleSearch b/rt/share/html/Elements/SimpleSearch index bd8a87642..d9f34fa07 100755 --- a/rt/share/html/Elements/SimpleSearch +++ b/rt/share/html/Elements/SimpleSearch @@ -46,8 +46,9 @@ %# %# 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> diff --git a/rt/share/html/Install/DatabaseDetails.html b/rt/share/html/Install/DatabaseDetails.html index 78672db5c..b4d3f8c8a 100644 --- a/rt/share/html/Install/DatabaseDetails.html +++ b/rt/share/html/Install/DatabaseDetails.html @@ -165,6 +165,7 @@ if ( $Run ) { ); my $sth = $dbh->prepare('select * from Users'); + $sth->execute(); }; unless ( $@ ) { diff --git a/rt/share/html/NoAuth/js/cascaded.js b/rt/share/html/NoAuth/js/cascaded.js index b72a33f67..1611fd192 100644 --- a/rt/share/html/NoAuth/js/cascaded.js +++ b/rt/share/html/NoAuth/js/cascaded.js @@ -64,10 +64,10 @@ function filter_cascade (id, vals) { } 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', ''); } } } diff --git a/rt/share/html/Prefs/MyRT.html b/rt/share/html/Prefs/MyRT.html index a595ccf78..288df0b9a 100644 --- a/rt/share/html/Prefs/MyRT.html +++ b/rt/share/html/Prefs/MyRT.html @@ -97,18 +97,19 @@ if ( $ARGS{'UpdateSummaryRows'} ) { $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')}; @@ -157,7 +158,6 @@ my @panes = $m->comp( 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'}; } ); diff --git a/rt/share/html/Prefs/Search.html b/rt/share/html/Prefs/Search.html index bb52a6212..3f2c404cf 100644 --- a/rt/share/html/Prefs/Search.html +++ b/rt/share/html/Prefs/Search.html @@ -68,6 +68,14 @@ </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").' '; @@ -81,6 +89,13 @@ Abort('No search specified') 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); diff --git a/rt/share/html/Search/Bulk.html b/rt/share/html/Search/Bulk.html index a215eac8e..38ca64248 100755 --- a/rt/share/html/Search/Bulk.html +++ b/rt/share/html/Search/Bulk.html @@ -202,6 +202,13 @@ $cfs->LimitToQueue($_) for keys %$seen_queues; % } 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); % } @@ -372,7 +379,27 @@ unless ( $ARGS{'AddMoreAttach'} ) { 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, @@ -411,6 +438,8 @@ unless ( $ARGS{'AddMoreAttach'} ) { # Cleanup WebUI delete $session{'Attachments'}; + + $Tickets->RedoSearch(); } my $TxnCFs = RT::CustomFields->new( $session{CurrentUser} ); diff --git a/rt/share/html/Search/Chart b/rt/share/html/Search/Chart index 4be28abd6..7256106e3 100644 --- a/rt/share/html/Search/Chart +++ b/rt/share/html/Search/Chart @@ -48,7 +48,7 @@ <%args> $Query => "id > 0" $PrimaryGroupBy => 'Queue' -$ChartStyle => 'bars' +$ChartStyle => 'bar' </%args> <%init> my $chart_class; diff --git a/rt/share/html/Search/Chart.html b/rt/share/html/Search/Chart.html index 952f09cf4..ab25745f8 100644 --- a/rt/share/html/Search/Chart.html +++ b/rt/share/html/Search/Chart.html @@ -47,7 +47,7 @@ %# END BPS TAGGED BLOCK }}} <%args> $PrimaryGroupBy => 'Queue' -$ChartStyle => 'bars' +$ChartStyle => 'bar' $Description => undef </%args> <%init> diff --git a/rt/share/html/Search/Elements/BuildFormatString b/rt/share/html/Search/Elements/BuildFormatString index 14e3a7105..66fd14763 100644 --- a/rt/share/html/Search/Elements/BuildFormatString +++ b/rt/share/html/Search/Elements/BuildFormatString @@ -109,6 +109,8 @@ my @fields = ( ) ); # 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'}); @@ -227,17 +229,17 @@ my @format_string; 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 ); } diff --git a/rt/share/html/Search/Elements/Chart b/rt/share/html/Search/Elements/Chart index 05a0422b1..f0d1e4a0f 100644 --- a/rt/share/html/Search/Elements/Chart +++ b/rt/share/html/Search/Elements/Chart @@ -48,7 +48,7 @@ <%args> $Query => "id > 0" $PrimaryGroupBy => 'Queue' -$ChartStyle => 'bars' +$ChartStyle => 'bar' </%args> <%init> use RT::Report::Tickets; diff --git a/rt/share/html/Search/Results.html b/rt/share/html/Search/Results.html index 601786f10..3c3187c1f 100755 --- a/rt/share/html/Search/Results.html +++ b/rt/share/html/Search/Results.html @@ -111,7 +111,6 @@ if ( !defined($Rows) ) { } $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"); @@ -141,11 +140,10 @@ $session{'CurrentSearchHash'} = { }; +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', diff --git a/rt/t/api/date.t b/rt/t/api/date.t index cc1c694cc..22c6f1b58 100644 --- a/rt/t/api/date.t +++ b/rt/t/api/date.t @@ -4,7 +4,7 @@ use DateTime; use warnings; use strict; -use RT::Test tests => 173; +use RT::Test tests => 175; use RT::User; use Test::Warn; @@ -440,6 +440,14 @@ my $year = (localtime(time))[5] + 1900; $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"); } { diff --git a/rt/t/api/password-types.t b/rt/t/api/password-types.t index e5155e35b..10a874a3d 100644 --- a/rt/t/api/password-types.t +++ b/rt/t/api/password-types.t @@ -3,6 +3,8 @@ use warnings; use RT::Test; use Digest::MD5; +use Encode 'encode_utf8'; +use utf8; my $default = "sha512"; @@ -38,3 +40,12 @@ my $trunc = MIME::Base64::encode_base64( $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"); diff --git a/rt/t/fts/indexed_mysql.t b/rt/t/fts/indexed_mysql.t index a54382ff8..0a4f02626 100644 --- a/rt/t/fts/indexed_mysql.t +++ b/rt/t/fts/indexed_mysql.t @@ -32,7 +32,7 @@ sub setup_indexing { 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'}; diff --git a/rt/t/pod.t b/rt/t/pod.t index d11a497eb..697a30b44 100644 --- a/rt/t/pod.t +++ b/rt/t/pod.t @@ -4,4 +4,4 @@ use warnings; 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")); diff --git a/rt/t/validator/group_members.t b/rt/t/validator/group_members.t index fbe758017..af93c518e 100644 --- a/rt/t/validator/group_members.t +++ b/rt/t/validator/group_members.t @@ -2,104 +2,45 @@ 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'; } @@ -109,7 +50,7 @@ sub run_validator { 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" @@ -120,7 +61,7 @@ sub run_validator { 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"); @@ -128,7 +69,7 @@ sub run_validator { 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"; @@ -136,7 +77,7 @@ sub run_validator { foreach 0..$i-1; } - ($ecode, $res) = run_validator(); + ($ecode, $res) = RT::Test->run_validator(); is $res, '', 'empty result'; } @@ -144,34 +85,51 @@ sub run_validator { { 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'; +} diff --git a/rt/t/web/path-traversal.t b/rt/t/web/path-traversal.t index 5d5c954a1..01302e672 100644 --- a/rt/t/web/path-traversal.t +++ b/rt/t/web/path-traversal.t @@ -1,9 +1,10 @@ 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); @@ -31,6 +32,12 @@ SKIP: { $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); @@ -44,3 +51,5 @@ is($agent->status, 200); $agent->get("$baseurl/index.html#/."); is($agent->status, 200); +undef $agent; +done_testing; |