rt 4.0.6
authorIvan Kohler <ivan@freeside.biz>
Thu, 7 Jun 2012 23:55:45 +0000 (16:55 -0700)
committerIvan Kohler <ivan@freeside.biz>
Thu, 7 Jun 2012 23:55:45 +0000 (16:55 -0700)
172 files changed:
FS/FS/Mason.pm
rt/Makefile.in
rt/bin/rt
rt/bin/rt-mailgate.in
rt/bin/rt.in
rt/configure
rt/docs/UPGRADING-4.0
rt/docs/hacking.pod
rt/docs/security.pod
rt/docs/web_deployment.pod
rt/etc/RT_Config.pm.in
rt/etc/schema.mysql
rt/etc/upgrade/vulnerable-passwords.in
rt/lib/RT/ACL.pm
rt/lib/RT/Action/CreateTickets.pm
rt/lib/RT/Action/SendEmail.pm
rt/lib/RT/Article.pm
rt/lib/RT/Attachments.pm
rt/lib/RT/Class.pm
rt/lib/RT/Config.pm
rt/lib/RT/CustomField.pm
rt/lib/RT/Dashboard/Mailer.pm
rt/lib/RT/Date.pm
rt/lib/RT/Generated.pm
rt/lib/RT/Graph/Tickets.pm
rt/lib/RT/Group.pm
rt/lib/RT/Groups.pm
rt/lib/RT/Handle.pm
rt/lib/RT/I18N.pm
rt/lib/RT/Installer.pm
rt/lib/RT/Interface/Email.pm
rt/lib/RT/Interface/Web.pm
rt/lib/RT/Interface/Web/Handler.pm
rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm
rt/lib/RT/Lifecycle.pm
rt/lib/RT/Links.pm
rt/lib/RT/ObjectCustomField.pm
rt/lib/RT/ObjectCustomFieldValue.pm
rt/lib/RT/Queue.pm
rt/lib/RT/Reminders.pm
rt/lib/RT/Report/Tickets.pm
rt/lib/RT/Report/Tickets/Entry.pm
rt/lib/RT/Scrip.pm
rt/lib/RT/SearchBuilder.pm
rt/lib/RT/Shredder.pm
rt/lib/RT/Shredder/Plugin.pm
rt/lib/RT/Shredder/Queue.pm
rt/lib/RT/Template.pm
rt/lib/RT/Test.pm
rt/lib/RT/Test/Web.pm
rt/lib/RT/Ticket.pm
rt/lib/RT/Tickets.pm
rt/lib/RT/Transaction.pm
rt/lib/RT/URI.pm
rt/lib/RT/URI/fsck_com_article.pm
rt/lib/RT/User.pm
rt/lib/RT/Users.pm
rt/sbin/rt-server.fcgi.in
rt/sbin/rt-server.in
rt/sbin/rt-shredder.in
rt/sbin/rt-test-dependencies.in
rt/sbin/standalone_httpd
rt/sbin/standalone_httpd.in
rt/share/html/Admin/Articles/Elements/Topics
rt/share/html/Admin/CustomFields/Modify.html
rt/share/html/Admin/Elements/EditCustomFields
rt/share/html/Admin/Elements/EditRights
rt/share/html/Admin/Elements/Portal
rt/share/html/Admin/Elements/SelectNewGroupMembers
rt/share/html/Admin/Groups/index.html
rt/share/html/Admin/Tools/Queries.html
rt/share/html/Admin/Tools/Shredder/Dumps/dhandler
rt/share/html/Admin/Tools/Shredder/Elements/Error/NoStorage
rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Attachment
rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Ticket
rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--User
rt/share/html/Admin/Users/Modify.html
rt/share/html/Admin/Users/index.html
rt/share/html/Approvals/Elements/PendingMyApproval
rt/share/html/Articles/Article/Edit.html
rt/share/html/Articles/Article/Elements/EditTopics
rt/share/html/Articles/Article/ExtractIntoClass.html
rt/share/html/Articles/Topics.html
rt/share/html/Elements/CollectionAsTable/Header
rt/share/html/Elements/CollectionList
rt/share/html/Elements/CollectionListPaging
rt/share/html/Elements/ColumnMap
rt/share/html/Elements/CreateTicket
rt/share/html/Elements/EditCustomField
rt/share/html/Elements/EditCustomFieldAutocomplete
rt/share/html/Elements/EditCustomFieldSelect
rt/share/html/Elements/Error
rt/share/html/Elements/Header
rt/share/html/Elements/HeaderJavascript
rt/share/html/Elements/MessageBox
rt/share/html/Elements/RT__CustomField/ColumnMap
rt/share/html/Elements/RT__Dashboard/ColumnMap
rt/share/html/Elements/RT__Queue/ColumnMap
rt/share/html/Elements/SelectOwner
rt/share/html/Elements/SelectOwnerAutocomplete
rt/share/html/Elements/SelectStatus
rt/share/html/Elements/ShowCustomFields
rt/share/html/Elements/ShowLink
rt/share/html/Elements/ShowSearch
rt/share/html/Elements/ShowUser
rt/share/html/Elements/Submit
rt/share/html/Elements/Tabs
rt/share/html/Helpers/Autocomplete/CustomFieldValues
rt/share/html/Helpers/Toggle/ShowRequestor
rt/share/html/Install/DatabaseType.html
rt/share/html/Install/Finish.html
rt/share/html/Install/Initialize.html
rt/share/html/Install/index.html
rt/share/html/NoAuth/Logout.html
rt/share/html/NoAuth/css/aileron/InHeader
rt/share/html/NoAuth/css/aileron/boxes.css
rt/share/html/NoAuth/css/aileron/msie-pie.css [deleted file]
rt/share/html/NoAuth/css/images/PIE.htc [deleted file]
rt/share/html/NoAuth/css/web2/InHeader
rt/share/html/NoAuth/css/web2/msie-pie.css [deleted file]
rt/share/html/NoAuth/js/titlebox-state.js
rt/share/html/NoAuth/js/userautocomplete.js
rt/share/html/NoAuth/js/util.js
rt/share/html/Prefs/Search.html
rt/share/html/REST/1.0/Forms/ticket/default
rt/share/html/REST/1.0/Forms/ticket/links
rt/share/html/REST/1.0/Forms/transaction/default
rt/share/html/REST/1.0/ticket/link
rt/share/html/Search/Build.html
rt/share/html/Search/Chart.html
rt/share/html/Search/Elements/BuildFormatString
rt/share/html/Search/Elements/Chart
rt/share/html/Search/Elements/PickBasics
rt/share/html/Search/Elements/PickCFs
rt/share/html/Search/Elements/PickCriteria
rt/share/html/Search/Results.html
rt/share/html/Search/Simple.html
rt/share/html/SelfService/Elements/MyRequests
rt/share/html/SelfService/index.html
rt/share/html/Ticket/Create.html
rt/share/html/Ticket/Display.html
rt/share/html/Ticket/Elements/Bookmark
rt/share/html/Ticket/Elements/ClickToShowHistory
rt/share/html/Ticket/Elements/FoldStanzaJS
rt/share/html/Ticket/Elements/Reminders
rt/share/html/Ticket/Elements/ShowHistory
rt/share/html/Ticket/Elements/ShowRequestor
rt/share/html/Ticket/Elements/UpdateCc
rt/share/html/Ticket/GnuPG.html
rt/share/html/Ticket/Graphs/Elements/EditGraphProperties
rt/share/html/Ticket/Graphs/Elements/ShowGraph
rt/share/html/Ticket/Graphs/dhandler
rt/share/html/Ticket/ModifyLinks.html
rt/share/html/Widgets/ComboBox
rt/share/html/Widgets/TitleBoxStart
rt/share/html/index.html
rt/share/html/l
rt/share/html/m/_elements/footer
rt/share/html/m/ticket/create
rt/share/html/m/ticket/show
rt/share/html/m/tickets/search
rt/t/api/date.t
rt/t/api/tickets.t
rt/t/lifecycles/basics.t
rt/t/mail/mime_decoding.t
rt/t/web/case-sensitivity.t
rt/t/web/query_builder.t
rt/t/web/redirect-after-login.t
rt/t/web/rest.t
rt/t/web/scrub.t
rt/t/web/ticket_forward.t
rt/t/web/ticket_links.t

index 4556b0e..f6ad714 100644 (file)
@@ -580,7 +580,7 @@ sub mason_interps {
                     },
     compiler     => HTML::Mason::Compiler::ToObject->new(
                       default_escape_flags => 'h',
-                      allow_globals        => [qw(%session)],
+                      allow_globals        => [qw(%session $DECODED_ARGS)],
                     ),
   );
 
index 98c2c30..b415a06 100644 (file)
@@ -355,7 +355,7 @@ test:
 parallel-test: test-parallel
 
 test-parallel: 
-       RT_TEST_PARALLEL=1 $(PERL) "-MApp::Prove" -e 'my $$p = App::Prove->new(); $$p->process_args("-wlrj5","--state=slow,save", "t"); $$p->run()'
+       RT_TEST_PARALLEL=1 $(PERL) "-MApp::Prove" -e 'my $$p = App::Prove->new(); $$p->process_args("-wlrj5","--state=slow,save", "t"); exit( $$p->run() ? 0 : 1 )'
 
 regression-reset-db: force-dropdb
        $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action init --dba-password ''
index 01e4a19..32f459a 100755 (executable)
--- a/rt/bin/rt
+++ b/rt/bin/rt
@@ -905,11 +905,6 @@ sub link {
     
     if (@ARGV == 3) {
         my ($from, $rel, $to) = @ARGV;
-        if ($from !~ /^\d+$/ || $to !~ /^\d+$/) {
-            my $bad = $from =~ /^\d+$/ ? $to : $from;
-            whine "Invalid $type ID '$bad' specified.";
-            $bad = 1;
-        }
         if (($type eq "ticket") && ( ! exists $ltypes{lc $rel})) {
             whine "Invalid link '$rel' for type $type specified.";
             $bad = 1;
@@ -1710,7 +1705,7 @@ sub prettyshow {
         }
         print "$k->{Content}\n" if exists $k->{Content} and
                                    $k->{Content} !~ /to have no content$/ and
-                                   $k->{Type} ne 'EmailRecord';
+                                   ($k->{Type}||'') ne 'EmailRecord';
         print "$k->{Attachments}\n" if exists $k->{Attachments} and
                                    $k->{Attachments};
     }
index b125a94..72cada6 100644 (file)
@@ -172,7 +172,6 @@ sub setup_session {
     my $self = shift;
     my $opts = shift;
     my %post_params;
-    $post_params{SessionType} = 'REST';    # Surpress login box
     foreach (qw(queue action)) {
         $post_params{$_} = $opts->{$_} if defined $opts->{$_};
     }
@@ -253,20 +252,13 @@ sub check_failure {
     my $r    = shift;
     return if $r->is_success;
 
-    # This ordinarily oughtn't to be able to happen, suggests a bug in RT.
-    # So only load these heavy modules when they're needed.
-    require HTML::TreeBuilder;
-    require HTML::FormatText;
+    # XXX TODO 4.2: Remove the multi-line error strings in favor of something more concise
+    print STDERR <<"    ERROR";
+An Error Occurred
+=================
 
-    my $error = $r->error_as_HTML;
-    my $tree  = HTML::TreeBuilder->new->parse($error);
-    $tree->eof;
-
-    # It'll be a cold day in hell before RT sends out bounces in HTML
-    my $formatter =
-        HTML::FormatText->new( leftmargin  => 0,
-                               rightmargin => 50, );
-    print STDERR $formatter->format($tree);
+@{[ $r->status_line ]}
+    ERROR
     print STDERR "\n$0: undefined server error\n" if $opts->{'debug'};
     return $self->tempfail();
 }
index 5e1c053..e54a07a 100644 (file)
@@ -905,11 +905,6 @@ sub link {
     
     if (@ARGV == 3) {
         my ($from, $rel, $to) = @ARGV;
-        if ($from !~ /^\d+$/ || $to !~ /^\d+$/) {
-            my $bad = $from =~ /^\d+$/ ? $to : $from;
-            whine "Invalid $type ID '$bad' specified.";
-            $bad = 1;
-        }
         if (($type eq "ticket") && ( ! exists $ltypes{lc $rel})) {
             whine "Invalid link '$rel' for type $type specified.";
             $bad = 1;
@@ -1710,7 +1705,7 @@ sub prettyshow {
         }
         print "$k->{Content}\n" if exists $k->{Content} and
                                    $k->{Content} !~ /to have no content$/ and
-                                   $k->{Type} ne 'EmailRecord';
+                                   ($k->{Type}||'') ne 'EmailRecord';
         print "$k->{Attachments}\n" if exists $k->{Attachments} and
                                    $k->{Attachments};
     }
index 4decdbd..1862c5f 100755 (executable)
@@ -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.5.
+# Generated by GNU Autoconf 2.67 for RT rt-4.0.6.
 #
 # Report bugs to <rt-bugs@bestpractical.com>.
 #
@@ -92,7 +92,6 @@ fi
 IFS=" ""       $as_nl"
 
 # Find who we are.  Look in the path if we contain no directory separator.
-as_myself=
 case $0 in #((
   *[\\/]* ) as_myself=$0 ;;
   *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
@@ -217,18 +216,11 @@ IFS=$as_save_IFS
   # We cannot yet assume a decent shell, so we have to provide a
        # neutralization value for shells without unset; and this also
        # works around shells that cannot unset nonexistent variables.
-       # Preserve -v and -x to the replacement shell.
        BASH_ENV=/dev/null
        ENV=/dev/null
        (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
        export CONFIG_SHELL
-       case $- in # ((((
-         *v*x* | *x*v* ) as_opts=-vx ;;
-         *v* ) as_opts=-v ;;
-         *x* ) as_opts=-x ;;
-         * ) as_opts= ;;
-       esac
-       exec "$CONFIG_SHELL" $as_opts "$as_myself" ${1+"$@"}
+       exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"}
 fi
 
     if test x$as_have_required = xno; then :
@@ -560,8 +552,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='RT'
 PACKAGE_TARNAME='rt'
-PACKAGE_VERSION='rt-4.0.5'
-PACKAGE_STRING='RT rt-4.0.5'
+PACKAGE_VERSION='rt-4.0.6'
+PACKAGE_STRING='RT rt-4.0.6'
 PACKAGE_BUGREPORT='rt-bugs@bestpractical.com'
 PACKAGE_URL=''
 
@@ -1173,7 +1165,7 @@ Try \`$0 --help' for more information"
     $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
     expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
       $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
-    : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
+    : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}
     ;;
 
   esac
@@ -1311,7 +1303,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.5 to adapt to many kinds of systems.
+\`configure' configures RT rt-4.0.6 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1372,7 +1364,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of RT rt-4.0.5:";;
+     short | recursive ) echo "Configuration of RT rt-4.0.6:";;
    esac
   cat <<\_ACEOF
 
@@ -1496,8 +1488,8 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-RT configure rt-4.0.5
-generated by GNU Autoconf 2.68
+RT configure rt-4.0.6
+generated by GNU Autoconf 2.67
 
 Copyright (C) 2010 Free Software Foundation, Inc.
 This configure script is free software; the Free Software Foundation
@@ -1543,7 +1535,7 @@ sed 's/^/| /' conftest.$ac_ext >&5
 
        ac_retval=1
 fi
-  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
   as_fn_set_status $ac_retval
 
 } # ac_fn_c_try_compile
@@ -1589,7 +1581,7 @@ fi
   # interfere with the next link command; also delete a directory that is
   # left behind by Apple's compiler.  We do this before executing the actions.
   rm -rf conftest.dSYM conftest_ipa8_conftest.oo
-  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
   as_fn_set_status $ac_retval
 
 } # ac_fn_c_try_link
@@ -1597,8 +1589,8 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by RT $as_me rt-4.0.5, which was
-generated by GNU Autoconf 2.68.  Invocation command line was
+It was created by RT $as_me rt-4.0.6, which was
+generated by GNU Autoconf 2.67.  Invocation command line was
 
   $ $0 $@
 
@@ -1856,7 +1848,7 @@ $as_echo "$as_me: loading site script $ac_site_file" >&6;}
       || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "failed to load site script $ac_site_file
-See \`config.log' for more details" "$LINENO" 5; }
+See \`config.log' for more details" "$LINENO" 5 ; }
   fi
 done
 
@@ -1954,7 +1946,7 @@ rt_version_major=4
 
 rt_version_minor=0
 
-rt_version_patch=5
+rt_version_patch=6
 
 test "x$rt_version_major" = 'x' && rt_version_major=0
 test "x$rt_version_minor" = 'x' && rt_version_minor=0
@@ -2006,7 +1998,7 @@ ac_configure="$SHELL $ac_aux_dir/configure"  # Please don't use this var.
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5
 $as_echo_n "checking for a BSD-compatible install... " >&6; }
 if test -z "$INSTALL"; then
-if ${ac_cv_path_install+:} false; then :
+if test "${ac_cv_path_install+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
@@ -2087,7 +2079,7 @@ test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
 set dummy perl; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_PERL+:} false; then :
+if test "${ac_cv_path_PERL+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   case $PERL in
@@ -2808,7 +2800,7 @@ if test -n "$ac_tool_prefix"; then
 set dummy ${ac_tool_prefix}gcc; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_CC+:} false; then :
+if test "${ac_cv_prog_CC+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$CC"; then
@@ -2848,7 +2840,7 @@ if test -z "$ac_cv_prog_CC"; then
 set dummy gcc; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_ac_ct_CC+:} false; then :
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$ac_ct_CC"; then
@@ -2901,7 +2893,7 @@ if test -z "$CC"; then
 set dummy ${ac_tool_prefix}cc; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_CC+:} false; then :
+if test "${ac_cv_prog_CC+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$CC"; then
@@ -2941,7 +2933,7 @@ if test -z "$CC"; then
 set dummy cc; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_CC+:} false; then :
+if test "${ac_cv_prog_CC+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$CC"; then
@@ -3000,7 +2992,7 @@ if test -z "$CC"; then
 set dummy $ac_tool_prefix$ac_prog; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_CC+:} false; then :
+if test "${ac_cv_prog_CC+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$CC"; then
@@ -3044,7 +3036,7 @@ do
 set dummy $ac_prog; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_ac_ct_CC+:} false; then :
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$ac_ct_CC"; then
@@ -3099,7 +3091,7 @@ fi
 test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "no acceptable C compiler found in \$PATH
-See \`config.log' for more details" "$LINENO" 5; }
+See \`config.log' for more details" "$LINENO" 5 ; }
 
 # Provide some information about the compiler.
 $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
@@ -3214,7 +3206,7 @@ sed 's/^/| /' conftest.$ac_ext >&5
 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error 77 "C compiler cannot create executables
-See \`config.log' for more details" "$LINENO" 5; }
+See \`config.log' for more details" "$LINENO" 5 ; }
 else
   { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 $as_echo "yes" >&6; }
@@ -3257,7 +3249,7 @@ else
   { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "cannot compute suffix of executables: cannot compile and link
-See \`config.log' for more details" "$LINENO" 5; }
+See \`config.log' for more details" "$LINENO" 5 ; }
 fi
 rm -f conftest conftest$ac_cv_exeext
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
@@ -3316,7 +3308,7 @@ $as_echo "$ac_try_echo"; } >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "cannot run C compiled programs.
 If you meant to cross compile, use \`--host'.
-See \`config.log' for more details" "$LINENO" 5; }
+See \`config.log' for more details" "$LINENO" 5 ; }
     fi
   fi
 fi
@@ -3327,7 +3319,7 @@ rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
 ac_clean_files=$ac_clean_files_save
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
 $as_echo_n "checking for suffix of object files... " >&6; }
-if ${ac_cv_objext+:} false; then :
+if test "${ac_cv_objext+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -3368,7 +3360,7 @@ sed 's/^/| /' conftest.$ac_ext >&5
 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "cannot compute suffix of object files: cannot compile
-See \`config.log' for more details" "$LINENO" 5; }
+See \`config.log' for more details" "$LINENO" 5 ; }
 fi
 rm -f conftest.$ac_cv_objext conftest.$ac_ext
 fi
@@ -3378,7 +3370,7 @@ OBJEXT=$ac_cv_objext
 ac_objext=$OBJEXT
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
 $as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
-if ${ac_cv_c_compiler_gnu+:} false; then :
+if test "${ac_cv_c_compiler_gnu+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -3415,7 +3407,7 @@ ac_test_CFLAGS=${CFLAGS+set}
 ac_save_CFLAGS=$CFLAGS
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
 $as_echo_n "checking whether $CC accepts -g... " >&6; }
-if ${ac_cv_prog_cc_g+:} false; then :
+if test "${ac_cv_prog_cc_g+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   ac_save_c_werror_flag=$ac_c_werror_flag
@@ -3493,7 +3485,7 @@ else
 fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
 $as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
-if ${ac_cv_prog_cc_c89+:} false; then :
+if test "${ac_cv_prog_cc_c89+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   ac_cv_prog_cc_c89=no
@@ -3591,7 +3583,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for aginitlib in -lgraph" >&5
 $as_echo_n "checking for aginitlib in -lgraph... " >&6; }
-if ${ac_cv_lib_graph_aginitlib+:} false; then :
+if test "${ac_cv_lib_graph_aginitlib+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   ac_check_lib_save_LIBS=$LIBS
@@ -3625,7 +3617,7 @@ LIBS=$ac_check_lib_save_LIBS
 fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_graph_aginitlib" >&5
 $as_echo "$ac_cv_lib_graph_aginitlib" >&6; }
-if test "x$ac_cv_lib_graph_aginitlib" = xyes; then :
+if test "x$ac_cv_lib_graph_aginitlib" = x""yes; then :
   RT_GRAPHVIZ="1"
 fi
 
@@ -3651,7 +3643,7 @@ fi
 set dummy gdlib-config; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_RT_GD+:} false; then :
+if test "${ac_cv_prog_RT_GD+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$RT_GD"; then
@@ -3707,7 +3699,7 @@ fi
 set dummy gpg; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_RT_GPG+:} false; then :
+if test "${ac_cv_prog_RT_GPG+set}" = set; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$RT_GPG"; then
@@ -3992,21 +3984,10 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
      :end' >>confcache
 if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
   if test -w "$cache_file"; then
-    if test "x$cache_file" != "x/dev/null"; then
+    test "x$cache_file" != "x/dev/null" &&
       { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
 $as_echo "$as_me: updating cache $cache_file" >&6;}
-      if test ! -f "$cache_file" || test -h "$cache_file"; then
-       cat confcache >"$cache_file"
-      else
-        case $cache_file in #(
-        */* | ?:*)
-         mv -f confcache "$cache_file"$$ &&
-         mv -f "$cache_file"$$ "$cache_file" ;; #(
-        *)
-         mv -f confcache "$cache_file" ;;
-       esac
-      fi
-    fi
+    cat confcache >$cache_file
   else
     { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
 $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
@@ -4074,7 +4055,7 @@ LTLIBOBJS=$ac_ltlibobjs
 
 
 
-: "${CONFIG_STATUS=./config.status}"
+: ${CONFIG_STATUS=./config.status}
 ac_write_fail=0
 ac_clean_files_save=$ac_clean_files
 ac_clean_files="$ac_clean_files $CONFIG_STATUS"
@@ -4175,7 +4156,6 @@ fi
 IFS=" ""       $as_nl"
 
 # Find who we are.  Look in the path if we contain no directory separator.
-as_myself=
 case $0 in #((
   *[\\/]* ) as_myself=$0 ;;
   *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
@@ -4482,8 +4462,8 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by RT $as_me rt-4.0.5, which was
-generated by GNU Autoconf 2.68.  Invocation command line was
+This file was extended by RT $as_me rt-4.0.6, which was
+generated by GNU Autoconf 2.67.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
   CONFIG_HEADERS  = $CONFIG_HEADERS
@@ -4535,8 +4515,8 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-RT config.status rt-4.0.5
-configured by $0, generated by GNU Autoconf 2.68,
+RT config.status rt-4.0.6
+configured by $0, generated by GNU Autoconf 2.67,
   with options \\"\$ac_cs_config\\"
 
 Copyright (C) 2010 Free Software Foundation, Inc.
@@ -4678,7 +4658,7 @@ do
     "t/data/configs/apache2.2+mod_perl.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.2+mod_perl.conf" ;;
     "t/data/configs/apache2.2+fastcgi.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.2+fastcgi.conf" ;;
 
-  *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
+  *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5 ;;
   esac
 done
 
@@ -4699,10 +4679,9 @@ fi
 # after its creation but before its name has been assigned to `$tmp'.
 $debug ||
 {
-  tmp= ac_tmp=
+  tmp=
   trap 'exit_status=$?
-  : "${ac_tmp:=$tmp}"
-  { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
+  { test -z "$tmp" || test ! -d "$tmp" || rm -fr "$tmp"; } && exit $exit_status
 ' 0
   trap 'as_fn_exit 1' 1 2 13 15
 }
@@ -4710,13 +4689,12 @@ $debug ||
 
 {
   tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
-  test -d "$tmp"
+  test -n "$tmp" && test -d "$tmp"
 }  ||
 {
   tmp=./conf$$-$RANDOM
   (umask 077 && mkdir "$tmp")
 } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
-ac_tmp=$tmp
 
 # Set up the scripts for CONFIG_FILES section.
 # No need to generate them if there are no CONFIG_FILES.
@@ -4738,7 +4716,7 @@ else
   ac_cs_awk_cr=$ac_cr
 fi
 
-echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
+echo 'BEGIN {' >"$tmp/subs1.awk" &&
 _ACEOF
 
 
@@ -4766,7 +4744,7 @@ done
 rm -f conf$$subs.sh
 
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
-cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
+cat >>"\$tmp/subs1.awk" <<\\_ACAWK &&
 _ACEOF
 sed -n '
 h
@@ -4814,7 +4792,7 @@ t delim
 rm -f conf$$subs.awk
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 _ACAWK
-cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
+cat >>"\$tmp/subs1.awk" <<_ACAWK &&
   for (key in S) S_is_set[key] = 1
   FS = "\a"
 
@@ -4846,7 +4824,7 @@ if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
   sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
 else
   cat
-fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
+fi < "$tmp/subs1.awk" > "$tmp/subs.awk" \
   || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
 _ACEOF
 
@@ -4886,7 +4864,7 @@ do
   esac
   case $ac_mode$ac_tag in
   :[FHL]*:*);;
-  :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
+  :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5 ;;
   :[FH]-) ac_tag=-:-;;
   :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
   esac
@@ -4905,7 +4883,7 @@ do
     for ac_f
     do
       case $ac_f in
-      -) ac_f="$ac_tmp/stdin";;
+      -) ac_f="$tmp/stdin";;
       *) # Look for the file first in the build tree, then in the source tree
         # (if the path is not absolute).  The absolute path cannot be DOS-style,
         # because $ac_f cannot contain `:'.
@@ -4914,7 +4892,7 @@ do
           [\\/$]*) false;;
           *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
           esac ||
-          as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
+          as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5 ;;
       esac
       case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
       as_fn_append ac_file_inputs " '$ac_f'"
@@ -4940,8 +4918,8 @@ $as_echo "$as_me: creating $ac_file" >&6;}
     esac
 
     case $ac_tag in
-    *:-:* | *:-) cat >"$ac_tmp/stdin" \
-      || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
+    *:-:* | *:-) cat >"$tmp/stdin" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5  ;;
     esac
     ;;
   esac
@@ -5071,22 +5049,21 @@ s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
 s&@INSTALL@&$ac_INSTALL&;t t
 $ac_datarootdir_hack
 "
-eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
-  >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$tmp/subs.awk" >$tmp/out \
+  || as_fn_error $? "could not create $ac_file" "$LINENO" 5
 
 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
-  { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
-  { ac_out=`sed -n '/^[         ]*datarootdir[  ]*:*=/p' \
-      "$ac_tmp/out"`; test -z "$ac_out"; } &&
+  { ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } &&
+  { ac_out=`sed -n '/^[         ]*datarootdir[  ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } &&
   { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
 which seems to be undefined.  Please make sure it is defined" >&5
 $as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
 which seems to be undefined.  Please make sure it is defined" >&2;}
 
-  rm -f "$ac_tmp/stdin"
+  rm -f "$tmp/stdin"
   case $ac_file in
-  -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
-  *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
+  -) cat "$tmp/out" && rm -f "$tmp/out";;
+  *) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";;
   esac \
   || as_fn_error $? "could not create $ac_file" "$LINENO" 5
  ;;
index a930134..4b64d2e 100644 (file)
@@ -106,3 +106,26 @@ with
   database level.
 
 *******
+
+UPGRADING FROM 4.0.5 and earlier - Changes:
+
+The fix for an attribute truncation bug on MySQL requires a small ALTER TABLE.
+Be sure you run `make upgrade-database` to apply this change automatically.
+The bug primarily manifested when uploading large logos in the theme editor on
+MySQL.  Refer to etc/upgrade/4.0.6/schema.mysql for the actual ALTER TABLE that
+will be run.
+
+*******
+The web-based query builder now uses Queue limits to restrict the set of
+displayed statuses and owners.  As part of this change, the %cfqueues
+parameter was renamed to %Queues; if you have local modifications to any
+of the following Mason templates, this feature will not function
+correctly:
+
+    share/html/Elements/SelectOwner
+    share/html/Elements/SelectStatus
+    share/html/Prefs/Search.html
+    share/html/Search/Build.html
+    share/html/Search/Elements/BuildFormatString
+    share/html/Search/Elements/PickCFs
+    share/html/Search/Elements/PickCriteria
index 8aa84fd..396c562 100644 (file)
@@ -186,11 +186,11 @@ which will be significantly faster:
 
     make test-parallel
 
-The C<*-trunk> and C<master> branches are expected to be passing always
-be passing all tests.  While it is acceptable to break tests in an
-intermediate commit, a branch which does not pass tests will not be
-merged.  Ideally, commits which fix a bug should also include a testcase
-which fails before the fix and succeeds after.
+The C<*-trunk> and C<master> branches are expected to always be passing
+all tests.  While it is acceptable to break tests in an intermediate
+commit, a branch which does not pass tests will not be merged.  Ideally,
+commits which fix a bug should also include a testcase which fails
+before the fix and succeeds after.
 
 
 
index b8650e0..620f868 100644 (file)
@@ -9,6 +9,21 @@ key).
 
 More information is available at L<http://bestpractical.com/security/>.
 
+
+=head2 RT's security process
+
+After a security vulnerability is reported to Best Practical and
+verified, we attempt to resolve it in as timely a fashion as possible.
+Best Practical support customers will be notified before we disclose the
+information to the public.  All security announcements will be sent to
+C<rt-announce@bestpractical.com>, which includes
+C<rt-users@bestpractical.com> and C<rt-devel@bestpractical.com>.
+
+As the tests for security vulnerabilities are often nearly identical to
+working exploits, sensitive tests will be embargoed for a period of six
+months before being added to the public RT repository.
+
+
 =head2 Security tips for running RT
 
 =over
index 65065c5..4c3f73f 100644 (file)
@@ -67,6 +67,19 @@ spontaneously logged in as other users in the system.
 
 =head3 mod_fcgid
 
+B<WARNING>: Before mod_fcgid 2.3.6, the maximum request size was 1GB.
+Starting in 2.3.6, this is now 128Kb.  This is unlikely to be large
+enough for any RT install that handles attachments.  You can read more
+about FcgidMaxRequestLen at
+L<http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#fcgidmaxrequestlen>
+
+Most distributions will have a mod_fcgid.conf or similar file with
+mod_fcgid configurations and you should add:
+
+    FcgidMaxRequestLen 1073741824
+
+to return to the old default.
+
     <VirtualHost rt.example.com>
         ### Optional apache logs for RT
         # Ensure that your log rotation scripts know about these files
index 925f0ca..de7660a 100644 (file)
@@ -350,13 +350,8 @@ Set($StoreLoops, undef);
 C<$MaxAttachmentSize> sets the maximum size (in bytes) of attachments
 stored in the database.
 
-For MySQL and Oracle, we set this size to 10 megabytes.  If you're
-running a PostgreSQL version earlier than 7.1, you will need to drop
-this to 8192. (8k)
-
 =cut
 
-
 Set($MaxAttachmentSize, 10_000_000);
 
 =item C<$TruncateLongAttachments>
@@ -892,6 +887,8 @@ Set($CanonicalizeRedirectURLs, 0);
 A list of JavaScript files to be included in head.  Removing any of
 the default entries is not suggested.
 
+If you're a plugin author, refer to RT->AddJavaScript.
+
 =cut
 
 Set(@JSFiles, qw/
@@ -928,6 +925,8 @@ directory, or from http://www.crockford.com/javascript/jsmin.html
 
 A list of additional CSS files to be included in head.
 
+If you're a plugin author, refer to RT->AddStyleSheets.
+
 =cut
 
 Set(@CSSFiles, qw//);
@@ -1789,8 +1788,50 @@ This disables RT's clickjacking protection.
 
 Set($Framebusting, 1);
 
+=item C<$RestrictReferrer>
+
+If set to a false value, the HTTP C<Referer> (sic) header will not be
+checked to ensure that requests come from RT's own domain.  As RT allows
+for GET requests to alter state, disabling this opens RT up to
+cross-site request forgery (CSRF) attacks.
+
+=cut
+
+Set($RestrictReferrer, 1);
+
+=item C<$RestrictLoginReferrer>
+
+If set to a false value, RT will allow the user to log in from any link
+or request, merely by passing in C<user> and C<pass> parameters; setting
+it to a true value forces all logins to come from the login box, so the
+user is aware that they are being logged in.  The default is off, for
+backwards compatability.
+
+=cut
+
+Set($RestrictLoginReferrer, 0);
+
+=item C<$ReferrerWhitelist>
+
+This is a list of hostname:port combinations that RT will treat as being
+part of RT's domain. This is particularly useful if you access RT as
+multiple hostnames or have an external auth system that needs to
+redirect back to RT once authentication is complete.
+
+ Set(@ReferrerWhitelist, qw(www.example.com:443  www3.example.com:80));
+
+If the "RT has detected a possible cross-site request forgery" error is triggered
+by a host:port sent by your browser that you believe should be valid, you can copy
+the host:port from the error message into this list.
+
+=cut
+
+Set(@ReferrerWhitelist, qw());
+
 =back
 
+
+
 =head1 Authorization and user configuration
 
 =over 4
@@ -1856,10 +1897,9 @@ Set($AutoCreate, undef);
 
 =item C<$WebSessionClass>
 
-C<$WebSessionClass> is the class you wish to use for managing
-Sessions.  It defaults to use your SQL database, but if you are using
-MySQL 3.x and plans to use non-ascii Queue names, uncomment and add
-this line to F<RT_SiteConfig.pm> to prevent session corruption.
+C<$WebSessionClass> is the class you wish to use for managing sessions.
+It defaults to use your SQL database, except on Oracle, where it
+defaults to files on disk.
 
 =cut
 
@@ -2206,6 +2246,14 @@ be changed to this value.
 When an approval is denied, the status of depending tickets will
 be changed to this value.
 
+=item reminder_on_open
+
+When a reminder is opened, the status will be changed to this value.
+
+=item reminder_on_resolve
+
+When a reminder is resolved, the status will be changed to this value.
+
 =back
 
 =head2 Transitions between statuses and UI actions
@@ -2352,6 +2400,8 @@ Set(%Lifecycles,
             on_merge  => 'resolved',
             approved  => 'open',
             denied    => 'rejected',
+            reminder_on_open     => 'open',
+            reminder_on_resolve  => 'resolved',
         },
 
         transitions => {
@@ -2425,6 +2475,8 @@ Set(%Lifecycles,
         defaults => {
             on_create => 'new',
             on_merge => 'resolved',
+            reminder_on_open     => 'open',
+            reminder_on_resolve  => 'resolved',
         },
 
         transitions => {
@@ -2529,7 +2581,7 @@ Set(%AdminSearchResultFormat,
     Queues =>
         q{'<a href="__WebPath__/Admin/Queues/Modify.html?id=__id__">__id__</a>/TITLE:#'}
         .q{,'<a href="__WebPath__/Admin/Queues/Modify.html?id=__id__">__Name__</a>/TITLE:Name'}
-        .q{,__Description__,__Address__,__Priority__,__DefaultDueIn__,__Disabled__},
+        .q{,__Description__,__Address__,__Priority__,__DefaultDueIn__,'__Disabled__,__Lifecycle__},
 
     Groups =>
         q{'<a href="__WebPath__/Admin/Groups/Modify.html?id=__id__">__id__</a>/TITLE:#'}
index c313aaf..9ed0337 100644 (file)
@@ -413,7 +413,7 @@ CREATE TABLE Attributes (
   id INTEGER NOT NULL  AUTO_INCREMENT,
   Name varchar(255) NULL  ,
   Description varchar(255) NULL  ,
-  Content BLOB,
+  Content LONGBLOB,
   ContentType varchar(16) CHARACTER SET ascii,
   ObjectType varchar(64) CHARACTER SET ascii,
   ObjectId integer, # foreign key to anything
index 728786f..a3d719c 100755 (executable)
@@ -89,6 +89,9 @@ push @{$users->{'restrictions'}{ "main.Password" }}, "AND", {
     value => '40',
 };
 
+# we want to update passwords on disabled users
+$users->{'find_disabled_rows'} = 1;
+
 my $count = $users->Count;
 if ($count == 0) {
     print "No users with unsalted or weak cryptography found.\n";
index d7c9ef2..49a7f1d 100755 (executable)
@@ -182,6 +182,9 @@ sub LimitToPrincipal {
                      ALIAS2 => $cgm,
                      FIELD2 => 'GroupId'
                    );
+        $self->Limit( ALIAS => $cgm,
+                      FIELD => 'Disabled',
+                      VALUE => 0 );
         $self->Limit( ALIAS           => $cgm,
                       FIELD           => 'MemberId',
                       OPERATOR        => '=',
index c26e2eb..31489c8 100644 (file)
@@ -325,9 +325,19 @@ sub Prepare {
 
     }
 
+    my $active = 0;
+    if ( $self->TemplateObj->Type eq 'Perl' ) {
+        $active = 1;
+    } else {
+        RT->Logger->info(sprintf(
+            "Template #%d is type %s.  You most likely want to use a Perl template instead.",
+            $self->TemplateObj->id, $self->TemplateObj->Type
+        ));
+    }
+
     $self->Parse(
         Content        => $self->TemplateObj->Content,
-        _ActiveContent => 1
+        _ActiveContent => $active,
     );
     return 1;
 
@@ -1171,6 +1181,7 @@ sub UpdateCustomFields {
         my $cf = $1;
 
         my $CustomFieldObj = RT::CustomField->new($self->CurrentUser);
+        $CustomFieldObj->SetContextObject( $ticket );
         $CustomFieldObj->LoadById($cf);
 
         my @values;
index e2aa00b..4ae1a8b 100755 (executable)
@@ -348,7 +348,7 @@ sub AddAttachments {
 
     $MIMEObj->head->delete('RT-Attach-Message');
 
-    my $attachments = RT::Attachments->new(RT->SystemUser);
+    my $attachments = RT::Attachments->new( RT->SystemUser );
     $attachments->Limit(
         FIELD => 'TransactionId',
         VALUE => $self->TransactionObj->Id
@@ -408,6 +408,10 @@ sub AddAttachment {
     my $attach  = shift;
     my $MIMEObj = shift || $self->TemplateObj->MIMEObj;
 
+    # $attach->TransactionObj may not always be $self->TransactionObj
+    return unless $attach->Id
+              and $attach->TransactionObj->CurrentUserCanSee;
+
     # ->attach expects just the disposition type; extract it if we have the header
     my $disp = ($attach->GetHeader('Content-Disposition') || '')
                     =~ /^\s*(inline|attachment)/i ? $1 : undef;
@@ -471,8 +475,7 @@ sub AddTicket {
     my $self = shift;
     my $tid  = shift;
 
-    # XXX: we need a current user here, but who is current user?
-    my $attachs   = RT::Attachments->new(RT->SystemUser);
+    my $attachs   = RT::Attachments->new( $self->TransactionObj->CreatorObj );
     my $txn_alias = $attachs->TransactionAlias;
     $attachs->Limit( ALIAS => $txn_alias, FIELD => 'Type', VALUE => 'Create' );
     $attachs->Limit(
index 7310241..24b952a 100644 (file)
@@ -543,6 +543,17 @@ sub CurrentUserHasRight {
 
 }
 
+=head2 CurrentUserCanSee
+
+Returns true if the current user can see the article, using ShowArticle
+
+=cut
+
+sub CurrentUserCanSee {
+    my $self = shift;
+    return $self->CurrentUserHasRight('ShowArticle');
+}
+
 # }}}
 
 # {{{ _Set
index c640b20..2bdbc24 100755 (executable)
@@ -227,15 +227,12 @@ sub Next {
     my $Attachment = $self->SUPER::Next;
     return $Attachment unless $Attachment;
 
-    my $txn = $Attachment->TransactionObj;
-    if ( $txn->__Value('Type') eq 'Comment' ) {
-        return $Attachment if $txn->CurrentUserHasRight('ShowTicketComments');
-    } elsif ( $txn->CurrentUserHasRight('ShowTicket') ) {
+    if ( $Attachment->TransactionObj->CurrentUserCanSee ) {
         return $Attachment;
+    } else {
+        # If the user doesn't have the right to show this ticket
+        return $self->Next;
     }
-
-    # If the user doesn't have the right to show this ticket
-    return $self->Next;
 }
 
 
index bb694ce..3906b9f 100644 (file)
@@ -275,6 +275,7 @@ sub ArticleCustomFields {
 
     my $cfs = RT::CustomFields->new( $self->CurrentUser );
     if ( $self->CurrentUserHasRight('SeeClass') ) {
+        $cfs->SetContextObject( $self );
         $cfs->LimitToGlobalOrObjectId( $self->Id );
         $cfs->LimitToLookupType( RT::Article->CustomFieldLookupType );
         $cfs->ApplySortOrder;
index cc47df3..c56d4c6 100644 (file)
@@ -620,6 +620,7 @@ our %META = (
             }
         }
     },
+    ReferrerWhitelist => { Type => 'ARRAY' },
     ResolveDefaultUpdateType => {
         PostLoadCheck => sub {
             my $self  = shift;
index 095caa5..263bde8 100644 (file)
@@ -474,10 +474,12 @@ sub LoadByName {
     }
 
     # if we're looking for a queue by name, make it a number
-    if ( defined $args{'Queue'} && $args{'Queue'} =~ /\D/ ) {
+    if ( defined $args{'Queue'} && ($args{'Queue'} =~ /\D/ || !$self->ContextObject) ) {
         my $QueueObj = RT::Queue->new( $self->CurrentUser );
         $QueueObj->Load( $args{'Queue'} );
         $args{'Queue'} = $QueueObj->Id;
+        $self->SetContextObject( $QueueObj )
+            unless $self->ContextObject;
     }
 
     # XXX - really naive implementation.  Slow. - not really. still just one query
@@ -535,6 +537,8 @@ sub Values {
     # if the user has no rights, return an empty object
     if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
         $cf_values->LimitToCustomField( $self->Id );
+    } else {
+        $cf_values->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' );
     }
     return ($cf_values);
 }
@@ -890,7 +894,77 @@ sub ContextObject {
     my $self = shift;
     return $self->{'context_object'};
 }
-  
+
+sub ValidContextType {
+    my $self = shift;
+    my $class = shift;
+
+    my %valid;
+    $valid{$_}++ for split '-', $self->LookupType;
+    delete $valid{'RT::Transaction'};
+
+    return $valid{$class};
+}
+
+=head2 LoadContextObject
+
+Takes an Id for a Context Object and loads the right kind of RT::Object
+for this particular Custom Field (based on the LookupType) and returns it.
+This is a good way to ensure you don't try to use a Queue as a Context
+Object on a User Custom Field.
+
+=cut
+
+sub LoadContextObject {
+    my $self = shift;
+    my $type = shift;
+    my $contextid = shift;
+
+    unless ( $self->ValidContextType($type) ) {
+        RT->Logger->debug("Invalid ContextType $type for Custom Field ".$self->Id);
+        return;
+    }
+
+    my $context_object = $type->new( $self->CurrentUser );
+    my ($id, $msg) = $context_object->LoadById( $contextid );
+    unless ( $id ) {
+        RT->Logger->debug("Invalid ContextObject id: $msg");
+        return;
+    }
+    return $context_object;
+}
+
+=head2 ValidateContextObject
+
+Ensure that a given ContextObject applies to this Custom Field.
+For custom fields that are assigned to Queues or to Classes, this checks that the Custom
+Field is actually applied to that objects.  For Global Custom Fields, it returns true
+as long as the Object is of the right type, because you may be using
+your permissions on a given Queue of Class to see a Global CF.
+For CFs that are only applied Globally, you don't need a ContextObject.
+
+=cut
+
+sub ValidateContextObject {
+    my $self = shift;
+    my $object = shift;
+
+    return 1 if $self->IsApplied(0);
+
+    # global only custom fields don't have objects
+    # that should be used as context objects.
+    return if $self->ApplyGlobally;
+
+    # Otherwise, make sure we weren't passed a user object that we're
+    # supposed to treat as a queue.
+    return unless $self->ValidContextType(ref $object);
+
+    # Check that it is applied correctly
+    my ($applied_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects);
+    return unless $applied_to;
+    return $self->IsApplied($applied_to->id);
+}
+
 
 sub _Set {
     my $self = shift;
@@ -1702,6 +1776,7 @@ sub SetBasedOn {
         unless defined $value and length $value;
 
     my $cf = RT::CustomField->new( $self->CurrentUser );
+    $cf->SetContextObject( $self->ContextObject );
     $cf->Load( ref $value ? $value->id : $value );
 
     return (0, "Permission denied")
@@ -1719,6 +1794,7 @@ sub BasedOnObj {
     my $self = shift;
 
     my $obj = RT::CustomField->new( $self->CurrentUser );
+    $obj->SetContextObject( $self->ContextObject );
     if ( $self->BasedOn ) {
         $obj->Load( $self->BasedOn );
     }
index 8558978..40b53b1 100644 (file)
@@ -252,7 +252,7 @@ SUMMARY
 
     $content = HTML::RewriteAttributes::Links->rewrite(
         $content,
-        RT->Config->Get('WebURL') . '/Dashboards/Render.html',
+        RT->Config->Get('WebURL') . 'Dashboards/Render.html',
     );
 
     $self->EmailDashboard(
@@ -447,6 +447,9 @@ sub BuildEmail {
                 autohandler_name => '', # disable forced login and more
                 data_dir => $data_dir,
             );
+            $mason->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
+            $mason->set_escape( u => \&RT::Interface::Web::EscapeURI  );
+            $mason->set_escape( j => \&RT::Interface::Web::EscapeJS   );
         }
         return $mason;
     }
index 7a97fbe..442c770 100644 (file)
@@ -604,6 +604,10 @@ sub Get
     my $self = shift;
     my %args = (Format => 'ISO', @_);
     my $formatter = $args{'Format'};
+    unless ( $self->ValidFormatter($formatter) ) {
+        RT->Logger->warning("Invalid date formatter '$formatter', falling back to ISO");
+        $formatter = 'ISO';
+    }
     $formatter = 'ISO' unless $self->can($formatter);
     return $self->$formatter( %args );
 }
@@ -642,6 +646,20 @@ sub Formatters
     return @FORMATTERS;
 }
 
+=head3 ValidFormatter FORMAT
+
+Returns a true value if C<FORMAT> is a known formatter.  Otherwise returns
+false.
+
+=cut
+
+sub ValidFormatter {
+    my $self   = shift;
+    my $format = shift;
+    return (grep { $_ eq $format } $self->Formatters and $self->can($format))
+                ? 1 : 0;
+}
+
 =head3 DefaultFormat
 
 =cut
@@ -720,15 +738,19 @@ sub LocalizedDateTime
     my %args = ( Date => 1,
                  Time => 1,
                  Timezone => '',
-                 DateFormat => 'date_format_full',
-                 TimeFormat => 'time_format_medium',
+                 DateFormat => '',
+                 TimeFormat => '',
                  AbbrDay => 1,
                  AbbrMonth => 1,
                  @_,
                );
 
-    my $date_format = $args{'DateFormat'};
-    my $time_format = $args{'TimeFormat'};
+    # Require valid names for the format methods
+    my $date_format = $args{DateFormat} =~ /^\w+$/
+                    ? $args{DateFormat} : 'date_format_full';
+
+    my $time_format = $args{TimeFormat} =~ /^\w+$/
+                    ? $args{TimeFormat} : 'time_format_medium';
 
     my $formatter = $self->LocaleObj;
     $date_format = $formatter->$date_format;
index b02a413..2abcf3b 100644 (file)
@@ -50,7 +50,7 @@ package RT;
 use warnings;
 use strict;
 
-our $VERSION = '4.0.5';
+our $VERSION = '4.0.6';
 
 
 
index 112934e..b839824 100644 (file)
@@ -100,7 +100,7 @@ EOT
 
 sub gv_escape($) {
     my $value = shift;
-    $value =~ s{(?=")}{\\}g;
+    $value =~ s{(?=["\\])}{\\}g;
     return $value;
 }
 
@@ -278,6 +278,14 @@ sub TicketLinks {
         ShowLinkDescriptions => 0,
         @_
     );
+
+    my %valid_links = map { $_ => 1 }
+        qw(Members MemberOf RefersTo ReferredToBy DependsOn DependedOnBy);
+
+    # Validate our link types
+    $args{ShowLinks}   = [ grep { $valid_links{$_} } @{$args{ShowLinks}} ];
+    $args{LeadingLink} = 'Members' unless $valid_links{ $args{LeadingLink} };
+
     unless ( $args{'Graph'} ) {
         $args{'Graph'} = GraphViz->new(
             name    => 'ticket_links_'. $args{'Ticket'}->id,
index 779c026..b367b2f 100755 (executable)
@@ -1171,8 +1171,18 @@ sub CurrentUserHasRight {
 }
 
 
+=head2 CurrentUserCanSee
 
+Always returns 1; unfortunately, for historical reasons, users have
+always been able to examine groups they have indirect access to, even if
+they do not have SeeGroup explicitly.
 
+=cut
+
+sub CurrentUserCanSee {
+    my $self = shift;
+    return 1;
+}
 
 
 =head2 PrincipalObj
index 46f1c23..578109c 100755 (executable)
@@ -234,6 +234,8 @@ sub WithMember {
                 ALIAS2 => $members, FIELD2 => 'GroupId');
 
     $self->Limit(ALIAS => $members, FIELD => 'MemberId', OPERATOR => '=', VALUE => $args{'PrincipalId'});
+    $self->Limit(ALIAS => $members, FIELD => 'Disabled', VALUE => 0)
+        if $args{'Recursively'};
 
     return $members;
 }
@@ -261,6 +263,12 @@ sub WithoutMember {
         VALUE    => $args{'PrincipalId'},
     );
     $self->Limit(
+        LEFTJOIN => $members_alias,
+        ALIAS    => $members_alias,
+        FIELD    => 'Disabled',
+        VALUE    => 0
+    ) if $args{'Recursively'};
+    $self->Limit(
         ALIAS    => $members_alias,
         FIELD    => 'MemberId',
         OPERATOR => 'IS',
index bb19aa9..99d10e3 100644 (file)
@@ -226,14 +226,12 @@ sub CheckIntegrity {
     my $self = shift;
     $self = new $self unless ref $self;
 
-    do {
+    unless ($RT::Handle and $RT::Handle->dbh) {
         local $@;
         unless ( eval { RT::ConnectToDatabase(); 1 } ) {
             return (0, 'no connection', "$@");
         }
-    };
-
-    RT::InitLogging();
+    }
 
     require RT::CurrentUser;
     my $test_user = RT::CurrentUser->new;
@@ -748,6 +746,10 @@ sub InsertData {
     my $self     = shift;
     my $datafile = shift;
     my $root_password = shift;
+    my %args     = (
+        disconnect_after => 1,
+        @_
+    );
 
     # Slurp in stuff to insert from the datafile. Possible things to go in here:-
     our (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
@@ -1071,8 +1073,14 @@ sub InsertData {
         $RT::Logger->debug("done.");
     }
 
-    my $db_type = RT->Config->Get('DatabaseType');
-    $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
+    # XXX: This disconnect doesn't really belong here; it's a relict from when
+    # this method was extracted from rt-setup-database.  However, too much
+    # depends on it to change without significant testing.  At the very least,
+    # we can provide a way to skip the side-effect.
+    if ( $args{disconnect_after} ) {
+        my $db_type = RT->Config->Get('DatabaseType');
+        $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
+    }
 
     $RT::Logger->debug("Done setting up database content.");
 
index 971eaa1..cadf7cc 100644 (file)
@@ -219,13 +219,6 @@ sub SetMIMEEntityToEncoding {
 
     my $head = $entity->head;
 
-    # convert at least MIME word encoded attachment filename
-    foreach my $attr (qw(content-type.name content-disposition.filename)) {
-       if ( my $name = $head->mime_attr($attr) and !$preserve_words ) {
-           $head->mime_attr( $attr => DecodeMIMEWordsToUTF8($name) );
-       }
-    }
-
     # If this is a textual entity, we'd need to preserve its original encoding
     $head->replace( "X-RT-Original-Encoding" => $charset )
        if $head->mime_attr('content-type.charset') or IsTextualContentType($head->mime_type);
@@ -292,7 +285,28 @@ sub DecodeMIMEWordsToEncoding {
     my $to_charset = _CanonicalizeCharset(shift);
     my $field = shift || '';
 
-    my @list = $str =~ m/(.*?)=\?([^?]+)\?([QqBb])\?([^?]+)\?=([^=]*)/gcs;
+    # handle filename*=ISO-8859-1''%74%E9%73%74%2E%74%78%74, parameter value
+    # continuations, and similar syntax from RFC 2231
+    if ($field =~ /^Content-(Type|Disposition)/i) {
+        # This concatenates continued parameters and normalizes encoded params
+        # to QB encoded-words which we handle below
+        $str = MIME::Field::ParamVal->parse($str)->stringify;
+    }
+
+    # XXX TODO: use decode('MIME-Header', ...) and Encode::Alias to replace our
+    # custom MIME word decoding and charset canonicalization.  We can't do this
+    # until we parse before decode, instead of the other way around.
+    my @list = $str =~ m/(.*?)          # prefix
+                         =\?            # =?
+                         ([^?]+?)       # charset
+                         (?:\*[^?]+)?   # optional '*language'
+                         \?             # ?
+                         ([QqBb])       # encoding
+                         \?             # ?
+                         ([^?]+)        # encoded string
+                         \?=            # ?=
+                         ([^=]*)        # trailing
+                        /xgcs;
 
     if ( @list ) {
         # add everything that hasn't matched to the end of the latest
@@ -350,27 +364,6 @@ sub DecodeMIMEWordsToEncoding {
         }
     }
 
-# handle filename*=ISO-8859-1''%74%E9%73%74%2E%74%78%74, see also rfc 2231
-    @list = $str =~ m/(.*?\*=)([^']*?)'([^']*?)'(\S+)(.*?)(?=(?:\*=|$))/gcs;
-    if (@list) {
-        $str = '';
-        while (@list) {
-            my ( $prefix, $charset, $language, $enc_str, $trailing ) =
-              splice @list, 0, 5;
-            $prefix =~ s/\*=$/=/; # remove the *
-            $charset = _CanonicalizeCharset($charset);
-            $enc_str =~ s/%(\w{2})/chr hex $1/eg;
-            unless ( $charset eq $to_charset ) {
-                Encode::from_to( $enc_str, $charset, $to_charset );
-            }
-            $enc_str = qq{"$enc_str"}
-              if $enc_str =~ /[,;]/
-              and $enc_str !~ /^".*"$/
-              and (!$field || $field =~ /^(?:To$|From$|B?Cc$|Content-)/i);
-            $str .= $prefix . $enc_str . $trailing;
-        }
-     }
-
     # We might have \n without trailing whitespace, which will result in
     # invalid headers.
     $str =~ s/\n//g;
index 3976ade..d12abb6 100644 (file)
@@ -252,7 +252,7 @@ sub CurrentValues {
 
 sub ConfigFile {
     require File::Spec;
-    return File::Spec->catfile( $RT::EtcPath, 'RT_SiteConfig.pm' );
+    return $ENV{RT_SITE_CONFIG} || File::Spec->catfile( $RT::EtcPath, 'RT_SiteConfig.pm' );
 }
 
 sub SaveConfig {
index b9145d6..02a1ec0 100755 (executable)
@@ -57,6 +57,7 @@ use RT::EmailParser;
 use File::Temp;
 use UNIVERSAL::require;
 use Mail::Mailer ();
+use Text::ParseWords qw/shellwords/;
 
 BEGIN {
     use base 'Exporter';
@@ -404,11 +405,11 @@ sub SendEmail {
 
     if ( $mail_command eq 'sendmailpipe' ) {
         my $path = RT->Config->Get('SendmailPath');
-        my $args = RT->Config->Get('SendmailArguments');
+        my @args = shellwords(RT->Config->Get('SendmailArguments'));
 
         # SetOutgoingMailFrom and bounces conflict, since they both want -f
         if ( $args{'Bounce'} ) {
-            $args .= ' '. RT->Config->Get('SendmailBounceArguments');
+            push @args, shellwords(RT->Config->Get('SendmailBounceArguments'));
         } elsif ( RT->Config->Get('SetOutgoingMailFrom') ) {
             my $OutgoingMailAddress;
 
@@ -425,7 +426,7 @@ sub SendEmail {
 
             $OutgoingMailAddress ||= RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'};
 
-            $args .= " -f $OutgoingMailAddress"
+            push @args, "-f", $OutgoingMailAddress
                 if $OutgoingMailAddress;
         }
 
@@ -437,32 +438,36 @@ sub SendEmail {
             my $from = $TransactionObj->CreatorObj->EmailAddress;
             $from =~ s/@/=/g;
             $from =~ s/\s//g;
-            $args .= " -f $prefix$from\@$domain";
+            push @args, "-f", "$prefix$from\@$domain";
         }
 
         eval {
             # don't ignore CHLD signal to get proper exit code
             local $SIG{'CHLD'} = 'DEFAULT';
 
-            open( my $mail, '|-', "$path $args >/dev/null" )
-                or die "couldn't execute program: $!";
-
             # if something wrong with $mail->print we will get PIPE signal, handle it
             local $SIG{'PIPE'} = sub { die "program unexpectedly closed pipe" };
+
+            require IPC::Open2;
+            my ($mail, $stdout);
+            my $pid = IPC::Open2::open2( $stdout, $mail, $path, @args )
+                or die "couldn't execute program: $!";
+
             $args{'Entity'}->print($mail);
+            close $mail or die "close pipe failed: $!";
 
-            unless ( close $mail ) {
-                die "close pipe failed: $!" if $!; # system error
+            waitpid($pid, 0);
+            if ($?) {
                 # sendmail exit statuses mostly errors with data not software
                 # TODO: status parsing: core dump, exit on signal or EX_*
-                my $msg = "$msgid: `$path $args` exitted with code ". ($?>>8);
+                my $msg = "$msgid: `$path @args` exited with code ". ($?>>8);
                 $msg = ", interrupted by signal ". ($?&127) if $?&127;
                 $RT::Logger->error( $msg );
                 die $msg;
             }
         };
         if ( $@ ) {
-            $RT::Logger->crit( "$msgid: Could not send mail with command `$path $args`: " . $@ );
+            $RT::Logger->crit( "$msgid: Could not send mail with command `$path @args`: " . $@ );
             if ( $TicketObj ) {
                 _RecordSendEmailFailure( $TicketObj );
             }
@@ -743,16 +748,19 @@ sub SendForward {
     $mail->add_part( $entity );
 
     my $from;
-    my $subject = '';
-    $subject = $txn->Subject if $txn;
-    $subject ||= $ticket->Subject if $ticket;
+    unless (defined $mail->head->get('Subject')) {
+        my $subject = '';
+        $subject = $txn->Subject if $txn;
+        $subject ||= $ticket->Subject if $ticket;
+
+        unless ( RT->Config->Get('ForwardFromUser') ) {
+            # XXX: what if want to forward txn of other object than ticket?
+            $subject = AddSubjectTag( $subject, $ticket );
+        }
 
-    unless ( RT->Config->Get('ForwardFromUser') ) {
-        # XXX: what if want to forward txn of other object than ticket?
-        $subject = AddSubjectTag( $subject, $ticket );
+        $mail->head->set( Subject => EncodeToMIME( String => "Fwd: $subject" ) );
     }
 
-    $mail->head->set( Subject => EncodeToMIME( String => "Fwd: $subject" ) );
     $mail->head->set(
         From => EncodeToMIME(
             String => GetForwardFrom( Transaction => $txn, Ticket => $ticket )
index 7c9d578..814a829 100644 (file)
@@ -158,6 +158,25 @@ sub EncodeJSON {
     JSON::to_json(shift, { utf8 => 1, allow_nonref => 1 });
 }
 
+sub _encode_surrogates {
+    my $uni = $_[0] - 0x10000;
+    return ($uni /  0x400 + 0xD800, $uni % 0x400 + 0xDC00);
+}
+
+sub EscapeJS {
+    my $ref = shift;
+    return unless defined $$ref;
+
+    $$ref = "'" . join('',
+                 map {
+                     chr($_) =~ /[a-zA-Z0-9]/ ? chr($_) :
+                     $_  <= 255   ? sprintf("\\x%02X", $_) :
+                     $_  <= 65535 ? sprintf("\\u%04X", $_) :
+                     sprintf("\\u%X\\u%X", _encode_surrogates($_))
+                 } unpack('U*', $$ref))
+        . "'";
+}
+
 =head2 WebCanonicalizeInfo();
 
 Different web servers set different environmental varibles. This
@@ -234,8 +253,10 @@ sub HandleRequest {
     ValidateWebConfig();
 
     DecodeARGS($ARGS);
+    local $HTML::Mason::Commands::DECODED_ARGS = $ARGS;
     PreprocessTimeUpdates($ARGS);
 
+    InitializeMenu();
     MaybeShowInstallModePage();
 
     $HTML::Mason::Commands::m->comp( '/Elements/SetupSessionCookie', %$ARGS );
@@ -285,6 +306,8 @@ sub HandleRequest {
         }
     }
 
+    MaybeShowInterstitialCSRFPage($ARGS);
+
     # now it applies not only to home page, but any dashboard that can be used as a workspace
     $HTML::Mason::Commands::session{'home_refresh_interval'} = $ARGS->{'HomeRefreshInterval'}
         if ( $ARGS->{'HomeRefreshInterval'} );
@@ -347,8 +370,6 @@ sub SetNextPage {
 
     $HTML::Mason::Commands::session{'NextPage'}->{$hash} = $next;
     $HTML::Mason::Commands::session{'i'}++;
-    
-    SendSessionCookie();
     return $hash;
 }
 
@@ -465,7 +486,6 @@ sub MaybeShowNoAuthPage {
         if $m->base_comp->path eq '/NoAuth/Login.html' and _UserLoggedIn();
 
     # If it's a noauth file, don't ask for auth.
-    SendSessionCookie();
     $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %$ARGS );
     $m->abort;
 }
@@ -492,7 +512,7 @@ sub MaybeRejectPrivateComponentRequest {
               _elements   | # mobile UI
               Widgets     |
               autohandler | # requesting this directly is suspicious
-              l           ) # loc component
+              l (_unsafe)? ) # loc component
             ( $ | / ) # trailing slash or end of path
         }xi
         && $path !~ m{ /RTx/Statistics/\w+/Elements/Chart }xi
@@ -526,13 +546,13 @@ sub ShowRequestedPage {
 
     my $m = $HTML::Mason::Commands::m;
 
+    # Ensure that the cookie that we send is up-to-date, in case the
+    # session-id has been modified in any way
+    SendSessionCookie();
+
     # precache all system level rights for the current user
     $HTML::Mason::Commands::session{CurrentUser}->PrincipalObj->HasRights( Object => RT->System );
 
-    InitializeMenu();
-
-    SendSessionCookie();
-
     # If the user isn't privileged, they can only see SelfService
     unless ( $HTML::Mason::Commands::session{'CurrentUser'}->Privileged ) {
 
@@ -681,7 +701,6 @@ sub AttemptPasswordAuthentication {
 
         InstantiateNewSession();
         $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
-        SendSessionCookie();
 
         $m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler' );
 
@@ -736,6 +755,7 @@ sub LoadSessionFromCookie {
 sub InstantiateNewSession {
     tied(%HTML::Mason::Commands::session)->delete if tied(%HTML::Mason::Commands::session);
     tie %HTML::Mason::Commands::session, 'RT::Interface::Web::Session', undef;
+    SendSessionCookie();
 }
 
 sub SendSessionCookie {
@@ -817,6 +837,10 @@ sub StaticFileHeaders {
     # make cache public
     $HTML::Mason::Commands::r->headers_out->{'Cache-Control'} = 'max-age=259200, public';
 
+    # remove any cookie headers -- if it is cached publicly, it
+    # shouldn't include anyone's cookie!
+    delete $HTML::Mason::Commands::r->err_headers_out->{'Set-Cookie'};
+
     # Expire things in a month.
     $date->Set( Value => time + 30 * 24 * 60 * 60 );
     $HTML::Mason::Commands::r->headers_out->{'Expires'} = $date->RFC2616;
@@ -828,6 +852,22 @@ sub StaticFileHeaders {
     # $HTML::Mason::Commands::r->headers_out->{'Last-Modified'} = $date->RFC2616;
 }
 
+=head2 ComponentPathIsSafe PATH
+
+Takes C<PATH> and returns a boolean indicating that the user-specified partial
+component path is safe.
+
+Currently "safe" means that the path does not start with a dot (C<.>) and does
+not contain a slash-dot C</.>.
+
+=cut
+
+sub ComponentPathIsSafe {
+    my $self = shift;
+    my $path = shift;
+    return $path !~ m{(?:^|/)\.};
+}
+
 =head2 PathIsSafe
 
 Takes a C<< Path => path >> and returns a boolean indicating that
@@ -1138,6 +1178,218 @@ sub ComponentRoots {
     return @roots;
 }
 
+our %is_whitelisted_component = (
+    # The RSS feed embeds an auth token in the path, but query
+    # information for the search.  Because it's a straight-up read, in
+    # addition to embedding its own auth, it's fine.
+    '/NoAuth/rss/dhandler' => 1,
+);
+
+sub IsCompCSRFWhitelisted {
+    my $comp = shift;
+    my $ARGS = shift;
+
+    return 1 if $is_whitelisted_component{$comp};
+
+    my %args = %{ $ARGS };
+
+    # If the user specifies a *correct* user and pass then they are
+    # golden.  This acts on the presumption that external forms may
+    # hardcode a username and password -- if a malicious attacker knew
+    # both already, CSRF is the least of your problems.
+    my $AllowLoginCSRF = not RT->Config->Get('RestrictReferrerLogin');
+    if ($AllowLoginCSRF and defined($args{user}) and defined($args{pass})) {
+        my $user_obj = RT::CurrentUser->new();
+        $user_obj->Load($args{user});
+        return 1 if $user_obj->id && $user_obj->IsPassword($args{pass});
+
+        delete $args{user};
+        delete $args{pass};
+    }
+
+    # Eliminate arguments that do not indicate an effectful request.
+    # For example, "id" is acceptable because that is how RT retrieves a
+    # record.
+    delete $args{id};
+
+    # If they have a valid results= from MaybeRedirectForResults, that's
+    # also fine.
+    delete $args{results} if $args{results}
+        and $HTML::Mason::Commands::session{"Actions"}->{$args{results}};
+
+    # The homepage refresh, which uses the Refresh header, doesn't send
+    # a referer in most browsers; whitelist the one parameter it reloads
+    # with, HomeRefreshInterval, which is safe
+    delete $args{HomeRefreshInterval};
+
+    # If there are no arguments, then it's likely to be an idempotent
+    # request, which are not susceptible to CSRF
+    return 1 if !%args;
+
+    return 0;
+}
+
+sub IsRefererCSRFWhitelisted {
+    my $referer = _NormalizeHost(shift);
+    my $base_url = _NormalizeHost(RT->Config->Get('WebBaseURL'));
+    $base_url = $base_url->host_port;
+
+    my $configs;
+    for my $config ( $base_url, RT->Config->Get('ReferrerWhitelist') ) {
+        push @$configs,$config;
+        return 1 if $referer->host_port eq $config;
+    }
+
+    return (0,$referer,$configs);
+}
+
+=head3 _NormalizeHost
+
+Takes a URI and creates a URI object that's been normalized
+to handle common problems such as localhost vs 127.0.0.1
+
+=cut
+
+sub _NormalizeHost {
+
+    my $uri= URI->new(shift);
+    $uri->host('127.0.0.1') if $uri->host eq 'localhost';
+
+    return $uri;
+
+}
+
+sub IsPossibleCSRF {
+    my $ARGS = shift;
+
+    # If first request on this session is to a REST endpoint, then
+    # whitelist the REST endpoints -- and explicitly deny non-REST
+    # endpoints.  We do this because using a REST cookie in a browser
+    # would open the user to CSRF attacks to the REST endpoints.
+    my $path = $HTML::Mason::Commands::r->path_info;
+    $HTML::Mason::Commands::session{'REST'} = $path =~ m{^/+REST/\d+\.\d+(/|$)}
+        unless defined $HTML::Mason::Commands::session{'REST'};
+
+    if ($HTML::Mason::Commands::session{'REST'}) {
+        return 0 if $path =~ m{^/+REST/\d+\.\d+(/|$)};
+        my $why = <<EOT;
+This login session belongs to a REST client, and cannot be used to
+access non-REST interfaces of RT for security reasons.
+EOT
+        my $details = <<EOT;
+Please log out and back in to obtain a session for normal browsing.  If
+you understand the security implications, disabling RT's CSRF protection
+will remove this restriction.
+EOT
+        chomp $details;
+        HTML::Mason::Commands::Abort( $why, Details => $details );
+    }
+
+    return 0 if IsCompCSRFWhitelisted(
+        $HTML::Mason::Commands::m->request_comp->path,
+        $ARGS
+    );
+
+    # if there is no Referer header then assume the worst
+    return (1,
+            "your browser did not supply a Referrer header", # loc
+        ) if !$ENV{HTTP_REFERER};
+
+    my ($whitelisted, $browser, $configs) = IsRefererCSRFWhitelisted($ENV{HTTP_REFERER});
+    return 0 if $whitelisted;
+
+    if ( @$configs > 1 ) {
+        return (1,
+                "the Referrer header supplied by your browser ([_1]) is not allowed by RT's configured hostname ([_2]) or whitelisted hosts ([_3])", # loc
+                $browser->host_port,
+                shift @$configs,
+                join(', ', @$configs) );
+    }
+
+    return (1,
+            "the Referrer header supplied by your browser ([_1]) is not allowed by RT's configured hostname ([_2])", # loc
+            $browser->host_port,
+            $configs->[0]);
+}
+
+sub ExpandCSRFToken {
+    my $ARGS = shift;
+
+    my $token = delete $ARGS->{CSRF_Token};
+    return unless $token;
+
+    my $data = $HTML::Mason::Commands::session{'CSRF'}{$token};
+    return unless $data;
+    return unless $data->{path} eq $HTML::Mason::Commands::r->path_info;
+
+    my $user = $HTML::Mason::Commands::session{'CurrentUser'}->UserObj;
+    return unless $user->ValidateAuthString( $data->{auth}, $token );
+
+    %{$ARGS} = %{$data->{args}};
+    $HTML::Mason::Commands::DECODED_ARGS = $ARGS;
+
+    # We explicitly stored file attachments with the request, but not in
+    # the session yet, as that would itself be an attack.  Put them into
+    # the session now, so they'll be visible.
+    if ($data->{attach}) {
+        my $filename = $data->{attach}{filename};
+        my $mime     = $data->{attach}{mime};
+        $HTML::Mason::Commands::session{'Attachments'}{$filename}
+            = $mime;
+    }
+
+    return 1;
+}
+
+sub StoreRequestToken {
+    my $ARGS = shift;
+
+    my $token = Digest::MD5::md5_hex(time . {} . $$ . rand(1024));
+    my $user = $HTML::Mason::Commands::session{'CurrentUser'}->UserObj;
+    my $data = {
+        auth => $user->GenerateAuthString( $token ),
+        path => $HTML::Mason::Commands::r->path_info,
+        args => $ARGS,
+    };
+    if ($ARGS->{Attach}) {
+        my $attachment = HTML::Mason::Commands::MakeMIMEEntity( AttachmentFieldName => 'Attach' );
+        my $file_path = delete $ARGS->{'Attach'};
+        $data->{attach} = {
+            filename => Encode::decode_utf8("$file_path"),
+            mime     => $attachment,
+        };
+    }
+
+    $HTML::Mason::Commands::session{'CSRF'}->{$token} = $data;
+    $HTML::Mason::Commands::session{'i'}++;
+    return $token;
+}
+
+sub MaybeShowInterstitialCSRFPage {
+    my $ARGS = shift;
+
+    return unless RT->Config->Get('RestrictReferrer');
+
+    # Deal with the form token provided by the interstitial, which lets
+    # browsers which never set referer headers still use RT, if
+    # painfully.  This blows values into ARGS
+    return if ExpandCSRFToken($ARGS);
+
+    my ($is_csrf, $msg, @loc) = IsPossibleCSRF($ARGS);
+    return if !$is_csrf;
+
+    $RT::Logger->notice("Possible CSRF: ".RT::CurrentUser->new->loc($msg, @loc));
+
+    my $token = StoreRequestToken($ARGS);
+    $HTML::Mason::Commands::m->comp(
+        '/Elements/CSRF',
+        OriginalURL => RT->Config->Get('WebPath') . $HTML::Mason::Commands::r->path_info,
+        Reason => HTML::Mason::Commands::loc( $msg, @loc ),
+        Token => $token,
+    );
+    # Calls abort, never gets here
+}
+
 package HTML::Mason::Commands;
 
 use vars qw/$r $m %session/;
@@ -1418,6 +1670,7 @@ sub CreateTicket {
             my $cfid = $1;
 
             my $cf = RT::CustomField->new( $session{'CurrentUser'} );
+            $cf->SetContextObject( $Queue );
             $cf->Load($cfid);
             unless ( $cf->id ) {
                 $RT::Logger->error( "Couldn't load custom field #" . $cfid );
@@ -2144,10 +2397,11 @@ sub ProcessTicketReminders {
 
     if ( $args->{'update-reminders'} ) {
         while ( my $reminder = $reminder_collection->Next ) {
-            if (   $reminder->Status ne 'resolved' && $args->{ 'Complete-Reminder-' . $reminder->id } ) {
+            my $resolve_status = $reminder->QueueObj->Lifecycle->ReminderStatusOnResolve;
+            if (   $reminder->Status ne $resolve_status && $args->{ 'Complete-Reminder-' . $reminder->id } ) {
                 $Ticket->Reminders->Resolve($reminder);
             }
-            elsif ( $reminder->Status eq 'resolved' && !$args->{ 'Complete-Reminder-' . $reminder->id } ) {
+            elsif ( $reminder->Status eq $resolve_status && !$args->{ 'Complete-Reminder-' . $reminder->id } ) {
                 $Ticket->Reminders->Open($reminder);
             }
 
@@ -2239,6 +2493,7 @@ sub ProcessObjectCustomFieldUpdates {
 
             foreach my $cf ( keys %{ $custom_fields_to_mod{$class}{$id} } ) {
                 my $CustomFieldObj = RT::CustomField->new( $session{'CurrentUser'} );
+                $CustomFieldObj->SetContextObject($Object);
                 $CustomFieldObj->LoadById($cf);
                 unless ( $CustomFieldObj->id ) {
                     $RT::Logger->warning("Couldn't load custom field #$cf");
@@ -2851,50 +3106,71 @@ sub ScrubHTML {
 
 =head2 _NewScrubber
 
-Returns a new L<HTML::Scrubber> object.  Override this if you insist on
-letting more HTML through.
+Returns a new L<HTML::Scrubber> object.
+
+If you need to be more lax about what HTML tags and attributes are allowed,
+create C</opt/rt4/local/lib/RT/Interface/Web_Local.pm> with something like the
+following:
+
+    package HTML::Mason::Commands;
+    # Let tables through
+    push @SCRUBBER_ALLOWED_TAGS, qw(TABLE THEAD TBODY TFOOT TR TD TH);
+    1;
 
 =cut
 
+our @SCRUBBER_ALLOWED_TAGS = qw(
+    A B U P BR I HR BR SMALL EM FONT SPAN STRONG SUB SUP STRIKE H1 H2 H3 H4 H5
+    H6 DIV UL OL LI DL DT DD PRE BLOCKQUOTE BDO
+);
+
+our %SCRUBBER_ALLOWED_ATTRIBUTES = (
+    # Match http, ftp and relative urls
+    # XXX: we also scrub format strings with this module then allow simple config options
+    href   => qr{^(?:http:|ftp:|https:|/|__Web(?:Path|BaseURL|URL)__)}i,
+    face   => 1,
+    size   => 1,
+    target => 1,
+    style  => qr{
+        ^(?:\s*
+            (?:(?:background-)?color: \s*
+                    (?:rgb\(\s* \d+, \s* \d+, \s* \d+ \s*\) |   # rgb(d,d,d)
+                       \#[a-f0-9]{3,6}                      |   # #fff or #ffffff
+                       [\w\-]+                                  # green, light-blue, etc.
+                       )                            |
+               text-align: \s* \w+                  |
+               font-size: \s* [\w.\-]+              |
+               font-family: \s* [\w\s"',.\-]+       |
+               font-weight: \s* [\w\-]+             |
+
+               # MS Office styles, which are probably fine.  If we don't, then any
+               # associated styles in the same attribute get stripped.
+               mso-[\w\-]+?: \s* [\w\s"',.\-]+
+            )\s* ;? \s*)
+         +$ # one or more of these allowed properties from here 'till sunset
+    }ix,
+    dir    => qr/^(rtl|ltr)$/i,
+    lang   => qr/^\w+(-\w+)?$/,
+);
+
+our %SCRUBBER_RULES = ();
+
 sub _NewScrubber {
     require HTML::Scrubber;
     my $scrubber = HTML::Scrubber->new();
     $scrubber->default(
         0,
         {
-            '*'    => 0,
-            id     => 1,
-            class  => 1,
-            # Match http, ftp and relative urls
-            # XXX: we also scrub format strings with this module then allow simple config options
-            href   => qr{^(?:http:|ftp:|https:|/|__Web(?:Path|BaseURL|URL)__)}i,
-            face   => 1,
-            size   => 1,
-            target => 1,
-            style  => qr{
-                ^(?:\s*
-                    (?:(?:background-)?color: \s*
-                            (?:rgb\(\s* \d+, \s* \d+, \s* \d+ \s*\) |   # rgb(d,d,d)
-                               \#[a-f0-9]{3,6}                      |   # #fff or #ffffff
-                               [\w\-]+                                  # green, light-blue, etc.
-                               )                            |
-                       text-align: \s* \w+                  |
-                       font-size: \s* [\w.\-]+              |
-                       font-family: \s* [\w\s"',.\-]+       |
-                       font-weight: \s* [\w\-]+             |
-
-                       # MS Office styles, which are probably fine.  If we don't, then any
-                       # associated styles in the same attribute get stripped.
-                       mso-[\w\-]+?: \s* [\w\s"',.\-]+
-                    )\s* ;? \s*)
-                 +$ # one or more of these allowed properties from here 'till sunset
-            }ix,
-        }
+            %SCRUBBER_ALLOWED_ATTRIBUTES,
+            '*' => 0, # require attributes be explicitly allowed
+        },
     );
     $scrubber->deny(qw[*]);
-    $scrubber->allow(
-        qw[A B U P BR I HR BR SMALL EM FONT SPAN STRONG SUB SUP STRIKE H1 H2 H3 H4 H5 H6 DIV UL OL LI DL DT DD PRE BLOCKQUOTE]
-    );
+    $scrubber->allow(@SCRUBBER_ALLOWED_TAGS);
+    $scrubber->rules(%SCRUBBER_RULES);
+
+    # Scrubbing comments is vital since IE conditional comments can contain
+    # arbitrary HTML and we'd pass it right on through.
     $scrubber->comment(0);
 
     return $scrubber;
index 69eee60..a740167 100644 (file)
@@ -69,12 +69,12 @@ sub DefaultHandlerArgs  { (
     ],
     default_escape_flags => 'h',
     data_dir             => "$RT::MasonDataDir",
-    allow_globals        => [qw(%session)],
+    allow_globals        => [qw(%session $DECODED_ARGS)],
     # Turn off static source if we're in developer mode.
     static_source        => (RT->Config->Get('DevelMode') ? '0' : '1'), 
     use_object_files     => (RT->Config->Get('DevelMode') ? '0' : '1'), 
     autoflush            => 0,
-    error_format         => (RT->Config->Get('DevelMode') ? 'html': 'brief'),
+    error_format         => (RT->Config->Get('DevelMode') ? 'html': 'rt_error'),
     request_class        => 'RT::Interface::Web::Request',
     named_component_subs => $INC{'Devel/Cover.pm'} ? 1 : 0,
 ) };
@@ -116,6 +116,7 @@ sub NewHandler {
   
     $handler->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
     $handler->interp->set_escape( u => \&RT::Interface::Web::EscapeURI  );
+    $handler->interp->set_escape( j => \&RT::Interface::Web::EscapeJS   );
     return($handler);
 }
 
@@ -202,6 +203,13 @@ sub CleanupRequest {
 }
 
 
+sub HTML::Mason::Exception::as_rt_error {
+    my ($self) = @_;
+    $RT::Logger->error( $self->full_message );
+    return "An internal RT error has occurred.  Your administrator can find more details in RT's log files.";
+}
+
+
 # PSGI App
 
 use RT::Interface::Web::Handler;
index e2ec1e5..2cfc889 100755 (executable)
@@ -92,8 +92,8 @@ sub TraversePrePost {
 
 =head2 GetReferencedQueues
 
-Returns a hash reference with keys each queue name referenced in a clause in
-the key (even if it's "Queue != 'Foo'"), and values all 1.
+Returns a hash reference; each queue referenced with an '=' operation
+will appear as a key whose value is 1.
 
 =cut
 
@@ -110,10 +110,12 @@ sub GetReferencedQueues {
             return unless $node->isLeaf;
 
             my $clause = $node->getNodeValue();
+            return unless $clause->{Key} eq 'Queue';
+            return unless $clause->{Op} eq '=';
 
-            if ( $clause->{Key} eq 'Queue' ) {
-                $queues->{ $clause->{Value} } = 1;
-            };
+            my $value = $clause->{Value};
+            $value =~ s/\\(.)/$1/g if $value =~ s/^'(.*)'$/$1/;
+            $queues->{ $value } = 1;
         }
     );
 
@@ -275,7 +277,7 @@ sub ParseSQL {
             $value = "'$value'";
         }
 
-        if ($key =~ s/(['\\])/\\$1/g or $key =~ /\s/) {
+        if ($key =~ s/(['\\])/\\$1/g or $key =~ /[^{}\w\.]/) {
             $key = "'$key'";
         }
 
index edb1795..056599e 100644 (file)
@@ -367,6 +367,28 @@ sub DefaultOnMerge {
     return $self->DefaultStatus('on_merge');
 }
 
+=head3 ReminderStatusOnOpen
+
+Returns the status that should be used when reminders are opened.
+
+=cut
+
+sub ReminderStatusOnOpen {
+    my $self = shift;
+    return $self->DefaultStatus('reminder_on_open') || 'open';
+}
+
+=head3 ReminderStatusOnResolve
+
+Returns the status that should be used when reminders are resolved.
+
+=cut
+
+sub ReminderStatusOnResolve {
+    my $self = shift;
+    return $self->DefaultStatus('reminder_on_resolve') || 'resolved';
+}
+
 =head2 Transitions, rights, labels and actions.
 
 =head3 Transitions
index 0d8ed2f..ccc72d7 100644 (file)
@@ -91,15 +91,17 @@ sub Limit  {
 
     if ( ($args{'FIELD'} eq 'Target') or 
         ($args{'FIELD'} eq 'LocalTarget') ) {
-       $self->OrderBy (ALIAS => 'main',
-                       FIELD => 'Base',
-                       ORDER => 'ASC');
+       $self->OrderByCols(
+            { ALIAS => 'main', FIELD => 'LocalBase', ORDER => 'ASC' },
+            { ALIAS => 'main', FIELD => 'Base', ORDER => 'ASC' },
+        );
     }
     elsif ( ($args{'FIELD'} eq 'Base') or 
            ($args{'FIELD'} eq 'LocalBase') ) {
-       $self->OrderBy (ALIAS => 'main',
-                       FIELD => 'Target',
-                       ORDER => 'ASC');
+       $self->OrderByCols(
+            { ALIAS => 'main', FIELD => 'LocalTarget', ORDER => 'ASC' },
+            { ALIAS => 'main', FIELD => 'Target', ORDER => 'ASC' },
+        );
     }
     
 
index 0b815ae..61bc355 100644 (file)
@@ -137,7 +137,19 @@ Returns the CustomField Object which has the id returned by CustomField
 sub CustomFieldObj {
     my $self = shift;
     my $id = shift || $self->CustomField;
+
+    # To find out the proper context object to load the CF with, we need
+    # data from the CF -- namely, the record class.  Go find that as the
+    # system user first.
+    my $system_CF = RT::CustomField->new( RT->SystemUser );
+    $system_CF->Load( $id );
+    my $class = $system_CF->RecordClassFromLookupType;
+
+    my $obj = $class->new( $self->CurrentUser );
+    $obj->Load( $self->ObjectId );
+
     my $CF = RT::CustomField->new( $self->CurrentUser );
+    $CF->SetContextObject( $obj );
     $CF->Load( $id );
     return $CF;
 }
index 0fd9d73..98714a0 100644 (file)
@@ -251,6 +251,8 @@ my $re_ip_serialized = qr/$re_ip_sunit(?:\.$re_ip_sunit){3}/;
 sub Content {
     my $self = shift;
 
+    return undef unless $self->CustomFieldObj->CurrentUserHasRight('SeeCustomField');
+
     my $content = $self->_Value('Content');
     if (   $self->CustomFieldObj->Type eq 'IPAddress'
         || $self->CustomFieldObj->Type eq 'IPAddressRange' )
@@ -364,11 +366,11 @@ sub _FillInTemplateURL {
     # special case, whole value should be an URL
     if ( $url =~ /^__CustomField__/ ) {
         my $value = $self->Content;
-        # protect from javascript: URLs
-        if ( $value =~ /^\s*javascript:/i ) {
+        # protect from potentially malicious URLs
+        if ( $value =~ /^\s*(?:javascript|data):/i ) {
             my $object = $self->Object;
             $RT::Logger->error(
-                "Dangerouse value with JavaScript in custom field '". $self->CustomFieldObj->Name ."'"
+                "Potentially dangerous URL type in custom field '". $self->CustomFieldObj->Name ."'"
                 ." on ". ref($object) ." #". $object->id
             );
             return undef;
index 3cb87c4..406df92 100755 (executable)
@@ -692,6 +692,7 @@ sub TicketTransactionCustomFields {
 
     my $cfs = RT::CustomFields->new( $self->CurrentUser );
     if ( $self->CurrentUserHasRight('SeeQueue') ) {
+        $cfs->SetContextObject( $self );
        $cfs->LimitToGlobalOrObjectId( $self->Id );
        $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' );
         $cfs->ApplySortOrder;
@@ -1249,6 +1250,17 @@ sub CurrentUserHasRight {
 
 }
 
+=head2 CurrentUserCanSee
+
+Returns true if the current user can see the queue, using SeeQueue
+
+=cut
+
+sub CurrentUserCanSee {
+    my $self = shift;
+
+    return $self->CurrentUserHasRight('SeeQueue');
+}
 
 
 =head2 HasRight
index e7c28a8..2b66325 100644 (file)
@@ -137,7 +137,8 @@ sub Open {
     my $self = shift;
     my $reminder = shift;
 
-    my ( $status, $msg ) = $reminder->SetStatus('open');
+    my ( $status, $msg ) =
+      $reminder->SetStatus( $reminder->QueueObj->Lifecycle->ReminderStatusOnOpen );
     $self->TicketObj->_NewTransaction(
         Type => 'OpenReminder',
         Field => 'RT::Ticket',
@@ -149,7 +150,8 @@ sub Open {
 sub Resolve {
     my $self = shift;
     my $reminder = shift;
-    my ( $status, $msg ) = $reminder->SetStatus('resolved');
+    my ( $status, $msg ) =
+      $reminder->SetStatus( $reminder->QueueObj->Lifecycle->ReminderStatusOnResolve );
     $self->TicketObj->_NewTransaction(
         Type => 'ResolveReminder',
         Field => 'RT::Ticket',
index af6e4ed..de40dbd 100644 (file)
@@ -89,13 +89,7 @@ sub Groupings {
         foreach my $id (keys %$queues) {
             my $queue = RT::Queue->new( $self->CurrentUser );
             $queue->Load($id);
-            unless ($queue->id) {
-                # XXX TODO: This ancient code dates from a former developer
-                # we have no idea what it means or why cfqueues are so encoded.
-                $id =~ s/^.'*(.*).'*$/$1/;
-                $queue->Load($id);
-            }
-            $CustomFields->LimitToQueue($queue->Id);
+            $CustomFields->LimitToQueue($queue->Id) if $queue->Id;
         }
         $CustomFields->LimitToGlobal;
         while ( my $CustomField = $CustomFields->Next ) {
index f3eeb4d..87754c4 100644 (file)
@@ -83,6 +83,10 @@ sub LabelValue {
     return $value;
 }
 
+sub ObjectType {
+    return 'RT::Ticket';
+}
+
 RT::Base->_ImportOverlays();
 
 1;
index d513e0a..9506616 100755 (executable)
@@ -514,13 +514,35 @@ sub _Set {
     }
 
 
-    if (length($args{Value})) {
+    if (exists $args{Value}) {
         if ($args{Field} eq 'CustomIsApplicableCode' || $args{Field} eq 'CustomPrepareCode' || $args{Field} eq 'CustomCommitCode') {
             unless ( $self->CurrentUser->HasRight( Object => $RT::System,
                                                    Right  => 'ExecuteCode' ) ) {
                 return ( 0, $self->loc('Permission Denied') );
             }
         }
+        elsif ($args{Field} eq 'Queue') {
+            if ($args{Value}) {
+                # moving to another queue
+                my $queue = RT::Queue->new( $self->CurrentUser );
+                $queue->Load($args{Value});
+                unless ($queue->Id and $queue->CurrentUserHasRight('ModifyScrips')) {
+                    return ( 0, $self->loc('Permission Denied') );
+                }
+            } else {
+                # moving to global
+                unless ($self->CurrentUser->HasRight( Object => RT->System, Right => 'ModifyScrips' )) {
+                    return ( 0, $self->loc('Permission Denied') );
+                }
+            }
+        }
+        elsif ($args{Field} eq 'Template') {
+            my $template = RT::Template->new( $self->CurrentUser );
+            $template->Load($args{Value});
+            unless ($template->Id and $template->CurrentUserCanRead) {
+                return ( 0, $self->loc('Permission Denied') );
+            }
+        }
     }
 
     return $self->__Set(@_);
index 02d4c50..3e98551 100644 (file)
@@ -105,10 +105,14 @@ sub JoinTransactions {
         TABLE2 => 'Transactions',
         FIELD2 => 'ObjectId',
     );
+
+    my $item = $self->NewItem;
+    my $object_type = $item->can('ObjectType') ? $item->ObjectType : ref $item;
+
     $self->RT::SearchBuilder::Limit(
         LEFTJOIN => $alias,
         FIELD    => 'ObjectType',
-        VALUE    => ref $self->NewItem,
+        VALUE    => $object_type,
     );
     $self->{'_sql_aliases'}{'transactions'} = $alias
         unless $args{'New'};
@@ -127,6 +131,19 @@ sub OrderByCols {
     return $self->SUPER::OrderByCols( @sort );
 }
 
+# If we're setting RowsPerPage or FirstRow, ensure we get a natural number or undef.
+sub RowsPerPage {
+    my $self = shift;
+    return if @_ and defined $_[0] and $_[0] =~ /\D/;
+    return $self->SUPER::RowsPerPage(@_);
+}
+
+sub FirstRow {
+    my $self = shift;
+    return if @_ and defined $_[0] and $_[0] =~ /\D/;
+    return $self->SUPER::FirstRow(@_);
+}
+
 =head2 LimitToEnabled
 
 Only find items that haven't been disabled
index 10d3536..40c73b3 100644 (file)
@@ -351,6 +351,8 @@ sub CastObjectsToRecords
     } elsif ( UNIVERSAL::isa( $targets, 'SCALAR' ) || !ref $targets ) {
         $targets = $$targets if ref $targets;
         my ($class, $id) = split /-/, $targets;
+        RT::Shredder::Exception->throw( "Unsupported class $class" )
+              unless $class =~ /^\w+(::\w+)*$/;
         $class = 'RT::'. $class unless $class =~ /^RTx?::/i;
         eval "require $class";
         die "Couldn't load '$class' module" if $@;
index e70d207..ad9af6a 100644 (file)
@@ -167,6 +167,7 @@ sub LoadByName
 {
     my $self = shift;
     my $name = shift or return (0, "Name not specified");
+    $name =~ /^\w+(::\w+)*$/ or return (0, "Invalid plugin name");
 
     local $@;
     my $plugin = "RT::Shredder::Plugin::$name";
index c2ec445..2c0d068 100644 (file)
@@ -91,6 +91,7 @@ sub __DependsOn
 
 # Custom Fields
     $objs = RT::CustomFields->new( $self->CurrentUser );
+    $objs->SetContextObject( $self );
     $objs->LimitToQueue( $self->id );
     push( @$list, $objs );
 
index 158547a..117cc3f 100755 (executable)
@@ -96,10 +96,34 @@ sub _Accessible {
 
 sub _Set {
     my $self = shift;
+    my %args = (
+        Field => undef,
+        Value => undef,
+        @_,
+    );
     
     unless ( $self->CurrentUserHasQueueRight('ModifyTemplate') ) {
         return ( 0, $self->loc('Permission Denied') );
     }
+
+    if (exists $args{Value}) {
+        if ($args{Field} eq 'Queue') {
+            if ($args{Value}) {
+                # moving to another queue
+                my $queue = RT::Queue->new( $self->CurrentUser );
+                $queue->Load($args{Value});
+                unless ($queue->Id and $queue->CurrentUserHasRight('ModifyTemplate')) {
+                    return ( 0, $self->loc('Permission Denied') );
+                }
+            } else {
+                # moving to global
+                unless ($self->CurrentUser->HasRight( Object => RT->System, Right => 'ModifyTemplate' )) {
+                    return ( 0, $self->loc('Permission Denied') );
+                }
+            }
+        }
+    }
+
     return $self->SUPER::_Set( @_ );
 }
 
index 0d6da1b..7d69dd6 100644 (file)
@@ -409,7 +409,11 @@ sub bootstrap_db {
         $args{$forceopt}=1;
     }
 
-    return if $args{nodb};
+    # Short-circuit the rest of ourselves if we don't want a db
+    if ($args{nodb}) {
+        __drop_database();
+        return;
+    }
 
     my $db_type = RT->Config->Get('DatabaseType');
     __create_database();
@@ -556,6 +560,13 @@ sub __drop_database {
         RT::Handle->SystemDSN,
         $ENV{RT_DBA_USER}, $ENV{RT_DBA_PASSWORD}
     );
+
+    # We ignore errors intentionally by not checking the return value of
+    # DropDatabase below, so let's also suppress DBI's printing of errors when
+    # we overzealously drop.
+    local $dbh->{PrintError} = 0;
+    local $dbh->{PrintWarn} = 0;
+
     RT::Handle->DropDatabase( $dbh );
     $dbh->disconnect if $my_dbh;
 }
@@ -1276,8 +1287,10 @@ sub started_ok {
 
     require RT::Test::Web;
 
-    if ($rttest_opt{nodb}) {
-        die "you are trying to use a test web server without db, try use noinitialdata => 1 instead";
+    if ($rttest_opt{nodb} and not $rttest_opt{server_ok}) {
+        die "You are trying to use a test web server without a database. "
+           ."You may want noinitialdata => 1 instead. "
+           ."Pass server_ok => 1 if you know what you're doing.";
     }
 
 
@@ -1298,11 +1311,31 @@ sub test_app {
     my $self = shift;
     my %server_opt = @_;
 
-    require RT::Interface::Web::Handler;
-    my $app = RT::Interface::Web::Handler->PSGIApp;
+    my $app;
+
+    my $warnings = "";
+    open( my $warn_fh, ">", \$warnings );
+    local *STDERR = $warn_fh;
+
+    if ($server_opt{variant} and $server_opt{variant} eq 'rt-server') {
+        $app = do {
+            my $file = "$RT::SbinPath/rt-server";
+            my $psgi = do $file;
+            unless ($psgi) {
+                die "Couldn't parse $file: $@" if $@;
+                die "Couldn't do $file: $!"    unless defined $psgi;
+                die "Couldn't run $file"       unless $psgi;
+            }
+            $psgi;
+        };
+    } else {
+        require RT::Interface::Web::Handler;
+        $app = RT::Interface::Web::Handler->PSGIApp;
+    }
 
     require Plack::Middleware::Test::StashWarnings;
-    $app = Plack::Middleware::Test::StashWarnings->wrap($app);
+    my $stashwarnings = Plack::Middleware::Test::StashWarnings->new;
+    $app = $stashwarnings->wrap($app);
 
     if ($server_opt{basic_auth}) {
         require Plack::Middleware::Auth::Basic;
@@ -1314,6 +1347,10 @@ sub test_app {
             }
         );
     }
+
+    close $warn_fh;
+    $stashwarnings->add_warning( $warnings ) if $warnings;
+
     return $app;
 }
 
@@ -1346,7 +1383,8 @@ sub start_plack_server {
         my $Tester = Test::Builder->new;
         $Tester->ok(1, "started plack server ok");
 
-        __reconnect_rt();
+        __reconnect_rt()
+            unless $rttest_opt{nodb};
         return ("http://localhost:$port", RT::Test::Web->new);
     }
 
index 28ca3b8..c2d9ac3 100644 (file)
@@ -52,15 +52,19 @@ use strict;
 use warnings;
 
 use base qw(Test::WWW::Mechanize);
+use Scalar::Util qw(weaken);
 
-require RT::Test;
+BEGIN { require RT::Test; }
 require Test::More;
 
+my $instance;
+
 sub new {
     my ($class, @args) = @_;
 
     push @args, app => $RT::Test::TEST_APP if $RT::Test::TEST_APP;
-    my $self = $class->SUPER::new(@args);
+    my $self = $instance = $class->SUPER::new(@args);
+    weaken $instance;
     $self->cookie_jar(HTTP::Cookies->new);
 
     return $self;
@@ -100,6 +104,7 @@ sub login {
         Test::More::diag("error: page has no Logout");
         return 0;
     }
+    RT::Interface::Web::EscapeUTF8(\$user);
     unless ( $self->content =~ m{<span class="current-user">\Q$user\E</span>}i ) {
         Test::More::diag("Page has no user name");
         return 0;
@@ -370,4 +375,10 @@ sub DESTROY {
     }
 }
 
+END {
+    return unless $instance;
+    return if RT::Test->builder->{Original_Pid} != $$;
+    $instance->no_warnings_ok if !$RT::Test::Web::DESTROY++;
+}
+
 1;
index 27bdc48..de8a396 100755 (executable)
@@ -2438,7 +2438,7 @@ sub _Links {
     my $links = $self->{ $cache_key }
               = RT::Links->new( $self->CurrentUser );
     unless ( $self->CurrentUserHasRight('ShowTicket') ) {
-        $links->Limit( FIELD => 'id', VALUE => 0 );
+        $links->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' );
         return $links;
     }
 
@@ -3594,6 +3594,16 @@ sub CurrentUserHasRight {
 }
 
 
+=head2 CurrentUserCanSee
+
+Returns true if the current user can see the ticket, using ShowTicket
+
+=cut
+
+sub CurrentUserCanSee {
+    my $self = shift;
+    return $self->CurrentUserHasRight('ShowTicket');
+}
 
 =head2 HasRight
 
@@ -3706,7 +3716,9 @@ sub Transactions {
 
 sub TransactionCustomFields {
     my $self = shift;
-    return $self->QueueObj->TicketTransactionCustomFields;
+    my $cfs = $self->QueueObj->TicketTransactionCustomFields;
+    $cfs->SetContextObject( $self );
+    return $cfs;
 }
 
 
index db54b52..4e2415b 100755 (executable)
@@ -1132,6 +1132,12 @@ sub _GroupMembersJoin {
         FIELD2          => 'GroupId',
         ENTRYAGGREGATOR => 'AND',
     );
+    $self->SUPER::Limit(
+        $args{'Left'} ? (LEFTJOIN => $alias) : (),
+        ALIAS => $alias,
+        FIELD => 'Disabled',
+        VALUE => 0,
+    );
 
     $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} } = $alias
         unless $args{'New'};
@@ -1296,6 +1302,12 @@ sub _WatcherMembershipLimit {
         FIELD2 => 'id'
     );
 
+    $self->Limit(
+        ALIAS => $groupmembers,
+        FIELD => 'Disabled',
+        VALUE => 0,
+    );
+
     $self->Join(
         ALIAS1 => $memberships,
         FIELD1 => 'MemberId',
@@ -1303,6 +1315,13 @@ sub _WatcherMembershipLimit {
         FIELD2 => 'id'
     );
 
+    $self->Limit(
+        ALIAS => $memberships,
+        FIELD => 'Disabled',
+        VALUE => 0,
+    );
+
+
     $self->_CloseParen;
 
 }
@@ -1639,11 +1658,8 @@ sub _CustomFieldLimit {
             $self->_CloseParen;
         }
         else {
-            my $cf = RT::CustomField->new( $self->CurrentUser );
-            $cf->Load($field);
-
             # need special treatment for Date
-            if ( $cf->Type eq 'DateTime' && $op eq '=' ) {
+            if ( $cf and $cf->Type eq 'DateTime' and $op eq '=' ) {
 
                 if ( $value =~ /:/ ) {
                     # there is time speccified.
@@ -3647,7 +3663,11 @@ sub _RestrictionsToClauses {
         # here is where we store extra data, say if it's a keyword or
         # something.  (I.e. "TYPE SPECIFIC STUFF")
 
-        push @{ $clause{$realfield} }, $data;
+        if (lc $ea eq 'none') {
+            $clause{$realfield} = [ $data ];
+        } else {
+            push @{ $clause{$realfield} }, $data;
+        }
     }
     return \%clause;
 }
index d0e6456..1f1bab1 100755 (executable)
@@ -512,7 +512,7 @@ sub Attachments {
     $self->{'attachments'} = RT::Attachments->new( $self->CurrentUser );
 
     unless ( $self->CurrentUserCanSee ) {
-        $self->{'attachments'}->Limit(FIELD => 'id', VALUE => '0');
+        $self->{'attachments'}->Limit(FIELD => 'id', VALUE => '0', SUBCLAUSE => 'acl');
         return $self->{'attachments'};
     }
 
@@ -714,6 +714,7 @@ sub BriefDescription {
 
         if ( $self->Field ) {
             my $cf = RT::CustomField->new( $self->CurrentUser );
+            $cf->SetContextObject( $self->Object );
             $cf->Load( $self->Field );
             $field = $cf->Name();
             $field = $self->loc('a custom field') if !defined($field);
@@ -1072,14 +1073,8 @@ sub CurrentUserCanSee {
         $cf->Load( $cf_id );
         return 0 unless $cf->CurrentUserHasRight('SeeCustomField');
     }
-    #if they ain't got rights to see, don't let em
-    elsif ( $self->__Value('ObjectType') eq "RT::Ticket" ) {
-        unless ( $self->CurrentUserHasRight('ShowTicket') ) {
-            return 0;
-        }
-    }
-
-    return 1;
+    # Defer to the object in question
+    return $self->Object->CurrentUserCanSee("Transaction");
 }
 
 
@@ -1103,7 +1098,7 @@ sub OldValue {
         return $Object->Content;
     }
     else {
-        return $self->__Value('OldValue');
+        return $self->_Value('OldValue');
     }
 }
 
@@ -1117,7 +1112,7 @@ sub NewValue {
         return $Object->Content;
     }
     else {
-        return $self->__Value('NewValue');
+        return $self->_Value('NewValue');
     }
 }
 
@@ -1207,6 +1202,7 @@ sub CustomFieldValues {
         #      do we want to cover this situation somehow here?
         unless ( defined $field && $field =~ /^\d+$/o ) {
             my $CFs = RT::CustomFields->new( $self->CurrentUser );
+            $CFs->SetContextObject( $self->Object );
             $CFs->Limit( FIELD => 'Name', VALUE => $field );
             $CFs->LimitToLookupType($self->CustomFieldLookupType);
             $CFs->LimitToGlobalOrObjectId($self->Object->QueueObj->id);
index 4af1cb0..fce0459 100644 (file)
@@ -130,7 +130,7 @@ sub FromURI {
     # Special case: integers passed in as URIs must be ticket ids
     if ($uri =~ /^(\d+)$/) {
        $scheme = "fsck.com-rt";
-    } elsif ($uri =~ /^((?:\w|\.|-)+?):/) {
+    } elsif ($uri =~ /^((?!javascript|data)(?:\w|\.|-)+?):/i) {
        $scheme = $1;
     }
     else {
index 503434a..0c09b7c 100644 (file)
@@ -188,7 +188,7 @@ Otherwise, return its URI
 sub HREF {
     my $self = shift;
     if ($self->IsLocal && $self->Object) {
-        return ( RT->Config->Get('WebURL') . "/Articles/Article/Display.html?id=".$self->Object->Id);
+        return ( RT->Config->Get('WebURL') . "Articles/Article/Display.html?id=".$self->Object->Id);
     }   
     else {
         return ($self->URI);
index 2a14cd1..9b4a826 100755 (executable)
@@ -1206,6 +1206,37 @@ sub HasRight {
     return $self->PrincipalObj->HasRight(@_);
 }
 
+=head2 CurrentUserCanSee [FIELD]
+
+Returns true if the current user can see the user, based on if it is
+public, ourself, or we have AdminUsers
+
+=cut
+
+sub CurrentUserCanSee {
+    my $self = shift;
+    my ($what) = @_;
+
+    # If it's public, fine.  Note that $what may be "transaction", which
+    # doesn't have an Accessible value, and thus falls through below.
+    if ( $self->_Accessible( $what, 'public' ) ) {
+        return 1;
+    }
+
+    # Users can see their own properties
+    elsif ( defined($self->Id) && $self->CurrentUser->Id == $self->Id ) {
+        return 1;
+    }
+
+    # If the user has the admin users right, that's also enough
+    elsif ( $self->CurrentUser->HasRight( Right => 'AdminUsers', Object => $RT::System) ) {
+        return 1;
+    }
+    else {
+        return 0;
+    }
+}
+
 =head2 CurrentUserCanModify RIGHT
 
 If the user has rights for this object, either because
@@ -1334,12 +1365,13 @@ sub Stylesheet {
 
     my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser);
 
+    if (RT::Interface::Web->ComponentPathIsSafe($style)) {
+        my @css_paths = map { $_ . '/NoAuth/css' } RT::Interface::Web->ComponentRoots;
 
-    my @css_paths = map { $_ . '/NoAuth/css' } RT::Interface::Web->ComponentRoots;
-
-    for my $css_path (@css_paths) {
-        if (-d "$css_path/$style") {
-            return $style
+        for my $css_path (@css_paths) {
+            if (-d "$css_path/$style") {
+                return $style
+            }
         }
     }
 
@@ -1409,6 +1441,12 @@ sub WatchedQueues {
                             FIELD => 'MemberId',
                             VALUE => $self->PrincipalId,
                           );
+    $watched_queues->Limit(
+                            ALIAS => $queues_alias,
+                            FIELD => 'Disabled',
+                            VALUE => 0,
+                          );
+
 
     $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
 
@@ -1447,7 +1485,9 @@ sub _Set {
     if ( $ret == 0 ) { return ( 0, $msg ); }
 
     if ( $args{'RecordTransaction'} == 1 ) {
-
+        if ($args{'Field'} eq "Password") {
+            $args{'Value'} = $Old = '********';
+        }
         my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
                                                Type => $args{'TransactionType'},
                                                Field     => $args{'Field'},
@@ -1473,25 +1513,9 @@ sub _Value {
     my $self  = shift;
     my $field = shift;
 
-    #if the field is public, return it.
-    if ( $self->_Accessible( $field, 'public' ) ) {
-        return ( $self->SUPER::_Value($field) );
-
-    }
-
-    #If the user wants to see their own values, let them
-    # TODO figure ouyt a better way to deal with this
-    elsif ( defined($self->Id) && $self->CurrentUser->Id == $self->Id ) {
-        return ( $self->SUPER::_Value($field) );
-    }
-
-    #If the user has the admin users right, return the field
-    elsif ( $self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
-        return ( $self->SUPER::_Value($field) );
-    } else {
-        return (undef);
-    }
-
+    # Defer to the abstraction above to know if the field can be read
+    return $self->SUPER::_Value($field) if $self->CurrentUserCanSee($field);
+    return undef;
 }
 
 =head2 FriendlyName
index f3b1b5c..2784fc7 100755 (executable)
@@ -188,6 +188,9 @@ sub MemberOfGroup {
                  FIELD1 => 'id',
                  ALIAS2 => $groupalias,
                  FIELD2 => 'MemberId' );
+    $self->Limit( ALIAS => $groupalias,
+                  FIELD => 'Disabled',
+                  VALUE => 0 );
 
     $self->Limit( ALIAS    => "$groupalias",
                   FIELD    => 'GroupId',
@@ -266,6 +269,11 @@ sub _JoinGroupMembers
         ALIAS2 => $principals,
         FIELD2 => 'id'
     );
+    $self->Limit(
+        ALIAS => $group_members,
+        FIELD => 'Disabled',
+        VALUE => 0,
+    ) if $args{'IncludeSubgroupMembers'};
 
     return $group_members;
 }
index b438202..45c3770 100644 (file)
@@ -91,6 +91,7 @@ if (grep { m/help/ } @ARGV) {
 
 require RT;
 RT->LoadConfig();
+RT->InitLogging();
 require Module::Refresh if RT->Config->Get('DevelMode');
 
 require RT::Handle;
index b438202..45c3770 100644 (file)
@@ -91,6 +91,7 @@ if (grep { m/help/ } @ARGV) {
 
 require RT;
 RT->LoadConfig();
+RT->InitLogging();
 require Module::Refresh if RT->Config->Get('DevelMode');
 
 require RT::Handle;
index c52116f..c0655db 100755 (executable)
@@ -74,7 +74,7 @@ should wipeout.
 
 =head2 --sqldump <filename>
 
-Outputs INSERT queiries into file. This dump can be used to restore data
+Outputs INSERT queries into file. This dump can be used to restore data
 after wiping out.
 
 By default creates files
index ebea86c..37ef32f 100644 (file)
@@ -189,6 +189,8 @@ File::ShareDir
 File::Spec 0.8
 HTML::Quoted
 HTML::Scrubber 0.08
+HTML::TreeBuilder
+HTML::FormatText
 Log::Dispatch 2.23
 Sys::Syslog 0.16
 Locale::Maketext 1.06
@@ -245,8 +247,6 @@ CGI::Emulate::PSGI
 .
 
 $deps{'MAILGATE'} = [ text_to_hash( << '.') ];
-HTML::TreeBuilder
-HTML::FormatText
 Getopt::Long
 LWP::UserAgent
 Pod::Usage
@@ -288,7 +288,7 @@ Test::Builder 0.90 # needed for is_passing
 Test::MockTime
 Log::Dispatch::Perl
 Test::WWW::Mechanize::PSGI
-Plack::Middleware::Test::StashWarnings
+Plack::Middleware::Test::StashWarnings 0.06
 Test::LongString
 .
 
@@ -339,6 +339,7 @@ Net::SMTP
 $deps{'DASHBOARDS'} = [ text_to_hash( << '.') ];
 HTML::RewriteAttributes 0.04
 MIME::Types
+URI 1.59
 .
 
 $deps{'GRAPHVIZ'} = [ text_to_hash( << '.') ];
index 78533c6..3386cd1 100755 (executable)
@@ -91,6 +91,7 @@ if (grep { m/help/ } @ARGV) {
 
 require RT;
 RT->LoadConfig();
+RT->InitLogging();
 require Module::Refresh if RT->Config->Get('DevelMode');
 
 require RT::Handle;
index b438202..45c3770 100644 (file)
@@ -91,6 +91,7 @@ if (grep { m/help/ } @ARGV) {
 
 require RT;
 RT->LoadConfig();
+RT->InitLogging();
 require Module::Refresh if RT->Config->Get('DevelMode');
 
 require RT::Handle;
index 96ddaf0..43ca956 100644 (file)
@@ -105,7 +105,7 @@ $topic
 % }
 % if ($Action) {
 % unless ($Action eq "Move" and grep {$_->getNodeValue->Id == $Modify} $Element->getAllChildren) {
-<li><input type="submit" name="<%$Prefix%>-<%$topic eq "root" ? 0 : $topic->Id%>" value="<&|/l&><%$Action%> here</&>" /></li>
+<li><input type="submit" name="<%$Prefix%>-<%$topic eq "root" ? 0 : $topic->Id%>" value="<% $Action eq 'Move' ? loc('Move here') : loc('Add here') %>" /></li>
 % }
 % }
 </ul>
index 20c3e9c..4ed86b6 100644 (file)
 <div class="hints">
 <&|/l&>RT can make this custom field's values into hyperlinks to another service.</&>
 <&|/l&>Fill in this field with a URL.</&>
-<&|/l, '<tt>__id__</tt>', '<tt>__CustomField__</tt>' &>RT will replace [_1] and [_2] with the record's id and the custom field's value, respectively.</&>
+<&|/l_unsafe, '<tt>__id__</tt>', '<tt>__CustomField__</tt>' &>RT will replace [_1] and [_2] with the record's id and the custom field's value, respectively.</&>
 </div></td></tr>
 
 <tr><td class="label"><&|/l&>Include page</&></td><td>
 <div class="hints">
 <&|/l&>RT can include content from another web service when showing this custom field.</&>
 <&|/l&>Fill in this field with a URL.</&>
-<&|/l, '<tt>__id__</tt>', '<tt>__CustomField__</tt>' &>RT will replace [_1] and [_2] with the record's id and the custom field's value, respectively.</&>
+<&|/l_unsafe, '<tt>__id__</tt>', '<tt>__CustomField__</tt>' &>RT will replace [_1] and [_2] with the record's id and the custom field's value, respectively.</&>
 <i><&|/l&>Some browsers may only load content from the same domain as your RT server.</&></i>
 </div></td></tr>
 
index aa7b622..d9d9134 100755 (executable)
@@ -128,6 +128,7 @@ if ( $MoveCustomFieldDown ) { {
 if ( $UpdateCFs ) {
     foreach my $cf_id ( @AddCustomField ) {
         my $CF = RT::CustomField->new( $session{'CurrentUser'} );
+        $CF->SetContextObject( $Object );
         $CF->Load( $cf_id );
         unless ( $CF->id ) {
             push @results, loc("Couldn't load CustomField #[_1]", $cf_id);
@@ -138,6 +139,7 @@ if ( $UpdateCFs ) {
     }
     foreach my $cf_id ( @RemoveCustomField ) {
         my $CF = RT::CustomField->new( $session{'CurrentUser'} );
+        $CF->SetContextObject( $Object );
         $CF->Load( $cf_id );
         unless ( $CF->id ) {
             push @results, loc("Couldn't load CustomField #[_1]", $cf_id);
@@ -153,6 +155,7 @@ $m->callback(CallbackName => 'UpdateExtraFields', Results => \@results, Object =
 my $applied_cfs = RT::CustomFields->new( $session{'CurrentUser'} );
 $applied_cfs->LimitToLookupType($lookup);
 $applied_cfs->LimitToGlobalOrObjectId($id);
+$applied_cfs->SetContextObject( $Object );
 $applied_cfs->ApplySortOrder;
 
 my $not_applied_cfs = RT::CustomFields->new( $session{'CurrentUser'} );
index e5b9908..e673593 100644 (file)
@@ -110,13 +110,13 @@ for my $category (@$Principals) {
                id="AddPrincipalForRights-<% lc $AddPrincipal %>" />
         <script type="text/javascript">
         jQuery(function() {
-            jQuery("#AddPrincipalForRights-<% lc $AddPrincipal %>").keyup(function(){
+            jQuery("#AddPrincipalForRights-"+<% lc $AddPrincipal |n,j%>).keyup(function(){
                 toggle_addprincipal_validity(this, true);
             });
 
 % if (lc $AddPrincipal eq 'group') {
-            jQuery("#AddPrincipalForRights-<% lc $AddPrincipal %>").autocomplete({
-                source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Groups",
+            jQuery("#AddPrincipalForRights-"+<% lc $AddPrincipal |n,j%>).autocomplete({
+                source: <% RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/Groups",
                 select: addprincipal_onselect,
                 change: addprincipal_onchange
             });
index d5e75c5..821ed57 100644 (file)
@@ -47,6 +47,6 @@
 %# END BPS TAGGED BLOCK }}}
 <div id="rt-portal">
 <&| /Widgets/TitleBox, title => 'RT Portal' &>
-<iframe src="http://bestpractical.com/rt/integration/news?utm_source=rt&utm_medium=iframe&utm_campaign=<%$RT::VERSION%>"></iframe>
+<iframe src="https://bestpractical.com/rt/integration/news?utm_source=rt&utm_medium=iframe&utm_campaign=<%$RT::VERSION%>"></iframe>
 </&>
 </div>
index f386ba5..8778dae 100755 (executable)
@@ -50,8 +50,8 @@
 <input type="text" value="" name="<% $Name %>Users" id="<% $Name %>Users" /><br />
 <script type="text/javascript">
 jQuery(function(){
-    jQuery("#<% $Name %>Users").autocomplete({
-        source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Users?return=Name;privileged=1;exclude=<% $user_ids |u %>",
+    jQuery("#"+<% $Name |n,j%>+"Users").autocomplete({
+        source: <% RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/Users?return=Name;privileged=1;exclude="+<% $user_ids |n,u,j %>,
         // Auto-submit once a user is chosen
         select: function( event, ui ) {
             jQuery(event.target).val(ui.item.value);
@@ -67,8 +67,8 @@ jQuery(function(){
 <input type="text" value="" name="<% $Name %>Groups" id="<% $Name %>Groups" /><br />
 <script type="text/javascript">
 jQuery(function(){
-    jQuery("#<% $Name %>Groups").autocomplete({
-        source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Groups?exclude=<% $group_ids |u %>",
+    jQuery("#"+<% $Name |n,j%>+"Groups").autocomplete({
+        source: <% RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/Groups?exclude="+<% $group_ids |n,u,j %>,
         // Auto-submit once a user is chosen
         select: function( event, ui ) {
             jQuery(event.target).val(ui.item.value);
index bd07b73..ef7395f 100755 (executable)
@@ -57,7 +57,7 @@
 <script type="text/javascript">
 jQuery(function(){
     jQuery("#autocomplete-GroupString").autocomplete({
-        source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Groups",
+        source: <% RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/Groups",
         // Auto-submit once a group is chosen
         select: function( event, ui ) {
             jQuery(event.target).val(ui.item.value);
index 2fe2d5a..dbc6fc5 100644 (file)
@@ -79,7 +79,7 @@ unless ($session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'Super
 
           <li>
               <tt><% $request->{Path} %></tt> - <i><&|/l, sprintf('%.4f', $seconds) &>[_1]s</&></i>
-              <a href="#" onclick="return hideshow('queries-<%$r%>');"><&|/l, $count &>Toggle [quant,_1,query,queries]</&></a>
+              <a href="#" onclick="return hideshow(<% "queries-$r" |n,j%>);"><&|/l, $count &>Toggle [quant,_1,query,queries]</&></a>
               <table id="queries-<%$r%>" class="tablesorter hidden">
                   <thead>
                       <tr>
@@ -115,7 +115,7 @@ unless ($session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'Super
                                        <br><tt>[<% join(", ", @$b) %>]</tt>
 %                                  }
 %                              }
-                               <a class="query-stacktrace-toggle" href="#" onclick="return hideshow('trace-<%$r%>-<%$s%>');"><&|/l &>Toggle stack trace</&></a>
+                               <a class="query-stacktrace-toggle" href="#" onclick="return hideshow(<% "trace-$r-$s" |n,j%>);"><&|/l &>Toggle stack trace</&></a>
                                <pre id="trace-<%$r%>-<%$s%>" class="hidden"><% $trace %></pre>
                            </td>
                        </tr>
index 8b84cf4..0d24fa0 100644 (file)
@@ -48,9 +48,6 @@
 <%ATTR>
 AutoFlush => 0
 </%ATTR>
-<%FLAGS>
-inherit => undef
-</%FLAGS>
 <%INIT>
 my $arg = $m->dhandler_arg;
 $m->abort(404) if $arg =~ m{\.\.|/|\\};
@@ -64,5 +61,5 @@ my $buf;
 while( read $fh, $buf, 1024*1024 ) {
     $m->out($buf);
 }
-return 0;
+$m->abort;
 </%INIT>
index ce93411..ae3b96e 100644 (file)
@@ -52,5 +52,5 @@ $Path => ''
 <& /Elements/Tabs &>
 <div class="error">
 % my $path_tag = q{<span class="file-path">} . $m->interp->apply_escapes($Path, 'h') . q{</span>};
-<&|/l, $path_tag &>Shredder needs a directory to write dumps to. Please ensure that the directory [_1] exists and that it is writable by your web server.</&>
+<&|/l_unsafe, $path_tag &>Shredder needs a directory to write dumps to. Please ensure that the directory [_1] exists and that it is writable by your web server.</&>
 </div>
index 11b876b..0da910d 100644 (file)
@@ -49,6 +49,6 @@
 $Object => undef
 </%ARGS>
 % my $name = (defined $Object->Filename and length $Object->Filename) ? $Object->Filename : loc("(no value)");
-<a href="<% RT->Config->Get('WebURL') %>/Ticket/Attachment/<% $Object->TransactionId %>/<% $Object->id %>/">
+<a href="<% RT->Config->Get('WebPath') %>/Ticket/Attachment/<% $Object->TransactionId %>/<% $Object->id %>/">
 <% loc('Attachment') %>(<% loc('id') %>:<% $Object->id %>, <% loc('Filename') %>: <% $name %>)
 </a>
index 13547ad..35f1aa8 100644 (file)
@@ -48,6 +48,6 @@
 <%ARGS>
 $Object => undef
 </%ARGS>
-<a href="<% RT->Config->Get('WebURL') %>/Ticket/Display.html?id=<% $Object->id %>">
+<a href="<% RT->Config->Get('WebPath') %>/Ticket/Display.html?id=<% $Object->id %>">
 <% loc('Ticket') %>(<% loc('id') %>:<% $Object->id %>, <% loc('Subject') %>: <% substr($Object->Subject, 0, 30) %>...)
 </a>
index f77169a..d7627eb 100644 (file)
@@ -48,6 +48,6 @@
 <%ARGS>
 $Object => undef
 </%ARGS>
-<a href="<% RT->Config->Get('WebURL') %>/Admin/Users/Modify.html?id=<% $Object->id %>">
+<a href="<% RT->Config->Get('WebPath') %>/Admin/Users/Modify.html?id=<% $Object->id %>">
 <% loc('User') %>(<% loc('id') %>:<% $Object->id %>, <% loc('Name') %>: <% $Object->Name %>)
 </a>
index b14f936..05dde17 100755 (executable)
 % if ($UserObj->id) {
 <& /Elements/EditCustomField, %ARGS, Object => $UserObj, CustomField => $CF &>
 % } else {
-<& /Elements/EditCustomField, %ARGS, NamePrefix => 'Object-RT::User-new-CustomField-', CustomField => $CF &>
+<& /Elements/EditCustomField, %ARGS, NamePrefix => 'Object-RT::User--CustomField-', CustomField => $CF &>
 % }
 </td></tr>
 % }
@@ -282,12 +282,6 @@ else {
 
        if ($val) {
                push @results, $msg;
-        foreach my $key ( keys %ARGS) {
-            # Convert custom fields on the "new" object to custom fields on the one we've just created
-            if ($key =~ /^Object-RT::User-new-CustomField-(.*)$/) {
-            $ARGS{'Object-RT::User-'.$val.'-CustomField-'.$1} = delete $ARGS{$key};
-            }
-        }
         push @results, ProcessObjectCustomFieldUpdates( ARGSRef => \%ARGS, Object => $UserObj );
        } else {
                push @results, loc('User could not be created: [_1]', $msg);
index a1e3fac..adcfeb5 100755 (executable)
@@ -62,7 +62,7 @@
 <script type="text/javascript">
 jQuery(function(){
     jQuery("#autocomplete-UserString").autocomplete({
-        source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Users?return=Name",
+        source: <% RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/Users?return=Name",
         // Auto-submit once a user is chosen
         select: function( event, ui ) {
             jQuery(event.target).val(ui.item.value);
index 75ad5e1..d2061da 100755 (executable)
@@ -63,9 +63,9 @@
 <input type="checkbox" class="checkbox" value="1" name="ShowRejected" <% defined($ARGS{'ShowRejected'}) && $ARGS{'ShowRejected'} && qq[checked="checked"] |n%> /> <&|/l&>Show denied requests</&><br />
 <input type="checkbox" class="checkbox" value="1" name="ShowDependent" <% defined($ARGS{'ShowDependent'}) && $ARGS{'ShowDependent'} && qq[checked="checked"] |n%> /> <&|/l&>Show requests awaiting other approvals</&><br />
 
-<&|/l, qq{<input size='15' class="ui-datepicker" value='}.($created_before->Unix > 0 &&$created_before->ISO(Timezone => 'user'))."' name='CreatedBefore' id='CreatedBefore' />"&>Only show approvals for requests created before [_1]</&><br />
+<&|/l_unsafe, qq{<input size='15' class="ui-datepicker" value='}.($created_before->Unix > 0 &&$created_before->ISO(Timezone => 'user'))."' name='CreatedBefore' id='CreatedBefore' />"&>Only show approvals for requests created before [_1]</&><br />
 
-<&|/l, qq{<input size='15' class="ui-datepicker" value='}.( $created_after->Unix >0 && $created_after->ISO(Timezone => 'user'))."' name='CreatedAfter' id='CreatedAfter' />"&>Only show approvals for requests created after [_1]</&>
+<&|/l_unsafe, qq{<input size='15' class="ui-datepicker" value='}.( $created_after->Unix >0 && $created_after->ISO(Timezone => 'user'))."' name='CreatedAfter' id='CreatedAfter' />"&>Only show approvals for requests created after [_1]</&>
 </&>
 
 <%init>
index 756aa2c..d14c330 100644 (file)
@@ -157,6 +157,7 @@ elsif ( $id eq 'new' ) {
             my $cfid = $1; 
         
             my $cf = RT::CustomField->new( $session{'CurrentUser'} );
+            $cf->SetContextObject( $ArticleObj );
             $cf->Load( $cfid );
             unless ( $cf->id ) {
                 $RT::Logger->error( "Couldn't load custom field #". $cfid );
index 807360b..82e9071 100644 (file)
 %# END BPS TAGGED BLOCK }}}
 <input type="hidden" name="EditTopics" value="1" />
 <select multiple size="10" name="Topics">
-<%perl>
-if (@Classes) {
-  $m->print("<optgroup label=\"Current classes (".join (' ',map {$_->Name} @Classes).")\">")
-    unless $OnlyThisClass;
-  $inTree->traverse(sub {
-    my $tree = shift;
-    my $topic = $tree->getNodeValue;
-    $m->print("<option value=\"".$topic->Id."\""
-      .(exists $topics{$topic->Id} ? " selected" : "").">"
-      .("&nbsp;" x ($tree->getDepth*5)).($topic->Name || loc("(no name)"))."</option>\n");
-  });
-}
-unless ($OnlyThisClass) {
-  my $class = $Classes[-1]->Id;
-  $otherTree->traverse(sub {
-    my $tree = shift;
-    my $topic = $tree->getNodeValue;
-    unless ($topic->ObjectId == $class) {
-      $class = $topic->ObjectId;
-      $m->print("</optgroup>\n");
-      my $c = RT::Class->new($session{'CurrentUser'});
-      $c->Load($topic->ObjectId);
-      $m->print("<optgroup label=\"".$c->Name."\">\n");
-    }
-    $m->print("<option value=\"".$topic->Id."\""
-      .(exists $topics{$topic->Id} ? " selected" : "").">"
-      .("&nbsp;" x ($tree->getDepth*5)).($topic->Name || loc("(no name)"))."</option>\n");
-  });
-</%perl>
+% if (@Classes) {
+%   unless ($OnlyThisClass) {
+<optgroup label="Current classes (<% join(" ", map {$_->Name} @Classes) %>)">
+%   }
+%   $inTree->traverse(sub {
+%     my $tree = shift;
+%     my $topic = $tree->getNodeValue;
+<option value="<% $topic->Id %>" <% exists $topics{$topic->Id} ? "selected" : "" %> >\
+<% "&nbsp;" x ($tree->getDepth*5) |n %><% $topic->Name || loc("(no name)") %></option>
+%   });
+% }
+% unless ($OnlyThisClass) {
+%   my $class = $Classes[-1]->Id;
+%   $otherTree->traverse(sub {
+%     my $tree = shift;
+%     my $topic = $tree->getNodeValue;
+%     unless ($topic->ObjectId == $class) {
+%       $class = $topic->ObjectId;
+</optgroup>
+%       my $c = RT::Class->new($session{'CurrentUser'});
+%       $c->Load($topic->ObjectId);
+<optgroup label="<% $c->Name %>">
+%     }
+<option value="<% $topic->Id %>" <% exists $topics{$topic->Id} ? "selected" : "" %> >\
+<% "&nbsp;" x ($tree->getDepth*5) |n %><% $topic->Name || loc("(no name)") %></option>
+%   });
 </optgroup>
 % }
 </select>
index adf23fc..f3618fe 100644 (file)
@@ -54,7 +54,7 @@
 % my $Classes = RT::Classes->new($session{'CurrentUser'});
 % $Classes->LimitToEnabled();
 % while (my $Class = $Classes->Next) {
-<li><a href="ExtractIntoTopic.html?Ticket=<%$Ticket%>&Class=<%$Class->Id%>" onclick="document.getElementById('topics-<% $Class->Id %>').style.display = (document.getElementById('topics-<% $Class->Id %>').style.display == 'block') ? 'none' : 'block'; return false;"><%$Class->Name%></a>: 
+<li><a href="ExtractIntoTopic.html?Ticket=<%$Ticket%>&Class=<%$Class->Id%>" onclick="document.getElementById('topics-'+<% $Class->Id |n,j%>).style.display = (document.getElementById('topics-'+<% $Class->Id |n,j%>).style.display == 'block') ? 'none' : 'block'; return false;"><%$Class->Name%></a>: 
 <%$Class->Description%>
 <div id="topics-<%$Class->Id%>" style="display: none">
 <form action="ExtractFromTicket.html">
index 9a07c08..5187315 100644 (file)
@@ -48,7 +48,6 @@
 <& /Elements/Header, Title => loc('Browse by topic') &>
 <& /Elements/Tabs &>
 
-<& /Elements/ListActions, actions => \@Actions &>
 <a href="Topics.html"><&|/l&>All topics</&></a>
 % if (defined $class) {
 &gt; <a href="Topics.html?class=<%$currclass_id%>"><% $currclass_name %></a>
 % }
 <br />
 <h1><&|/l&>Browse by topic</&></h1>
-<%perl>
-if (defined $class) {
-   $m->print('<h2>'.'<a href="'.
-   RT->Config->Get('WebPath')."/Articles/Topics.html?class=" . $currclass_id
-   .'">'.$currclass_name."</a></h2>\n");
-   ProduceTree(\@Actions, $currclass, $currclass_id, $currclass_name, 0, $id);
-} else {
-    $m->print("<ul>\n");
-    while (my $c = $Classes->Next) {
-        $m->print('<li><h2>'.'<a href="'.
-        RT->Config->Get('WebPath')."/Articles/Topics.html?class=" . $c->Id
-        .'">'.$c->Name."</a></h2>\n");
-        $m->print("\n</li>\n");
-    }
-    $m->print(qq|<li><h2><a href="|.RT->Config->Get('WebPath').qq|/Articles/Topics.html?class=0">|.loc('Global Topics').qq|</a></h2></li>\n|);
-    $m->print("</ul>\n");
-}
-</%perl>
+% if (defined $class) {
+<h2><a href="<% RT->Config->Get('WebPath') %>/Articles/Topics.html?class=<% $currclass_id %>"><% $currclass_name %></a></h2>
+%     my $rtopic = RT::Topic->new( $session{'CurrentUser'} );
+%     $rtopic->Load($id);
+%     unless ( $rtopic->Id()
+%         && $rtopic->ObjectId() == $currclass->Id )
+%     {
+%         # Show all of them
+%         $ProduceTree->( 0 );
+%     } else {
+%         my @showtopics = ( $rtopic );
+%         my $parent = $rtopic->ParentObj;
+%         while ( $parent->Id ) {
+%             unshift @showtopics, $parent;
+%             $parent = $parent->ParentObj;
+%         }
+%         # List the topics.
+%         for my $t ( @showtopics ) {
+<ul><li><& /Articles/Elements/ShowTopicLink, Topic => $t, Class => $currclass_id &>
+%             $ProduceTree->( $id ) if $t->Id == $id;
+%         }
+%         for ( @showtopics ) {
+              </li></ul>
+%         }
+%     }
+% } else {
+<ul>
+%     while (my $c = $Classes->Next) {
+<li><h2><a href="<% RT->Config->Get('WebPath') %>/Articles/Topics.html?class=<% $c->Id %>"><% $c->Name %></a></h2></li>
+%     }
+<li><h2><a href="<% RT->Config->Get('WebPath') %>/Articles/Topics.html?class=0"><&|/l&>Global Topics</&></a></h2></li>
+</ul>
+% }
 
 <br />
-<%perl>
-my @articles;
-if ($id or $showall) {
-    my $Articles = RT::ObjectTopics->new($session{'CurrentUser'});
-    $Articles->Limit(FIELD => 'ObjectType', VALUE => 'RT::Article');
-    if ($id) {
-        $Articles->Limit(FIELD => 'Topic', VALUE => $id, ENTRYAGGREGATOR => 'OR');
-        if ($showall) {
-            my $kids = $currtopic->Children;
-            while (my $k = $kids->Next) {
-                $Articles->Limit(FIELD => 'Topic', VALUE => $k->Id,
-                                 ENTRYAGGREGATOR => 'OR');
-            }
-        }
-    }
-    @articles = map {$a = RT::Article->new($session{'CurrentUser'}); $a->Load($_->ObjectId); $a} @{$Articles->ItemsArrayRef}
-} elsif ($class) {
-    my $Articles = RT::Articles->new($session{'CurrentUser'});
-    my $TopicsAlias = $Articles->Join(
-        TYPE   => 'left',
-        ALIAS1 => 'main',
-        FIELD1 => 'id',
-        TABLE2 => 'ObjectTopics',
-        FIELD2 => 'ObjectId',
-    );
-    $Articles->Limit(
-        LEFTJOIN => $TopicsAlias,
-        FIELD    => 'ObjectType',
-        VALUE    => 'RT::Article',
-    );
-    $Articles->Limit(
-        ALIAS      => $TopicsAlias,
-        FIELD      => 'Topic',
-        OPERATOR   => 'IS',
-        VALUE      => 'NULL',
-        QUOTEVALUE => 0,
-    );
-    $Articles->Limit(
-        FIELD      => 'Class',
-        OPERATOR   => '=',
-        VALUE      => $class,
-    );
-    @articles = @{$Articles->ItemsArrayRef};
-}
-</%perl>
 
 % if (@articles) {
 %   if ($id) {
@@ -139,7 +108,6 @@ if ($id or $showall) {
 % }
 
 <%init>
-my @Actions;
 my $Classes;
 my $currclass;
 my $currclass_id;
@@ -167,106 +135,65 @@ if ($id) {
     $currtopic->Load($id);
 }
 
-# A subroutine that iterates through topics and their children, producing
-# the necessary ul, li, and href links for the table of contents.  Thank
-# heaven for query caching.  The $restrict variable is used to display only
-# the branch of the hierarchy which contains that topic ID.
-
-sub ProduceTree {
-    my ( $Actions, $currclass, $currclass_id, $currclass_name, $parentid, $restrictid ) = @_;
-    $parentid = 0 unless $parentid;
-
-    # Deal with tree restriction, if any.
-    if ($restrictid) {
-        my $rtopic = RT::Topic->new( $session{'CurrentUser'} );
-        $rtopic->Load($restrictid);
-        unless ( $rtopic->Id()
-            && $rtopic->ObjectId() == $currclass_id )
-        {
-            push( @{$Actions},"Could not restrict view to topic $restrictid");
-
-            # Start over, without the restriction.
-            &ProduceTree( $Actions, $currclass, $currclass_id, $currclass_name, $parentid, undef );
-        } else {
-            my @showtopics;
-            push( @showtopics, $rtopic );
-            my $parent = $rtopic->ParentObj;
-            while ( $parent->Id ) {
-                push( @showtopics, $parent );
-                my $newparent = $parent->ParentObj;
-                $parent = $newparent;
-            }
-
-            # List the topics.
-            my $indents = @showtopics;
-            while ( my $t = pop @showtopics ) {
-                print "<ul>";
-                print &MakeLinks( $t, $currclass, $currclass_id, $currclass_name, $t->Children->Count );
-                if ( $t->Id == $restrictid ) {
-                    &ProduceTree( $Actions, $currclass, $currclass_id, $currclass_name, $restrictid, undef );
-                }
-            }
-            print "</ul>" x $indents;
-        }
-    } else {
-
-        # No restriction in place.  Build the entire tree.
-        my $topics = RT::Topics->new( $session{'CurrentUser'} );
-        $topics->LimitToObject($currclass);
-        $topics->LimitToKids($parentid);
-        $topics->OrderBy( FIELD => 'Name' );
-        print "<ul>" if $topics->Count;
-        while ( my $t = $topics->Next ) {
-            if ( $t->Children->Count ) {
-                print &MakeLinks( $t, $currclass, $currclass_id, $currclass_name, 1 );
-                &ProduceTree( $Actions, $currclass, $currclass_id, $currclass_name, $t->Id );
-            } else {
-                print &MakeLinks( $t, $currclass, $currclass_id, $currclass_name, 0 );
-            }
-        }
-        print "</ul>\n" if $topics->Count;
+my $ProduceTree;
+$ProduceTree = sub {
+    my ( $parentid ) = @_;
+    my $topics = RT::Topics->new( $session{'CurrentUser'} );
+    $topics->LimitToObject($currclass);
+    $topics->LimitToKids($parentid || 0);
+    $topics->OrderBy( FIELD => 'Name' );
+    return unless $topics->Count;
+    $m->out("<ul>");
+    while ( my $t = $topics->Next ) {
+        $m->out("<li>");
+        $m->comp("/Articles/Elements/ShowTopicLink",
+                 Topic => $t,
+                 Class => $currclass_id,
+             );
+        $ProduceTree->( $t->Id ) if $t->Children->Count;
+        $m->out("</li>");
     }
-}
-
-sub MakeLinks {
-    my ( $topic, $currclass, $currclass_id, $currclass_name, $haschild ) = @_;
-    my $query;
-    my $output;
-
-    if ( ref($topic) eq 'RT::Topic' ) {
-
-        my $topic_info = $topic->Name() || loc("(no name)");
-        $topic_info .= ": " . $topic->Description() if $topic->Description;
-
-        if ($haschild) { # has topics below it
-            $query  = "Topics.html?id=" . $topic->Id . "&class=" . $currclass_id;
-            $output = qq(<li><a href="$query">$topic_info</a>);
-        } else {
-            $output = qq(<li>$topic_info);
-        }
+    $m->out("</ul>");
+};
 
-        my $Articles = RT::ObjectTopics->new( $session{'CurrentUser'} );
-        $Articles->Limit( FIELD => 'ObjectType', VALUE => 'RT::Article' );
-        $Articles->Limit( FIELD => 'Topic',      VALUE => $topic->Id );
-        if ( $Articles->Count ) {
-            my $article_text = " (" . loc( "[quant,_1,article]", $Articles->Count ) . ")";
-            my $query  = "Topics.html?id=" . $topic->Id . "&class=$currclass_id&showall=1";
-            $output .= qq(<a href="$query">$article_text</a>);
-        }
-
-        $output .= "</li>\n";
-
-    } else {
-
-        # This builds a link for the class specified, with no particular topic.
-        $query  = "Topics.html?class=" . $currclass_id;
-        $output = "<li><a href=\"$query\">" . $currclass_name . "</a>";
-        $output .= ": " . $currclass->Description if $currclass->Description;
+my @articles;
+if ($id) {
+    my $Articles = RT::ObjectTopics->new($session{'CurrentUser'});
+    $Articles->Limit(FIELD => 'ObjectType', VALUE => 'RT::Article');
+    $Articles->Limit(FIELD => 'Topic', VALUE => $id);
+    while (my $objtopic = $Articles->Next) {
+        my $a = RT::Article->new($session{'CurrentUser'});
+        $a->Load($objtopic->ObjectId);
+        push @articles, $a;
     }
-
-    return $output;
+} elsif ($class) {
+    my $Articles = RT::Articles->new($session{'CurrentUser'});
+    my $TopicsAlias = $Articles->Join(
+        TYPE   => 'left',
+        ALIAS1 => 'main',
+        FIELD1 => 'id',
+        TABLE2 => 'ObjectTopics',
+        FIELD2 => 'ObjectId',
+    );
+    $Articles->Limit(
+        LEFTJOIN => $TopicsAlias,
+        FIELD    => 'ObjectType',
+        VALUE    => 'RT::Article',
+    );
+    $Articles->Limit(
+        ALIAS      => $TopicsAlias,
+        FIELD      => 'Topic',
+        OPERATOR   => 'IS',
+        VALUE      => 'NULL',
+        QUOTEVALUE => 0,
+    );
+    $Articles->Limit(
+        FIELD      => 'Class',
+        OPERATOR   => '=',
+        VALUE      => $class,
+    );
+    @articles = @{$Articles->ItemsArrayRef};
 }
-
 </%init>
 
 <%args>
index 81d8bbb..20586f9 100644 (file)
@@ -129,11 +129,11 @@ foreach my $col ( @Format ) {
             if $OrderBy[0] && ($OrderBy[0] eq $attr or "$attr|$OrderBy[0]" =~ /^(Created|id)\|(Created|id)$/);
 
         $m->out(
-            '<a href="' . $BaseURL
+            '<a href="' . $m->interp->apply_escapes($BaseURL
             . $m->comp( '/Elements/QueryString',
                 %$generic_query_args,
                 OrderBy => $attr, Order => $new_order
-            )
+            ), 'h')
             . '">'. loc($title) .'</a>'
         );
     }
index 0c816fe..402a272 100644 (file)
@@ -68,7 +68,7 @@ if ( $Rows ) {
 # collection is ordered or not
 if ( @OrderBy && ($AllowSorting || !$Collection->{'order_by'}) ) {
     if ( $OrderBy[0] =~ /\|/ ) {
-        @OrderBy = split /\|/, $OrderBy[0];
+        @OrderBy = grep length($_), split /\|/, $OrderBy[0];
         @Order = split /\|/,$Order[0];
     }
     $Collection->OrderByCols(
index b1faa21..26c0823 100644 (file)
@@ -55,22 +55,24 @@ $URLParams => undef
 </%ARGS>
 
 <%INIT>
+$BaseURL = $m->interp->apply_escapes($BaseURL, 'h');
+
 $m->out(qq{<div class="paging">});
 if ($Pages == 1) {
   $m->out(loc('Page 1 of 1'));
 }
 else{
 $m->out(loc('Page') . ' ');
-my $prev = $m->comp(
+my $prev = $m->interp->apply_escapes($m->comp(
                    '/Elements/QueryString',
                    %$URLParams,
                    Page    => ( $CurrentPage - 1 )
-                  );
-my $next = $m->comp(
+                  ), 'h');
+my $next = $m->interp->apply_escapes($m->comp(
                    '/Elements/QueryString',
                    %$URLParams,
                    Page    => ( $CurrentPage + 1 )
-                  );
+                  ), 'h');
 my %show;
 $show{1} = 1;
 $show{$_} = 1 for (($CurrentPage - 2)..($CurrentPage + 2));
@@ -81,7 +83,7 @@ for my $number ( 1 .. $Pages ) {
     if ( $show{$number} ) {
         $dots = undef;
         my $qs =
-          $m->comp( '/Elements/QueryString', %$URLParams, Page => $number );
+          $m->interp->apply_escapes($m->comp( '/Elements/QueryString', %$URLParams, Page => $number ), 'h');
        $m->out(qq{<span class="pagenum">});
         if ( $number == $CurrentPage ) {
             $m->out(qq{<span class="currentpage">$number</span> });
index 745e57f..b9c3b4b 100644 (file)
@@ -120,14 +120,16 @@ my $COLUMN_MAP = {
             my $name = $_[1] || 'SelectedTickets';
             my $checked = $m->request_args->{ $name .'All' }? 'checked="checked"': '';
 
-            return \qq{<input type="checkbox" name="${name}All" value="1" $checked
-                              onclick="setCheckbox(this.form, '$name', this.checked)" />};
+            return \qq{<input type="checkbox" name="}, $name, \qq{All" value="1" $checked
+                              onclick="setCheckbox(this.form, },
+                              $m->interp->apply_escapes($name,'j'),
+                              \qq{, this.checked)" />};
         },
         value => sub {
             my $id = $_[0]->id;
 
             my $name = $_[2] || 'SelectedTickets';
-            return \qq{<input type="checkbox" name="$name" value="$id" checked="checked" />}
+            return \qq{<input type="checkbox" name="}, $name, \qq{" value="$id" checked="checked" />}
                 if $m->request_args->{ $name . 'All'};
 
             my $arg = $m->request_args->{ $name };
@@ -138,7 +140,7 @@ my $COLUMN_MAP = {
             elsif ( $arg ) {
                 $checked = 'checked="checked"' if $arg == $id;
             }
-            return \qq{<input type="checkbox" name="$name" value="$id" $checked />}
+            return \qq{<input type="checkbox" name="}, $name, \qq{" value="$id" $checked />}
         },
     },
     RadioButton => {
index 6e541db..6702abc 100755 (executable)
@@ -51,7 +51,7 @@
 % my $button_start = '<input type="submit" class="button" value="';
 % my $button_end = '" />';
 % my $queue_selector = $m->scomp('/Elements/SelectNewTicketQueue', OnChange => 'document.CreateTicketInQueue.submit()', SendTo => $SendTo );
-<&|/l, $button_start, $button_end, $queue_selector &>[_1]New ticket in[_2]&nbsp;[_3]</&>
+<&|/l_unsafe, $button_start, $button_end, $queue_selector &>[_1]New ticket in[_2]&nbsp;[_3]</&>
 % $m->callback(CallbackName => 'BeforeFormEnd');
 </form>
 <%ARGS>
index c7c8bfa..b74c484 100644 (file)
@@ -85,7 +85,7 @@ if ($MaxValues == 1 && $Values) {
 }
 # The "Magic" hidden input causes RT to know that we were trying to edit the field, even if 
 # we don't see a value later, since browsers aren't compelled to submit empty form fields
-$m->out("\n".'<input type="hidden" class="hidden" name="'.$NamePrefix.$CustomField->Id.'-Values-Magic" value="1" />'."\n");
+$m->out("\n".'<input type="hidden" class="hidden" name="'.$m->interp->apply_escapes($NamePrefix, 'h').$CustomField->Id.'-Values-Magic" value="1" />'."\n");
 
 my $EditComponent = "EditCustomField$Type";
 $m->callback( %ARGS, CallbackName => 'EditComponentName', Name => \$EditComponent, CustomField => $CustomField, Object => $Object );
index aaf5517..911e607 100644 (file)
 <textarea cols="<% $Cols %>" rows="<% $Rows %>" name="<% $name %>-Values" id="<% $name %>-Values" class="CF-<%$CustomField->id%>-Edit"><% $Default || '' %></textarea>
 
 <script type="text/javascript">
-var id = '<% $name . '-Values' %>';
+var id = <% "$name-Values" |n,j%>;
 id = id.replace(/:/g,'\\:');
 jQuery('#'+id).autocomplete( {
-    source: "<%RT->Config->Get('WebPath')%>/Helpers/Autocomplete/CustomFieldValues?<% $name . '-Values' %>",
+    source: <%RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/CustomFieldValues?"+<% $Context |n,j %>+<% "$name-Values" |n,u,j%>,
     focus: function () {
         // prevent value inserted on focus
         return false;
@@ -73,10 +73,10 @@ jQuery('#'+id).autocomplete( {
 % } else {
 <input type="text" id="<% $name %>-Value" name="<% $name %>-Value" class="CF-<%$CustomField->id%>-Edit" value="<% $Default || '' %>"/>
 <script type="text/javascript">
-var id = '<% $name . '-Value' %>';
+var id = <% "$name-Value" |n,j%>;
 id = id.replace(/:/g,'\\:');
 jQuery('#'+id).autocomplete( {
-    source: "<%RT->Config->Get('WebPath')%>/Helpers/Autocomplete/CustomFieldValues?<% $name . '-Value' %>"
+    source: <%RT->Config->Get('WebPath')|n,j%>+"/Helpers/Autocomplete/CustomFieldValues?"+<% $Context |n,j %>+<% "$name-Value" |n,u,j%>
 }
 );
 % }
@@ -92,6 +92,11 @@ if ( $Multiple and $Values ) {
         $Default .= $value->Content ."\n";
     }
 }
+my $Context = "";
+if ($CustomField->ContextObject) {
+    $Context .= "ContextId="  . $CustomField->ContextObject->Id  . "&";
+    $Context .= "ContextType=". ref($CustomField->ContextObject) . "&";
+}
 </%INIT>
 <%ARGS>
 $CustomField => undef
index b3fefbd..ed6bb14 100644 (file)
@@ -55,7 +55,7 @@
 % if (!$HideCategory and @category and not $CustomField->BasedOnObj->id) {
   <script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/NoAuth/js/cascaded.js"></script>
 %# XXX - Hide this select from w3m?
-  <select onchange="filter_cascade('<% $id %>-Values', this.value)" name="<% $id %>-Category" class="CF-<%$CustomField->id%>-Edit">
+  <select onchange="filter_cascade(<% "$id-Values" |n,j%>, this.value)" name="<% $id %>-Category" class="CF-<%$CustomField->id%>-Edit">
     <option value=""<% !$selected && qq[ selected="selected"] |n %>><&|/l&>-</&></option>
 %   foreach my $cat (@category) {
 %     my ($depth, $name) = @$cat;
 <script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/NoAuth/js/cascaded.js"></script>
 <script type="text/javascript"><!--
 jQuery(  function () {
-    var basedon = document.getElementById('<% $NamePrefix . $CustomField->BasedOnObj->id %>-Values');
+    var basedon = document.getElementById(<% $NamePrefix . $CustomField->BasedOnObj->id . "-Values" |n,j%>);
     if (basedon != null) {
         var oldchange = basedon.onchange;
         basedon.onchange = function () {
             filter_cascade(
-                '<% $id %>-Values',
+                <% "$id-Values" |n,j%>,
                 basedon.value,
                 1
             );
index 50f3b77..87dfd02 100755 (executable)
@@ -81,7 +81,7 @@ Encode::_utf8_off($error);
 
 $RT::Logger->error($error);
 
-if ( defined $session{'SessionType'} && $session{'SessionType'} eq 'REST' ) {
+if ( $session{'REST'} ) {
     $r->content_type('text/plain');
     $m->out( "Error: " . $Why . "\n" );
     $m->out( $Details . "\n" ) if defined $Details && length $Details;
index 5d89fe6..1830c4b 100755 (executable)
@@ -87,7 +87,8 @@ my $head = '';
 #XXX $head .= <& /Elements/Framekiller &>;
 
 if ($Refresh && $Refresh =~ /^(\d+)/ && $1 > 0) {
-  $head .= qq( <meta http-equiv="refresh" content="$Refresh" /> );
+  my $URL = $m->notes->{RefreshURL}; $URL = $URL ? ";URL=$URL" : "";
+  $head .= qq( <meta http-equiv="refresh" content="$1$URL" /> );
 }
 
 my $WebPath = RT->Config->Get('WebPath');
index e392ac2..28788db 100644 (file)
@@ -60,14 +60,14 @@ $onload => undef
 <script type="text/javascript"><!--
        jQuery( loadTitleBoxStates );
 % if ( $focus ) {
-    jQuery(function () { focusElementById('<% $focus %>') });
+    jQuery(function () { focusElementById(<% $focus |n,j%>) });
 % }
 % if ( $onload ) {
     jQuery( <% $onload |n %> );
 % }
 
 % if ( $RichText and RT->Config->Get('MessageBoxRichText',  $session{'CurrentUser'})) {
-    jQuery().ready(function ()  { ReplaceAllTextareas('<%$m->request_args->{'CKeditorEncoded'} || 0 %>') });
+    jQuery().ready(function ()  { ReplaceAllTextareas(<%$m->request_args->{'CKeditorEncoded'} || 0 |n,j%>) });
 % }
 --></script>
 <%ARGS>
index 2943cab..61995e0 100755 (executable)
@@ -45,7 +45,7 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<textarea autocomplete="off" class="messagebox" <% $cols |n %> rows="<% $Height %>" <% $wrap_type |n %> name="<% $Name %>" id="<% $Name %>">\
+<textarea autocomplete="off" class="messagebox" <% $width_attr %>="<% $Width %>" rows="<% $Height %>" <% $wrap_type |n %> name="<% $Name %>" id="<% $Name %>">\
 % $m->comp('/Articles/Elements/IncludeArticle', %ARGS);
 % $m->callback( %ARGS, SignatureRef => \$signature );
 <% $Default || '' %><% $message %><% $signature %></textarea>
@@ -68,13 +68,16 @@ if ( $IncludeSignature and my $text = $session{'CurrentUser'}->UserObj->Signatur
 # wrap="something" seems to really break IE + richtext
 my $wrap_type = '';
 if ( not RT->Config->Get('MessageBoxRichText',  $session{'CurrentUser'}) ) {
-    $wrap_type = qq(wrap="$Wrap");
+    $wrap_type = 'wrap="' . $m->interp->apply_escapes($Wrap, 'h') . '"';
 }
 
-# If there's no cols specified, we want to set the width to 100%
-my $cols = 'style="width: 100%"';
-if ( defined $Width and length $Width ) {
-    $cols = qq(cols="$Width");
+# If there's no cols specified, we want to set the width to 100% in CSS
+my $width_attr;
+if ($Width) {
+    $width_attr = 'cols';
+} else {
+    $width_attr = 'style';
+    $Width = 'width: 100%';
 }
 
 </%INIT>
index 06e2674..ecb219d 100644 (file)
@@ -120,8 +120,10 @@ my $COLUMN_MAP = {
             my $name = 'RemoveCustomField';
             my $checked = $m->request_args->{ $name .'All' }? 'checked="checked"': '';
 
-            return \qq{<input type="checkbox" name="${name}All" value="1" $checked
-                              onclick="setCheckbox(this.form, '$name', this.checked)" />};
+            return \qq{<input type="checkbox" name="}, $name, \qq{All" value="1" $checked
+                              onclick="setCheckbox(this.form, },
+                              $m->interp->apply_escapes($name,'j'),
+                              \qq{, this.checked)" />};
         },
         value => sub {
             my $id = $_[0]->id;
@@ -137,7 +139,7 @@ my $COLUMN_MAP = {
             elsif ( $arg ) {
                 $checked = 'checked="checked"' if $arg == $id;
             }
-            return \qq{<input type="checkbox" name="$name" value="$id" $checked />}
+            return \qq{<input type="checkbox" name="}, $name, \qq{" value="$id" $checked />}
         },
     },
     MoveCF => {
index 8bc4383..6c366ec 100644 (file)
@@ -111,7 +111,7 @@ my $COLUMN_MAP = {
                 }
             }
 
-            return \('<a href="'.$url.'">'.$frequency.'</a>');
+            return \'<a href="', $url, \'">', $frequency, \'</a>';
         },
     },
     ShowURL => {
index 00655c5..e08dd7c 100644 (file)
@@ -84,12 +84,16 @@ my $COLUMN_MAP = {
         title => 'Encrypt', # loc
         value => sub { return $_[0]->Encrypt? $_[0]->loc('yes') : $_[0]->loc('no') },
     },
+    Lifecycle => {
+        title => 'Lifecycle',
+        attribute => 'Lifecycle',
+        value => sub { return $_[0]->Lifecycle->Name },
+    },
 };
 
 foreach my $field (qw(
     Name Description CorrespondAddress CommentAddress
     InitialPriority FinalPriority DefaultDueIn
-    Lifecycle
 )) {
     $COLUMN_MAP->{$field} = {
         title => $field,
index cc64e24..37a5971 100755 (executable)
@@ -59,8 +59,12 @@ if ($TicketObj) {
     @objects = ($TicketObj);
 } elsif ($QueueObj) {
     @objects = ($QueueObj);
-} elsif ($cfqueues) {
-    @objects = keys %{$cfqueues};
+} elsif (%Queues) {
+    for my $name (keys %Queues) {
+        my $q = RT::Queue->new($session{'CurrentUser'});
+        $q->Load($name);
+        push @objects, $q;
+    }
 } else {
     # Let's check rights on an empty queue object. that will do a search
     # for any queue.
@@ -77,5 +81,5 @@ $m->callback(
 <%ARGS>
 $TicketObj  => undef
 $QueueObj   => undef
-$cfqueues   => undef
+%Queues     => ()
 </%ARGS>
index cf2010a..81b3838 100644 (file)
@@ -78,7 +78,7 @@ my $query = $m->comp('/Elements/QueryString',
 <script type="text/javascript">
     jQuery(function() {
         var cache = {};
-        jQuery("#<% $Name %>").autocomplete({
+        jQuery("#"+<% $Name |n,j%>).autocomplete({
             minLength: 2,
             source: function(request, response) {
                 if ( request.term in cache ) {
@@ -86,7 +86,7 @@ my $query = $m->comp('/Elements/QueryString',
                 }
                 else {
                     jQuery.ajax({
-                        url: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Owners?<% $query|n %>",
+                        url: <% RT->Config->Get('WebPath')|n,j%>+"/Helpers/Autocomplete/Owners?"+<% $query|n,j %>,
                         dataType: "json",
                         data: request,
                         success: function( data ) {
index e571baf..af1ff61 100755 (executable)
@@ -66,6 +66,8 @@ if ( @Statuses ) {
 }
 elsif ( $TicketObj ) {
     my $current = $TicketObj->Status;
+    push @status, $current;
+
     my $lifecycle = $TicketObj->QueueObj->Lifecycle;
 
     my %has = ();
@@ -78,8 +80,15 @@ elsif ( $TicketObj ) {
 }
 elsif ( $QueueObj ) {
     @status = $QueueObj->Lifecycle->Transitions('');
-}
-else {
+} elsif ( %Queues ) {
+    for my $id (keys %Queues) {
+        my $queue = RT::Queue->new($session{'CurrentUser'});
+        $queue->Load($id);
+        push @status, $queue->Lifecycle->Valid if $queue->id;
+    }
+    my %seen;
+    @status = grep { not $seen{$_}++ } @status;
+} else {
     @status = RT::Queue->Lifecycle->Valid;
 }
 </%INIT>
@@ -89,6 +98,7 @@ $Name => undef
 @Statuses => ()
 $TicketObj => undef
 $QueueObj => undef
+%Queues => ()
 
 $Default => ''
 $SkipDeleted => 0
index fcd530e..6059f4e 100644 (file)
@@ -114,12 +114,12 @@ my $print_value = sub {
        my $vid = $value->id;
        $m->out(   '<div class="object_cf_value_include" id="object_cf_value_'. $vid .'">' );
        $m->out( loc("See also:") );
-       $m->out(   '<a href="'. $value->IncludeContentForValue .'">' );
-       $m->out( $value->IncludeContentForValue );
+       $m->out(   '<a href="'. $m->interp->apply_escapes($value->IncludeContentForValue, 'h') .'">' );
+       $m->out( $m->interp->apply_escapes($value->IncludeContentForValue, 'h') );
        $m->out(   qq{</a></div>\n} );
-       $m->out(   qq{<script><!--\njQuery('#object_cf_value_$vid').load('} );
-       $m->out( $value->IncludeContentForValue );
-       $m->out(   qq{');\n--></script>\n} );
+       $m->out(   qq{<script><!--\njQuery('#object_cf_value_$vid').load(} );
+       $m->out(   $m->interp->apply_escapes($value->IncludeContentForValue, 'j') );
+       $m->out(   qq{);\n--></script>\n} );
     }
 };
 
index 8913a32..1727fa3 100644 (file)
@@ -45,7 +45,7 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<a href="<%$URI->Resolver->HREF%>">
+<a href="<% $href %>">
 % if ($URI->IsLocal) {
 % my $member = $URI->Object;
 % my $has_name = UNIVERSAL::can($member, 'Name') || (UNIVERSAL::can($member, '_Accessible') && $member->_Accessible('Name', 'read'));
 <%ARGS>
 $URI => undef
 </%ARGS>
+
+<%INIT>
+my $href = $URI->Resolver->HREF;
+if ( $URI->IsLocal ) {
+    my $base = RT->Config->Get('WebBaseURL');
+    # URI->rel doesn't contain the leading '/'
+    $href = '/' . URI->new($href)->rel($base);
+}
+</%INIT>
index 2b23181..4b96bbf 100644 (file)
@@ -64,12 +64,12 @@ my $query_link_url = RT->Config->Get('WebPath').'/Search/Results.html';
 if ($SavedSearch) {
     my ( $container_object, $search_id ) = _parse_saved_search($SavedSearch);
     unless ( $container_object ) {
-        $m->out(loc("Either you have no rights to view saved search [_1] or identifier is incorrect", $SavedSearch));
+        $m->out(loc("Either you have no rights to view saved search [_1] or identifier is incorrect", $m->interp->apply_escapes($SavedSearch, 'h')));
         return;
     }
     $search = $container_object->Attributes->WithId($search_id);
     unless ( $search->Id && ref( $SearchArg = $search->Content ) eq 'HASH' ) {
-        $m->out(loc("Saved Search [_1] not found", $SavedSearch)) unless $IgnoreMissing;
+        $m->out(loc("Saved Search [_1] not found", $m->interp->apply_escapes($SavedSearch, 'h'))) unless $IgnoreMissing;
         return;
     }
     $SearchArg->{'SavedSearchId'} ||= $SavedSearch;
@@ -79,7 +79,7 @@ if ($SavedSearch) {
         # XXX: dispatch to different handler here
         $query_display_component
             = '/Search/Elements/' . $SearchArg->{SearchType};
-        $query_link_url = RT->Config->Get('WebURL') . "/Search/$SearchArg->{SearchType}.html";
+        $query_link_url = RT->Config->Get('WebPath') . "/Search/$SearchArg->{SearchType}.html";
     } elsif ($ShowCustomize) {
         $customize = RT->Config->Get('WebPath') . '/Search/Build.html?'
             . $m->comp( '/Elements/QueryString',
@@ -93,7 +93,7 @@ if ($SavedSearch) {
             if ($custom->Description eq $Name) { $search = $custom; last }
         }
         unless ($search && $search->id) {
-            $m->out("Predefined search $Name not found");
+            $m->out(loc("Predefined search [_1] not found", $m->interp->apply_escapes($Name, 'h')));
             return;
         }
     }
index 044ec4c..3654977 100644 (file)
@@ -51,7 +51,7 @@
 # $Address is Email::Address object
 
 my $comp = '/Elements/ShowUser'. ucfirst lc $style;
-unless ( $m->comp_exists( $comp ) ) {
+unless ( RT::Interface::Web->ComponentPathIsSafe($comp) and $m->comp_exists( $comp ) ) {
     $RT::Logger->error(
         'Either system config or user #'
         . $session{'CurrentUser'}->id
index cbf3f58..b7840d3 100755 (executable)
@@ -52,10 +52,10 @@ id="<%$id%>"
 >
   <div class="extra-buttons">
 % if ($CheckAll) {
-  <input type="button" value="<%$CheckAllLabel%>" onclick="setCheckbox(this.form, <% length $CheckboxName ? qq{'$CheckboxName'} : length $CheckboxNameRegex ? $CheckboxNameRegex : q{''} %>, true);return false;" class="button" />
+  <input type="button" value="<%$CheckAllLabel%>" onclick="setCheckbox(this.form, <% $match %>, true);return false;" class="button" />
 % }
 % if ($ClearAll) {
-  <input type="button" value="<%$ClearAllLabel%>" onclick="setCheckbox(this.form, <% length $CheckboxName ? qq{'$CheckboxName'} : length $CheckboxNameRegex ? $CheckboxNameRegex : q{''} %>, false);return false;" class="button" />
+  <input type="button" value="<%$ClearAllLabel%>" onclick="setCheckbox(this.form, <% $match %>, false);return false;" class="button" />
 % }
 % if ($Reset) {
   <input type="reset" value="<%$ResetLabel%>" class="button" />
@@ -115,3 +115,13 @@ $ResetLabel => loc('Reset')
 $SubmitId => undef
 $id => undef
 </%ARGS>
+<%init>
+my $match;
+if (length $CheckboxName) {
+    $match = $m->interp->apply_escapes($CheckboxName,'j');
+} elsif (length $CheckboxNameRegex) {
+    $match = $CheckboxNameRegex;
+} else {
+    $match = q{''};
+}
+</%init>
index 75b8160..3193b48 100755 (executable)
@@ -734,6 +734,9 @@ my $build_main_nav = sub {
             $current_search_menu->child( bulk  => title => loc('Bulk Update'), path => "/Search/Bulk.html$args" );
             $current_search_menu->child( chart => title => loc('Chart'),       path => "/Search/Chart.html$args" );
 
+            #formerly Callbacks/RTx-Calendar/Ticket/Element/Tabs/Default
+            $current_search_menu->child( calendar => title => loc('Calendar'), path => "/Search/Calendar.html$args" );
+
             my $more = $current_search_menu->child( more => title => loc('Feeds') );
 
             $more->child( tsv => title => loc('TSV'), path => "/Search/Results.tsv$args" );
index b8b21e4..887302f 100644 (file)
 # Only autocomplete the last value
 my $term = (split /\n/, $ARGS{term} || '')[-1];
 
+my $abort = sub {
+    $r->content_type('application/json');
+    $m->out(JSON::to_json( [] ));
+    $m->abort;
+};
+
+unless ( exists $ARGS{ContextType} and exists $ARGS{ContextId} ) {
+    RT->Logger->debug("No context provided");
+    $abort->();
+}
+
 my $CustomField;
 for my $k ( keys %ARGS ) {
     next unless $k =~ /^Object-.*?-\d*-CustomField-(\d+)-Values?$/;
@@ -59,9 +70,38 @@ for my $k ( keys %ARGS ) {
     last;
 }
 
-$m->abort unless $CustomField;
+unless ( $CustomField ) {
+    RT->Logger->debug("No CustomField provided");
+    $abort->();
+}
+
+my $SystemCustomFieldObj = RT::CustomField->new( RT->SystemUser );
+my ($id, $msg) = $SystemCustomFieldObj->LoadById( $CustomField ) ;
+unless ( $id ) {
+    RT->Logger->debug("Invalid CustomField provided: $msg");
+    $abort->();
+}
+
+my $context_object = $SystemCustomFieldObj->LoadContextObject(
+    $ARGS{ContextType}, $ARGS{ContextId} );
+$abort->() unless $context_object;
+
 my $CustomFieldObj = RT::CustomField->new( $session{'CurrentUser'} );
-$CustomFieldObj->Load( $CustomField );
+if ( $SystemCustomFieldObj->ValidateContextObject($context_object) ) {
+    # drop our privileges that came from calling LoadContextObject as the System User
+    $context_object->new($session{'CurrentUser'});
+    $context_object->LoadById($ARGS{ContextId});
+    $CustomFieldObj->SetContextObject( $context_object );
+} else {
+    RT->Logger->debug("Invalid Context Object ".$context_object->id." for Custom Field ".$SystemCustomFieldObj->id);
+    $abort->();
+}
+
+($id, $msg) = $CustomFieldObj->LoadById( $CustomField );
+unless ( $CustomFieldObj->Name ) {
+    RT->Logger->debug("Current User cannot see this Custom Field, terminating");
+    $abort->();
+}
 
 my $values = $CustomFieldObj->Values;
 $values->Limit(
index bb90b98..68e8a05 100644 (file)
@@ -47,7 +47,9 @@
 %# END BPS TAGGED BLOCK }}}
 <%INIT>
 my $TicketTemplate = "/Ticket/Elements/ShowRequestorTickets$Status";
-$TicketTemplate = "/Ticket/Elements/ShowRequestorTicketsActive" unless $m->comp_exists($TicketTemplate);
+$TicketTemplate = "/Ticket/Elements/ShowRequestorTicketsActive"
+    unless RT::Interface::Web->ComponentPathIsSafe($TicketTemplate)
+       and $m->comp_exists($TicketTemplate);
 my $user_obj = RT::User->new($session{CurrentUser});
 my ($val, $msg) = $user_obj->Load($Requestor);
 unless ($val) {
index 3312b57..68f8a67 100644 (file)
@@ -58,7 +58,7 @@
 <&|/l&>SQLite is a database that doesn't need a server or any configuration whatsoever. RT's authors recommend it for testing, demoing and development, but it's not quite right for a high-volume production RT server.</&>
 </b></p>
 <p>
-<&|/l, '<a href="http://search.cpan.org" target="_new">CPAN</a>' &>If your preferred database isn't listed in the dropdown below, that means RT couldn't find a <i>database driver</i> for it installed locally. You may be able to remedy this by using [_1] to download and install DBD::MySQL, DBD::Oracle or DBD::Pg.</&>
+<&|/l_unsafe, '<a href="http://search.cpan.org" target="_new">CPAN</a>' &>If your preferred database isn't listed in the dropdown below, that means RT couldn't find a <i>database driver</i> for it installed locally. You may be able to remedy this by using [_1] to download and install DBD::MySQL, DBD::Oracle or DBD::Pg.</&>
 </p>
 </div>
 
index ee81e70..24ac0ff 100644 (file)
@@ -53,7 +53,7 @@
 </p>
 
 <p>
-<&|/l, '<tt>root</tt>' &>You should be taken directly to a login page. You'll be able to log in with username of [_1] and the password you set earlier.</&>
+<&|/l_unsafe, '<tt>root</tt>' &>You should be taken directly to a login page. You'll be able to log in with username of [_1] and the password you set earlier.</&>
 </p>
 
 <p>
index 47d7616..0cc39af 100644 (file)
@@ -125,7 +125,7 @@ if ( $Run ) {
                 $RT::Handle = RT::Handle->new;
                 RT::Init();
                 my $file = $RT::EtcPath . "/initialdata";
-                ($status, $msg) = $RT::Handle->InsertData( $file );
+                ($status, $msg) = $RT::Handle->InsertData( $file, undef, disconnect_after => 0 );
             }
             unless ( $status ) {
                 push @errors, loc('ERROR: [_1]', $msg);
index 61fb89e..78069af 100644 (file)
@@ -92,7 +92,7 @@
 my @errors;
 my $locked;
 
-my $file = File::Spec->catfile( $RT::EtcPath, 'RT_SiteConfig.pm' );
+my $file = RT::Installer->ConfigFile;
 
 if ( ! -e $file ) {
     # write a blank RT_SiteConfig.pm
index b8e119a..20024cc 100755 (executable)
@@ -81,5 +81,5 @@ if (keys %session) {
 }
 
 $m->callback( %ARGS, CallbackName => 'AfterSessionDelete' );
-$m->notes->{LogoutURL} = $URL;
+$m->notes->{RefreshURL} = $URL;
 </%INIT>
index aff24d8..e6d4cb3 100644 (file)
@@ -45,9 +45,6 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<!--[if (lt IE 9)&(gt IE 6)]>
-<link rel="stylesheet" href="<%RT->Config->Get('WebPath')%>/NoAuth/css/aileron/msie-pie.css" type="text/css" media="all" />
-<![endif]-->
 <!--[if lt IE 8]>
 <link rel="stylesheet" href="<%RT->Config->Get('WebPath')%>/NoAuth/css/aileron/msie.css" type="text/css" media="all" />
 <![endif]-->
index f90ac9f..ed6623c 100644 (file)
 
 .titlebox .titlebox-title {
  position: relative;
+ /* This is for [rt3 #19044]. Move it to an IE-specific file if it causes
+  * problems. If we remove CSS3PIE, it can also probably go away, although it
+  * probably won't hurt. */
+ z-index: 1;
 }
 
 .titlebox .titlebox-title a {
diff --git a/rt/share/html/NoAuth/css/aileron/msie-pie.css b/rt/share/html/NoAuth/css/aileron/msie-pie.css
deleted file mode 100644 (file)
index baa9ebe..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
-%#                                          <sales@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-.search-result-views,
-.ticket-transaction div.metadata span.actions,
-div#ticket-history div.downloadattachment, 
-.ticket-transaction div.metadata  span.type,
-.titlebox,
-.titlebox .titlebox-title .right,
-.titlebox .titlebox-title .left,
-div#footer,
-div#body {
-    behavior: url(<%RT->Config->Get('WebPath')%>/NoAuth/css/images/PIE.htc);
-}
diff --git a/rt/share/html/NoAuth/css/images/PIE.htc b/rt/share/html/NoAuth/css/images/PIE.htc
deleted file mode 100644 (file)
index 6a40cef..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-<!--
-PIE: CSS3 rendering for IE
-Version 1.0beta2
-http://css3pie.com
-Dual-licensed for use under the Apache License Version 2.0 or the General Public License (GPL) Version 2.
--->
-<PUBLIC:COMPONENT lightWeight="true">
-    <PUBLIC:ATTACH EVENT="onresize" FOR="element" ONEVENT="update()" />
-    <PUBLIC:ATTACH EVENT="onresize" FOR="window" ONEVENT="update()" />
-    <PUBLIC:ATTACH EVENT="onmove" FOR="element" ONEVENT="update()" />
-    <PUBLIC:ATTACH EVENT="onpropertychange" FOR="element" ONEVENT="propChanged()" />
-    <PUBLIC:ATTACH EVENT="onmouseenter" FOR="element" ONEVENT="mouseEntered()" />
-    <PUBLIC:ATTACH EVENT="onmouseleave" FOR="element" ONEVENT="mouseLeft()" />
-    <PUBLIC:ATTACH EVENT="oncontentready" FOR="element" ONEVENT="update()" />
-    <PUBLIC:ATTACH EVENT="ondocumentready" FOR="element" ONEVENT="update()" />
-    <PUBLIC:ATTACH EVENT="ondetach" FOR="element" ONEVENT="cleanup()" />
-
-    <script type="text/javascript">
-function i(){return function(){}}var D=window.PIE;
-if(!D){D=window.PIE={T:"-pie-",Ma:"Pie",Ka:"pie_"};if(!window.XMLHttpRequest){D.Yb=true;D.T=D.T.replace(/^-/,"")}D.oa=element.document.documentMode;D.Da=!!D.oa;if(D.oa===8){D.Ca={ya:{},add:function(a){this.ya[a.id||(a.id=""+(new Date).getTime()+Math.random())]=a},remove:function(a){delete this.ya[a.id]},Tb:function(){var a=this.ya,b;for(b in a)a.hasOwnProperty(b)&&a[b]()}};setInterval(function(){D.Ca.Tb()},250)}D.q={ja:function(a){var b=D.Gb;if(!b){b=D.Gb=element.document.createDocumentFragment();
-b.namespaces.add("css3vml","urn:schemas-microsoft-com:vml")}return b.createElement("css3vml:"+a)},Fa:function(a){var b,c,d,e,f=arguments;b=1;for(c=f.length;b<c;b++){e=f[b];for(d in e)if(e.hasOwnProperty(d))a[d]=e[d]}return a},ib:function(a,b,c){var d=D.Ab||(D.Ab={}),e=d[a],f;if(e)b.call(c,e);else{f=new Image;f.onload=function(){e=d[a]={v:f.width,i:f.height};b.call(c,e);f.onload=null};f.src=a}}};D.g=function(){function a(b){this.F=b}a.prototype={Ga:/(px|em|ex|mm|cm|in|pt|pc|%)$/,Va:function(){var b=
-this.Bb;if(b===undefined)b=this.Bb=parseFloat(this.F);return b},Aa:function(){var b=this.ua;if(!b)b=this.ua=(b=this.F.match(this.Ga))&&b[0]||"px";return b},a:function(b,c){var d=this.Va(),e=this.Aa();switch(e){case "px":return d;case "%":return d*(typeof c==="function"?c():c)/100;case "em":return d*this.Ua(b);case "ex":return d*this.Ua(b)/2;default:return d*a.Ob[e]}},Ua:function(b){var c=b.currentStyle.fontSize,d;if(c.indexOf("px")>0)return parseFloat(c);else{c=this.Fb;if(!c){c=this.Fb=b.document.createElement("length-calc");
-d=c.style;d.width="1em";d.position="absolute";d.top=d.left=-9999}b.appendChild(c);d=c.offsetWidth;b.removeChild(c);return d}}};a.Ob=function(){for(var b=["mm","cm","in","pt","pc"],c={},d=element.parentNode,e=0,f=b.length,j,g,h;e<f;e++){j=b[e];g=element.document.createElement("length-calc");h=g.style;h.position="absolute";h.top=h.left=-9999;h.width="100"+j;d.appendChild(g);c[j]=g.offsetWidth/100;d.removeChild(g)}return c}();a.Oa=new a("0");return a}();D.ra=function(){function a(b){this.C=b}a.prototype=
-{Vb:function(){if(!this.Qa){var b=this.C,c=b.length,d=D.g.Oa,e=new D.g("50%"),f=D.k.Y.V,j={top:1,center:1,bottom:1},g={left:1,center:1,right:1};d=["left",d,"top",d];if(c===1){b.push({type:f,value:"center"});c++}if(c===2){f&(b[0].type|b[1].type)&&b[0].value in j&&b[1].value in g&&b.push(b.shift());if(b[0].type&f)if(b[0].value==="center")d[1]=e;else d[0]=b[0].value;else if(b[0].J())d[1]=new D.g(b[0].value);if(b[1].type&f)if(b[1].value==="center")d[3]=e;else d[2]=b[1].value;else if(b[1].J())d[3]=new D.g(b[1].value)}this.Qa=
-d}return this.Qa},coords:function(b,c,d){var e=this.Vb(),f=e[1].a(b,c);b=e[3].a(b,d);return{x:Math.round(e[0]==="right"?c-f:f),y:Math.round(e[2]==="bottom"?d-b:b)}}};return a}();D.kb=function(){function a(b){this.F=b}a.prototype={Ga:/[a-z]+$/i,Aa:function(){return this.ua||(this.ua=this.F.match(this.Ga)[0].toLowerCase())},Pb:function(){var b=this.zb,c;if(b===undefined){b=this.Aa();c=parseFloat(this.F,10);b=this.zb=b==="deg"?c:b==="rad"?c/Math.PI*180:b==="grad"?c/400*360:b==="turn"?c*360:0}return b}};
-return a}();D.U=function(){function a(b){this.F=b}a.ec=/\s*rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d+|\d*\.\d+)\s*\)\s*/;a.prototype={parse:function(){if(!this.ha){var b=this.F,c=b.match(a.ec);if(c){this.ha="rgb("+c[1]+","+c[2]+","+c[3]+")";this.Pa=parseFloat(c[4])}else{this.ha=b;this.Pa=1}}},value:function(b){this.parse();return this.ha==="currentColor"?b.currentStyle.color:this.ha},Ra:function(){this.parse();return this.Pa}};return a}();D.k=function(){function a(c){this.ka=c;
-this.ch=0;this.C=[];this.ea=0}var b=a.Y={fa:1,Ja:2,S:4,ub:8,La:16,V:32,n:64,W:128,X:256,ga:512,wb:1024,URL:2048};a.Na=function(c,d){this.type=c;this.value=d};a.Na.prototype={Ea:function(){return this.type&b.n||this.type&b.W&&this.value==="0"},J:function(){return this.Ea()||this.type&b.ga}};a.prototype={mc:/\s/,$b:/^[\+\-]?(\d*\.)?\d+/,url:/^url\(\s*("([^"]*)"|'([^']*)'|([!#$%&*-~]*))\s*\)/i,Ya:/^\-?[_a-z][\w-]*/i,hc:/^("([^"]*)"|'([^']*)')/,Wb:/^#([\da-f]{6}|[\da-f]{3})/i,kc:{px:b.n,em:b.n,ex:b.n,
-mm:b.n,cm:b.n,"in":b.n,pt:b.n,pc:b.n,deg:b.fa,rad:b.fa,grad:b.fa},Lb:{aqua:1,black:1,blue:1,fuchsia:1,gray:1,green:1,lime:1,maroon:1,navy:1,olive:1,purple:1,red:1,silver:1,teal:1,white:1,yellow:1,currentColor:1},Kb:{rgb:1,rgba:1,hsl:1,hsla:1},next:function(c){function d(n,p){n=new a.Na(n,p);if(!c){k.C.push(n);k.ea++}return n}function e(){k.ea++;return null}var f,j,g,h,k=this;if(this.ea<this.C.length)return this.C[this.ea++];for(;this.mc.test(this.ka.charAt(this.ch));)this.ch++;if(this.ch>=this.ka.length)return e();
-j=this.ch;f=this.ka.substring(this.ch);g=f.charAt(0);switch(g){case "#":if(h=f.match(this.Wb)){this.ch+=h[0].length;return d(b.S,h[0])}break;case '"':case "'":if(h=f.match(this.hc)){this.ch+=h[0].length;return d(b.wb,h[2]||h[3]||"")}break;case "/":case ",":this.ch++;return d(b.X,g);case "u":if(h=f.match(this.url)){this.ch+=h[0].length;return d(b.URL,h[2]||h[3]||h[4]||"")}}if(h=f.match(this.$b)){g=h[0];this.ch+=g.length;if(f.charAt(g.length)==="%"){this.ch++;return d(b.ga,g+"%")}if(h=f.substring(g.length).match(this.Ya)){g+=
-h[0];this.ch+=h[0].length;return d(this.kc[h[0].toLowerCase()]||b.ub,g)}return d(b.W,g)}if(h=f.match(this.Ya)){g=h[0];this.ch+=g.length;if(g.toLowerCase()in this.Lb)return d(b.S,g);if(f.charAt(g.length)==="("){this.ch++;if(g.toLowerCase()in this.Kb){f=function(n){return n&&n.type&b.W};h=function(n){return n&&n.type&(b.W|b.ga)};var o=function(n,p){return n&&n.value===p},m=function(){return k.next(1)};if((g.charAt(0)==="r"?h(m()):f(m()))&&o(m(),",")&&h(m())&&o(m(),",")&&h(m())&&(g==="rgb"||g==="hsa"||
-o(m(),",")&&f(m()))&&o(m(),")"))return d(b.S,this.ka.substring(j,this.ch));return e()}return d(b.La,g+"(")}return d(b.V,g)}this.ch++;return d(b.Ja,g)},m:function(){return this.C[this.ea-- -2]},all:function(){for(;this.next(););return this.C},R:function(c,d){for(var e=[],f,j;f=this.next();){if(c(f)){j=true;this.m();break}e.push(f)}return d&&!j?null:e}};return a}();D.w={M:function(a){function b(c){this.element=c}D.q.Fa(b.prototype,D.w,a);return b},f:function(){if(this.j())this.Eb=this.N(this.yb=this.ba());
-return this.Eb},ba:function(){var a=this.element,b=a.style;a=a.currentStyle;var c=this.aa,d=this.da,e=this.Cb||(this.Cb=D.T+c),f=this.Db||(this.Db=D.Ma+d.charAt(0).toUpperCase()+d.substring(1));return b[f]||a.getAttribute(e)||b[d]||a.getAttribute(c)},d:function(){return!!this.f()},j:function(){return this.yb!==this.ba()}};D.mb=D.w.M({aa:D.T+"background",da:D.Ma+"Background",Ib:{scroll:1,fixed:1,local:1},qa:{"repeat-x":1,"repeat-y":1,repeat:1,"no-repeat":1},ac:{"padding-box":1,"border-box":1,"content-box":1},
-Jb:{"padding-box":1,"border-box":1},dc:{top:1,right:1,bottom:1,left:1,center:1},fc:{contain:1,cover:1},N:function(a){function b(q){return q.J()||q.type&h&&q.value in n}function c(q){return q.J()&&new D.g(q.value)||q.value==="auto"&&"auto"}var d=this.element.currentStyle,e,f,j=D.k.Y,g=j.X,h=j.V,k=j.S,o,m,n=this.dc,p,t,r=null;if(this.za()){a=new D.k(a);r={images:[]};for(f={};e=a.next();){o=e.type;m=e.value;if(!f.type&&o&j.La&&m==="linear-gradient("){p={P:[],type:"linear-gradient"};for(t={};e=a.next();){o=
-e.type;m=e.value;if(o&j.Ja&&m===")"){t.color&&p.P.push(t);p.P.length>1&&D.q.Fa(f,p);break}if(o&k){if(p.wa||p.Ba){e=a.m();if(e.type!==g)break;a.next()}t={color:new D.U(m)};e=a.next();if(e.J())t.Za=new D.g(e.value);else a.m()}else if(o&j.fa&&!p.wa&&!t.color&&!p.P.length)p.wa=new D.kb(e.value);else if(b(e)&&!p.Ba&&!t.color&&!p.P.length){a.m();p.Ba=new D.ra(a.R(function(q){return!b(q)},false))}else if(o&g&&m===","){if(t.color){p.P.push(t);t={}}}else break}}else if(!f.type&&o&j.URL){f.url=m;f.type="image"}else if(b(e)&&
-!f.size){a.m();f.position=new D.ra(a.R(function(q){return!b(q)},false))}else if(o&h)if(m in this.qa)f.repeat=m;else if(m in this.ac){f.origin=m;if(m in this.Jb)f.clip=m}else{if(m in this.Ib)f.rc=m}else if(o&k&&!r.color)r.color=new D.U(m);else if(o&g)if(m==="/"){e=a.next();o=e.type;m=e.value;if(o&h&&m in this.fc)f.size=m;else if(m=c(e))f.size={v:m,i:c(a.next())||a.m()&&m}}else{if(m===","&&f.type){r.images.push(f);f={}}}else return null}f.type&&r.images.push(f)}else this.gb(function(){var q=d.backgroundPositionX,
-w=d.backgroundPositionY,l=d.backgroundImage,u=d.backgroundColor;r={};if(u!=="transparent")r.color=new D.U(u);if(l!=="none")r.images=[{type:"image",url:(new D.k(l)).next().value,repeat:d.backgroundRepeat,position:new D.ra((new D.k(q+" "+w)).all())}]});return r},gb:function(a){var b=this.element.runtimeStyle,c=b.backgroundImage,d=b.backgroundColor;b.backgroundImage=b.backgroundColor="";a=a.call(this);b.backgroundImage=c;b.backgroundColor=d;return a},ba:function(){var a=this.element.currentStyle;return this.za()||
-this.gb(function(){return a.backgroundColor+" "+a.backgroundImage+" "+a.backgroundRepeat+" "+a.backgroundPositionX+" "+a.backgroundPositionY})},za:function(){var a=this.element;return a.style[this.da]||a.currentStyle.getAttribute(this.aa)},d:function(){return this.za()&&!!this.f()}});D.qb=D.w.M({bb:["Top","Right","Bottom","Left"],Zb:{uc:"1px",sc:"3px",tc:"5px"},N:function(){var a={},b={},c={},d=false,e=true,f=true,j=true;this.hb(function(){for(var g=this.element.currentStyle,h=0,k,o,m,n,p,t,r;h<4;h++){m=
-this.bb[h];r=m.charAt(0).toLowerCase();k=b[r]=g["border"+m+"Style"];o=g["border"+m+"Color"];m=g["border"+m+"Width"];if(h>0){if(k!==n)f=false;if(o!==p)e=false;if(m!==t)j=false}n=k;p=o;t=m;c[r]=new D.U(o);m=a[r]=new D.g(b[r]==="none"?"0":this.Zb[m]||m);if(m.a(this.element)>0)d=true}});return d?{fb:a,ic:b,Mb:c,nc:j,Nb:e,jc:f}:null},ba:function(){var a=this.element.currentStyle,b;this.hb(function(){b=a.borderWidth+"|"+a.borderStyle+"|"+a.borderColor});return b},hb:function(a){var b=this.element.runtimeStyle,
-c=b.borderWidth,d=b.borderStyle,e=b.borderColor;b.borderWidth=b.borderStyle=b.borderColor="";a=a.call(this);b.borderWidth=c;b.borderStyle=d;b.borderColor=e;return a}});(function(){D.sa=D.w.M({aa:"border-radius",da:"borderRadius",N:function(b){var c=null,d,e,f,j,g=false;if(b){e=new D.k(b);var h=function(){for(var k=[],o;(f=e.next())&&f.J();){j=new D.g(f.value);o=j.Va();if(o<0)return null;if(o>0)g=true;k.push(j)}return k.length>0&&k.length<5?{tl:k[0],tr:k[1]||k[0],br:k[2]||k[0],bl:k[3]||k[1]||k[0]}:
-null};if(b=h()){if(f){if(f.type&D.k.Y.X&&f.value==="/")d=h()}else d=b;if(g&&b&&d)c={x:b,y:d}}}return c}});var a=D.g.Oa;a={tl:a,tr:a,br:a,bl:a};D.sa.jb={x:a,y:a}})();D.ob=D.w.M({aa:"border-image",da:"borderImage",qa:{stretch:1,round:1,repeat:1,space:1},N:function(a){var b=null,c,d,e,f,j,g,h=0,k,o=D.k.Y,m=o.V,n=o.W,p=o.n,t=o.ga;if(a){c=new D.k(a);b={};for(var r=function(l){return l&&l.type&o.X&&l.value==="/"},q=function(l){return l&&l.type&m&&l.value==="fill"},w=function(){f=c.R(function(l){return!(l.type&
-(n|t))});if(q(c.next())&&!b.fill)b.fill=true;else c.m();if(r(c.next())){h++;j=c.R(function(){return!(d.type&(n|t|p))&&!(d.type&m&&d.value==="auto")});if(r(c.next())){h++;g=c.R(function(){return!(d.type&(n|p))})}}else c.m()};d=c.next();){a=d.type;e=d.value;if(a&(n|t)&&!f){c.m();w()}else if(q(d)&&!b.fill){b.fill=true;w()}else if(a&m&&this.qa[e]&&!b.repeat){b.repeat={i:e};if(d=c.next())if(d.type&m&&this.qa[d.value])b.repeat.Ha=d.value;else c.m()}else if(a&o.URL&&!b.src)b.src=e;else return null}if(!b.src||
-!f||f.length<1||f.length>4||j&&j.length>4||h===1&&j.length<1||g&&g.length>4||h===2&&g.length<1)return null;if(!b.repeat)b.repeat={i:"stretch"};if(!b.repeat.Ha)b.repeat.Ha=b.repeat.i;a=function(l,u){return{Q:u(l[0]),O:u(l[1]||l[0]),H:u(l[2]||l[0]),K:u(l[3]||l[1]||l[0])}};b.slice=a(f,function(l){return new D.g(l.type&n?l.value+"px":l.value)});b.width=j&&j.length>0?a(j,function(l){return l.type&(p|t)?new D.g(l.value):l.value}):(k=this.element.currentStyle)&&{Q:new D.g(k.borderTopWidth),O:new D.g(k.borderRightWidth),
-H:new D.g(k.borderBottomWidth),K:new D.g(k.borderLeftWidth)};b.ca=a(g||[0],function(l){return l.type&p?new D.g(l.value):l.value})}return b}});D.tb=D.w.M({aa:"box-shadow",da:"boxShadow",N:function(a){var b,c=D.g,d=D.k.Y,e;if(a){e=new D.k(a);b={ca:[],pa:[]};for(a=function(){for(var f,j,g,h,k,o;f=e.next();){g=f.value;j=f.type;if(j&d.X&&g===",")break;else if(f.Ea()&&!k){e.m();k=e.R(function(m){return!m.Ea()})}else if(j&d.S&&!h)h=g;else if(j&d.V&&g==="inset"&&!o)o=true;else return false}f=k&&k.length;
-if(f>1&&f<5){(o?b.pa:b.ca).push({oc:new c(k[0].value),qc:new c(k[1].value),blur:new c(k[2]?k[2].value:"0"),gc:new c(k[3]?k[3].value:"0"),color:new D.U(h||"currentColor")});return true}return false};a(););}return b&&(b.pa.length||b.ca.length)?b:null}});D.xb=D.w.M({ba:function(){var a=this.element.currentStyle;return a.visibility+"|"+a.display},N:function(){var a=this.element,b=a.runtimeStyle;a=a.currentStyle;var c=b.visibility,d;b.visibility="";d=a.visibility;b.visibility=c;return{lc:d!=="hidden",
-Qb:a.display!=="none"}},d:function(){return false}});D.p={L:function(a){function b(c,d,e){this.element=c;this.e=d;this.parent=e}D.q.Fa(b.prototype,D.p,a);return b},B:function(){return false},D:i(),cb:i(),u:i(),va:function(a,b){this.ab(a);for(var c=this.Z||(this.Z=[]),d=a+1,e=c.length,f;d<e;d++)if(f=c[d])break;c[a]=b;this.o().insertBefore(b,f||null)},ma:function(a){var b=this.Z;return b&&b[a]||null},ab:function(a){var b=this.ma(a),c=this.G;if(b&&c){c.removeChild(b);this.Z[a]=null}},na:function(a,b,
-c,d){var e=this.ta||(this.ta={}),f=e[a];if(!f){f=e[a]=D.q.ja("shape");if(b)f.appendChild(f[b]=D.q.ja(b));if(d){c=this.ma(d);if(!c){this.va(d,this.element.document.createElement("group"+d));c=this.ma(d)}}c.appendChild(f);a=f.style;a.position="absolute";a.left=a.top=0;a.behavior="url(#default#VML)"}return f},xa:function(a){var b=this.ta,c=b&&b[a];if(c){c.parentNode.removeChild(c);delete b[a]}return!!c},Wa:function(a){var b=this.element,c=b.offsetWidth,d=b.offsetHeight,e,f,j,g,h,k,o;e=a.x.tl.a(b,c);
-f=a.y.tl.a(b,d);j=a.x.tr.a(b,c);g=a.y.tr.a(b,d);h=a.x.br.a(b,c);k=a.y.br.a(b,d);o=a.x.bl.a(b,c);a=a.y.bl.a(b,d);c=Math.min(c/(e+j),d/(g+k),c/(o+h),d/(f+a));if(c<1){e*=c;f*=c;j*=c;g*=c;h*=c;k*=c;o*=c;a*=c}return{x:{tl:e,tr:j,br:h,bl:o},y:{tl:f,tr:g,br:k,bl:a}}},la:function(a,b,c){b=b||1;var d,e,f=this.element;e=f.offsetWidth*b;f=f.offsetHeight*b;var j=this.e.s,g=Math.floor,h=Math.ceil,k=a?a.Q*b:0,o=a?a.O*b:0,m=a?a.H*b:0;a=a?a.K*b:0;var n,p,t,r,q;if(c||j.d()){d=this.Wa(c||j.f());c=d.x.tl*b;j=d.y.tl*
-b;n=d.x.tr*b;p=d.y.tr*b;t=d.x.br*b;r=d.y.br*b;q=d.x.bl*b;b=d.y.bl*b;e="m"+g(a)+","+g(j)+"qy"+g(c)+","+g(k)+"l"+h(e-n)+","+g(k)+"qx"+h(e-o)+","+g(p)+"l"+h(e-o)+","+h(f-r)+"qy"+h(e-t)+","+h(f-m)+"l"+g(q)+","+h(f-m)+"qx"+g(a)+","+h(f-b)+" x e"}else e="m"+g(a)+","+g(k)+"l"+h(e-o)+","+g(k)+"l"+h(e-o)+","+h(f-m)+"l"+g(a)+","+h(f-m)+"xe";return e},o:function(){var a=this.parent.ma(this.zIndex),b;if(!a){a=this.element.document.createElement(this.ia);b=a.style;b.position="absolute";b.top=b.left=0;this.parent.va(this.zIndex,
-a)}return a},h:function(){this.parent.ab(this.zIndex);delete this.ta;delete this.Z}};D.vb=D.p.L({d:function(){var a=this.e;for(var b in a)if(a.hasOwnProperty(b)&&a[b].d())return true;return false},B:function(){return this.e.eb.j()},cb:function(){if(this.d()){var a=this.element,b=a,c,d,e=this.o().style,f=0;c=0;do b=b.offsetParent;while(b&&b.currentStyle.position==="static");c=a.getBoundingClientRect();if(b){d=b.getBoundingClientRect();b=b.currentStyle;f=c.left-d.left-(parseFloat(b.borderLeftWidth)||
-0);c=c.top-d.top-(parseFloat(b.borderTopWidth)||0)}else{b=a.document.documentElement;f=c.left+b.scrollLeft-b.clientLeft;c=c.top+b.scrollTop-b.clientTop}e.left=f;e.top=c;e.zIndex=a.currentStyle.position==="static"?-1:a.currentStyle.zIndex}},u:i(),db:function(){var a=this.e.eb.f();this.o().style.display=a.lc&&a.Qb?"":"none"},D:function(){this.d()?this.db():this.h()},o:function(){var a=this.G,b,c;if(!a){b=this.element;a=this.G=b.document.createElement("css3-container");c=a.style;c.position=b.currentStyle.position===
-"fixed"?"fixed":"absolute";this.db();b.parentNode.insertBefore(a,b)}return a},h:function(){var a=this.G;a&&a.parentNode&&a.parentNode.removeChild(a);delete this.G;delete this.Z}});D.lb=D.p.L({zIndex:2,ia:"background",B:function(){var a=this.e;return a.I.j()||a.s.j()},d:function(){var a=this.e,b=this.element;return b.offsetWidth&&b.offsetHeight&&(a.z.d()||a.s.d()||a.I.d()||a.A.d()&&a.A.f().pa)},u:function(){this.d()&&this.Sa()},D:function(){this.h();this.d()&&this.Sa()},Sa:function(){this.Rb();this.Sb()},
-Rb:function(){var a=this.e.I.f(),b=this.element,c=a&&a.color&&a.color.value(b),d,e,f;if(c&&c!=="transparent"){this.Xa();d=this.na("bgColor","fill",this.o(),1);e=b.offsetWidth;b=b.offsetHeight;d.stroked=false;d.coordsize=e*2+","+b*2;d.coordorigin="1,1";d.path=this.la(null,2);f=d.style;f.width=e;f.height=b;d.fill.color=c;a=a.color.Ra();if(a<1)d.fill.opacity=a}else this.xa("bgColor")},Sb:function(){var a=this.e.I.f();a=a&&a.images;var b,c,d,e,f,j;if(a){this.Xa();b=this.element;d=b.offsetWidth;e=b.offsetHeight;
-for(j=a.length;j--;){b=a[j];c=this.na("bgImage"+j,"fill",this.o(),2);c.stroked=false;c.fill.type="tile";c.fillcolor="none";c.coordsize=d*2+","+e*2;c.coordorigin="1,1";c.path=this.la(0,2);f=c.style;f.width=d;f.height=e;if(b.type==="linear-gradient")this.Hb(c,b);else{c.fill.src=b.url;this.cc(c,j)}}}for(j=a?a.length:0;this.xa("bgImage"+j++););},cc:function(a,b){D.q.ib(a.fill.src,function(c){var d=a.fill,e=this.element,f=e.offsetWidth,j=e.offsetHeight,g=this.e,h=g.$.f(),k=h&&h.fb;h=k?k.t.a(e):0;var o=
-k?k.r.a(e):0,m=k?k.b.a(e):0;k=k?k.l.a(e):0;g=g.I.f().images[b];e=g.position?g.position.coords(e,f-c.v-k-o,j-c.i-h-m):{x:0,y:0};g=g.repeat;m=o=0;var n=f+1,p=j+1,t=D.Da?0:1;k=e.x+k+0.5;h=e.y+h+0.5;d.position=k/f+","+h/j;if(g&&g!=="repeat"){if(g==="repeat-x"||g==="no-repeat"){o=h+1;p=h+c.i+t}if(g==="repeat-y"||g==="no-repeat"){m=k+1;n=k+c.v+t}a.style.clip="rect("+o+"px,"+n+"px,"+p+"px,"+m+"px)"}},this)},Hb:function(a,b){function c(x,y,v,C,G){if(v===0||v===180)return[C,y];else if(v===90||v===270)return[x,
-G];else{v=Math.tan(-v*m/180);x=v*x-y;y=-1/v;C=y*C-G;G=y-v;return[(C-x)/G,(v*C-y*x)/G]}}function d(){q=h>=90&&h<270?j:0;w=h<180?g:0;l=j-q;u=g-w}function e(x,y){var v=y[0]-x[0];x=y[1]-x[1];return Math.abs(v===0?x:x===0?v:Math.sqrt(v*v+x*x))}var f=this.element,j=f.offsetWidth,g=f.offsetHeight;a=a.fill;var h=b.wa,k=b.Ba;b=b.P;var o=b.length,m=Math.PI,n,p,t,r,q,w,l,u,s,z,B,A;if(k){k=k.coords(f,j,g);n=k.x;p=k.y}if(h){h=h.Pb();if(h<0)h+=360;h%=360;d();if(!k){n=q;p=w}k=c(n,p,h,l,u);t=k[0];r=k[1]}else if(k){t=
-j-n;r=g-p}else{n=p=t=0;r=g}k=t-n;s=r-p;if(h===undefined){h=-Math.atan2(s,k)/m*180;if(h<0)h+=360;h%=360;d()}k=Math.atan2(k*j/g,s)/m*180;k+=180;k%=360;z=e([n,p],[t,r]);t=e([q,w],c(q,w,h,l,u));r=[];p=e([n,p],c(n,p,h,q,w))/t*100;n=[];for(s=0;s<o;s++)n.push(b[s].Za?b[s].Za.a(f,z):s===0?0:s===o-1?z:null);for(s=1;s<o;s++){if(n[s]===null){B=n[s-1];z=s;do A=n[++z];while(A===null);n[s]=B+(A-B)/(z-s+1)}n[s]=Math.max(n[s],n[s-1])}for(s=0;s<o;s++)r.push(p+n[s]/t*100+"% "+b[s].color.value(f));a.angle=k;a.type=
-"gradient";a.method="sigma";a.color=b[0].color.value(f);a.color2=b[o-1].color.value(f);a.colors.value=r.join(",")},Xa:function(){var a=this.element.runtimeStyle;a.backgroundImage="url(about:blank)";a.backgroundColor="transparent"},h:function(){D.p.h.call(this);var a=this.element.runtimeStyle;a.backgroundImage=a.backgroundColor=""}});D.pb=D.p.L({zIndex:4,ia:"border",B:function(){var a=this.e;return a.$.j()||a.s.j()},d:function(){var a=this.e;return a.z.d()||a.s.d()||a.I.d()},u:function(){this.d()&&
-this.Ta()},D:function(){this.h();this.d()&&this.Ta()},Ta:function(){var a=this.element,b=a.offsetWidth,c=a.offsetHeight,d,e,f,j,g,h;if(this.e.$.f()){this.Xb();f=this.Ub(2);g=0;for(h=f.length;g<h;g++){j=f[g];d=this.na("borderPiece"+g,j.stroke?"stroke":"fill",this.o());d.coordsize=b*2+","+c*2;d.coordorigin="1,1";d.path=j.path;e=d.style;e.width=b;e.height=c;d.filled=!!j.fill;d.stroked=!!j.stroke;if(j.stroke){d=d.stroke;d.weight=j.Ia+"px";d.color=j.color.value(a);d.dashstyle=j.stroke==="dashed"?"2 2":
-j.stroke==="dotted"?"1 1":"solid";d.linestyle=j.stroke==="double"&&j.Ia>2?"ThinThin":"Single"}else d.fill.color=j.fill.value(a)}for(;this.xa("borderPiece"+g++););}},Xb:function(){var a=this.element,b=a.currentStyle,c=a.runtimeStyle,d=a.tagName,e;if(d==="BUTTON"||d==="INPUT"&&a.type in{submit:1,button:1,reset:1}){c.borderWidth="";a=this.e.$.bb;for(e=a.length;e--;){d=a[e];c["padding"+d]="";c["padding"+d]=parseInt(b["padding"+d])+parseInt(b["border"+d+"Width"])+(!D.Da&&e%2?1:0)}c.borderWidth=0}else if(D.Yb){if(a.childNodes.length!==
-1||a.firstChild.tagName!=="ie6-mask"){b=a.document.createElement("ie6-mask");d=b.style;d.visibility="visible";for(d.zoom=1;d=a.firstChild;)b.appendChild(d);a.appendChild(b);c.visibility="hidden"}}else c.borderColor="transparent"},Ub:function(a){var b=this.element,c,d,e=this.e.$,f=[],j,g,h,k,o,m,n,p;if(e.d()){e=e.f();m=e.fb;n=e.ic;p=e.Mb;if(e.nc&&e.jc&&e.Nb){e=m.t.a(b);h=e/2;f.push({path:this.la({Q:h,O:h,H:h,K:h},a),stroke:n.t,color:p.t,Ia:e})}else{a=a||1;c=b.offsetWidth;d=b.offsetHeight;e=m.t.a(b);
-h=m.r.a(b);k=m.b.a(b);b=m.l.a(b);var t={t:e,r:h,b:k,l:b};b=this.e.s;if(b.d())o=this.Wa(b.f());j=Math.floor;g=Math.ceil;var r=function(l,u){return o?o[l][u]:0},q=function(l,u,s,z,B,A){var x=r("x",l),y=r("y",l),v=l.charAt(1)==="r";l=l.charAt(0)==="b";return x>0&&y>0?(A?"al":"ae")+(v?g(c-x):j(x))*a+","+(l?g(d-y):j(y))*a+","+(j(x)-u)*a+","+(j(y)-s)*a+","+z*65535+","+2949075*(B?1:-1):(A?"m":"l")+(v?c-u:u)*a+","+(l?d-s:s)*a},w=function(l,u,s,z){var B=l==="t"?j(r("x","tl"))*a+","+g(u)*a:l==="r"?g(c-u)*a+
-","+j(r("y","tr"))*a:l==="b"?g(c-r("x","br"))*a+","+j(d-u)*a:j(u)*a+","+g(d-r("y","bl"))*a;l=l==="t"?g(c-r("x","tr"))*a+","+g(u)*a:l==="r"?g(c-u)*a+","+g(d-r("y","br"))*a:l==="b"?j(r("x","bl"))*a+","+j(d-u)*a:j(u)*a+","+j(r("y","tl"))*a;return s?(z?"m"+l:"")+"l"+B:(z?"m"+B:"")+"l"+l};b=function(l,u,s,z,B,A){var x=l==="l"||l==="r",y=t[l],v,C;if(y>0&&n[l]!=="none"){v=t[x?l:u];u=t[x?u:l];C=t[x?l:s];s=t[x?s:l];if(n[l]==="dashed"||n[l]==="dotted"){f.push({path:q(z,v,u,A+45,0,1)+q(z,0,0,A,1,0),fill:p[l]});
-f.push({path:w(l,y/2,0,1),stroke:n[l],Ia:y,color:p[l]});f.push({path:q(B,C,s,A,0,1)+q(B,0,0,A-45,1,0),fill:p[l]})}else f.push({path:q(z,v,u,A+45,0,1)+w(l,y,0,0)+q(B,C,s,A,0,0)+(n[l]==="double"&&y>2?q(B,C-j(C/3),s-j(s/3),A-45,1,0)+w(l,g(y/3*2),1,0)+q(z,v-j(v/3),u-j(u/3),A,1,0)+"x "+q(z,j(v/3),j(u/3),A+45,0,1)+w(l,j(y/3),1,0)+q(B,j(C/3),j(s/3),A,0,0):"")+q(B,0,0,A-45,1,0)+w(l,0,1,0)+q(z,0,0,A,1,0),fill:p[l]})}};b("t","l","r","tl","tr",90);b("r","t","b","tr","br",0);b("b","r","l","br","bl",-90);b("l",
-"b","t","bl","tl",-180)}}return f},h:function(){D.p.h.call(this);this.element.runtimeStyle.borderColor=""}});D.nb=D.p.L({zIndex:5,bc:["t","tr","r","br","b","bl","l","tl","c"],B:function(){var a=this.e;return a.z.j()||a.z.j()},d:function(){return this.e.z.d()},u:function(){if(this.d()){var a=this.e.z.f();this.o();var b=this.element,c=this.$a;D.q.ib(a.src,function(d){function e(q,w,l,u,s){q=c[q].style;q.width=w;q.height=l;q.left=u;q.top=s}function f(q,w,l){for(var u=0,s=q.length;u<s;u++)c[q[u]].imagedata[w]=
-l}var j=b.offsetWidth,g=b.offsetHeight,h=a.width,k=h.Q.a(b),o=h.O.a(b),m=h.H.a(b);h=h.K.a(b);var n=a.slice,p=n.Q.a(b),t=n.O.a(b),r=n.H.a(b);n=n.K.a(b);e("tl",h,k,0,0);e("t",j-h-o,k,h,0);e("tr",o,k,j-o,0);e("r",o,g-k-m,j-o,k);e("br",o,m,j-o,g-m);e("b",j-h-o,m,h,g-m);e("bl",h,m,0,g-m);e("l",h,g-k-m,0,k);e("c",j-h-o,g-k-m,h,k);f(["tl","t","tr"],"cropBottom",(d.i-p)/d.i);f(["tl","l","bl"],"cropRight",(d.v-n)/d.v);f(["bl","b","br"],"cropTop",(d.i-r)/d.i);f(["tr","r","br"],"cropLeft",(d.v-t)/d.v);if(a.repeat.Ha===
-"stretch"){f(["l","r","c"],"cropTop",p/d.i);f(["l","r","c"],"cropBottom",r/d.i)}if(a.repeat.i==="stretch"){f(["t","b","c"],"cropLeft",n/d.v);f(["t","b","c"],"cropRight",t/d.v)}c.c.style.display=a.fill?"":"none"},this)}else this.h()},D:function(){this.h();this.d()&&this.u()},o:function(){var a=this.G,b,c,d,e=this.bc,f=e.length;if(!a){a=this.G=this.element.document.createElement("border-image");b=a.style;b.position="absolute";this.$a={};for(d=0;d<f;d++){c=this.$a[e[d]]=D.q.ja("rect");c.appendChild(D.q.ja("imagedata"));
-b=c.style;b.behavior="url(#default#VML)";b.position="absolute";b.top=b.left=0;c.imagedata.src=this.e.z.f().src;c.stroked=false;c.filled=false;a.appendChild(c)}this.parent.va(this.zIndex,a)}return a}});D.sb=D.p.L({zIndex:1,ia:"outset-box-shadow",B:function(){var a=this.e;return a.A.j()||a.s.j()},d:function(){var a=this.e.A;return a.d()&&a.f().ca[0]},u:function(){if(this.d()){var a=this,b=this.element,c=this.o(),d=this.e,e=d.A.f().ca;d=d.s.f();for(var f=e.length,j=f,g,h=b.offsetWidth,k=b.offsetHeight,
-o=D.Da?1:0,m=["tl","tr","br","bl"],n,p,t,r,q,w,l,u,s,z,B,A,x,y=function(v,C,G,O,P,Q,R){v=a.na("shadow"+v+C,"fill",c,f-v);C=v.style;var I=v.fill;C.left=G;C.top=O;v.coordsize=h*2+","+k*2;v.coordorigin="1,1";v.stroked=false;v.filled=true;I.color=P.value(b);if(Q){I.type="gradienttitle";I.color2=I.color;I.opacity=0}v.path=R;C.width=h;C.height=k;return v};j--;){p=e[j];r=p.oc.a(b);q=p.qc.a(b);g=p.gc.a(b);w=p.blur.a(b);p=p.color;l=-g-w;if(!d&&w)d=D.sa.jb;l=this.la({Q:l,O:l,H:l,K:l},2,d);if(w){u=(g+w)*2+h;
-s=(g+w)*2+k;z=w*2/u;B=w*2/s;if(w-g>h/2||w-g>k/2)for(g=4;g--;){n=m[g];A=n.charAt(0)==="b";x=n.charAt(1)==="r";n=y(j,n,r,q,p,w,l);t=n.fill;t.focusposition=(x?1-z:z)+","+(A?1-B:B);t.focussize="0,0";n.style.clip="rect("+((A?s/2:0)+o)+"px,"+(x?u:u/2)+"px,"+(A?s:s/2)+"px,"+((x?u/2:0)+o)+"px)"}else{n=y(j,"",r,q,p,w,l);t=n.fill;t.focusposition=z+","+B;t.focussize=1-z*2+","+(1-B*2)}}else{n=y(j,"",r,q,p,w,l);r=p.Ra();if(r<1)n.fill.opacity=r}}}else this.h()},D:function(){this.h();this.u()}});D.rb=D.p.L({zIndex:3,
-ia:"inset-box-shadow",B:function(){var a=this.e;return a.A.j()||a.s.j()},d:function(){var a=this.e.A;return a.d()&&a.f().pa[0]},u:i(),D:i()})}var E,F,H,J,K,L,M;function update(){init();var a=element.getBoundingClientRect(),b=a.left,c=a.top,d=a.right-b;a=a.bottom-c;var e,f;if(b!==H||c!==J){e=0;for(f=K.length;e<f;e++)K[e].cb();H=b;J=c}if(d!==E||a!==F){e=0;for(f=K.length;e<f;e++)K[e].u();E=d;F=a}}
-function propChanged(){init();var a,b,c=[];a=0;for(b=K.length;a<b;a++)K[a].B()&&c.push(K[a]);a=0;for(b=c.length;a<b;a++)c[a].D()}function mouseEntered(){event.srcElement.className+=" "+D.Ka+"hover";setTimeout(propChanged,0)}function mouseLeft(){var a=event.srcElement;a.className=a.className.replace(new RegExp("\\b"+D.Ka+"hover\\b","g"),"");setTimeout(propChanged,0)}function N(){var a=event.propertyName;if(a==="className"||a==="id")propChanged()}
-function cleanup(){var a,b;if(K){a=0;for(b=K.length;a<b;a++)K[a].h();K=null}L=null;if(M){a=0;for(b=M.length;a<b;a++){M[a].detachEvent("onpropertychange",N);M[a].detachEvent("onmouseenter",mouseEntered);M[a].detachEvent("onmouseleave",mouseLeft)}M=null}D.oa===8&&D.Ca.remove(update)}
-function init(){if(!K){var a=element;a.runtimeStyle.zoom=1;L={I:new D.mb(a),$:new D.qb(a),z:new D.ob(a),s:new D.sa(a),A:new D.tb(a),eb:new D.xb(a)};var b=new D.vb(a,L);K=[b,new D.sb(a,L,b),new D.lb(a,L,b),new D.rb(a,L,b),new D.pb(a,L,b),new D.nb(a,L,b)];var c=element;if(a=c.currentStyle.getAttribute(D.T+"watch-ancestors")){M=[];a=parseInt(a,10);b=0;for(c=c.parentNode;c&&(a==="NaN"||b++<a);){M.push(c);c.attachEvent("onpropertychange",N);c.attachEvent("onmouseenter",mouseEntered);c.attachEvent("onmouseleave",
-mouseLeft);c=c.parentNode}}D.oa===8&&D.Ca.add(update)}}element.readyState==="complete"&&update();
-    </script>
-
-</PUBLIC:COMPONENT>
index 408a541..a083eec 100644 (file)
@@ -73,6 +73,3 @@ jQuery(document).ready(function(){
     jQuery("#prefs-menu").addClass("sf-menu sf-js-enabled").supersubs().superfish().supposition({ speed: 'fast' });
 });
 </script>
-<!--[if (lt IE 9)&(gt IE 6)]>
-<link rel="stylesheet" href="<%RT->Config->Get('WebPath')%>/NoAuth/css/web2/msie-pie.css" type="text/css" media="all" />
-<![endif]-->
diff --git a/rt/share/html/NoAuth/css/web2/msie-pie.css b/rt/share/html/NoAuth/css/web2/msie-pie.css
deleted file mode 100644 (file)
index 73d76d0..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
-%#                                          <sales@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-.search-result-views,
-.ticket-transaction div.metadata span.actions,
-div#ticket-history div.downloadattachment, 
-.ticket-transaction div.metadata  span.type,
-.titlebox,
-.titlebox .titlebox-title .right,
-.titlebox .titlebox-title .left,
-div#footer,
-#main-navigation,
-#page-navigation,
-div#body {
-    behavior: url(<%RT->Config->Get('WebPath')%>/NoAuth/css/images/PIE.htc);
-}
index 5199637..a5e5dac 100644 (file)
@@ -46,7 +46,7 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 function createCookie(name,value,days) {
-    var path = "<%RT->Config->Get('WebPath')%>" ? "<%RT->Config->Get('WebPath')%>" : "/";
+    var path = <%RT->Config->Get('WebPath')|n,j%> ? <%RT->Config->Get('WebPath')|n,j%> : "/";
 
     if (days) {
         var date = new Date();
index db244d1..b4f678c 100644 (file)
@@ -70,7 +70,7 @@ jQuery(function() {
             continue;
 
         var options = {
-            source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Users"
+            source: <% RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/Users"
         };
 
         var queryargs = [];
index 62ee922..5bfce41 100644 (file)
@@ -294,8 +294,8 @@ function ReplaceAllTextareas(encoded) {
             textArea.parentNode.appendChild(typeField);
 
 
-            CKEDITOR.replace(textArea.name,{width:'100%',height:'<% RT->Config->Get('MessageBoxRichTextHeight') %>'});
-            CKEDITOR.basePath = "<%RT->Config->Get('WebPath')%>/NoAuth/RichText/";
+            CKEDITOR.replace(textArea.name,{width:'100%',height:<% RT->Config->Get('MessageBoxRichTextHeight') |n,j%>});
+            CKEDITOR.basePath = <%RT->Config->Get('WebPath')|n,j%>+"/NoAuth/RichText/";
 
             jQuery("#" + textArea.name + "___Frame").addClass("richtext-editor");
         }
index fdd9c17..42aa16b 100644 (file)
@@ -95,7 +95,7 @@ $ARGS{'OrderBy'} = join '|', grep defined && /\S/, (ref $ARGS{'OrderBy'})? @{$AR
 my ( $AvailableColumns, $CurrentFormat );
 ( $ARGS{Format}, $AvailableColumns, $CurrentFormat ) = $m->comp(
     '/Search/Elements/BuildFormatString',
-    cfqueues => {}, %ARGS
+    %ARGS
 );
 
 if ($ARGS{'Save'}) {
index 9ae803d..9a2212b 100755 (executable)
@@ -149,10 +149,16 @@ else {
             }
             # Set custom field
             elsif ($k =~ /^$cf_spec/) {
-                my $cf = RT::CustomField->new( RT->SystemUser );
-                my $cfk = $1 || $2;
-                unless($cf->LoadByName( Name => $cfk )) {
-                    push @comments, "# Invalid custom field name ($cfk)";
+                my $key = $1 || $2;
+
+                my $cf = RT::CustomField->new( $session{CurrentUser} );
+                $cf->LoadByName( Name => $key, Queue => $data{Queue} || $v{Queue} );
+                unless ( $cf->id ) {
+                    $cf->LoadByName( Name => $key, Queue => 0 );
+                }
+
+                if (not $cf->id) {
+                    push @comments, "# Invalid custom field name ($key)";
                     delete $data{$k};
                     next;
                 }
@@ -348,9 +354,15 @@ else {
         }
         # Set custom field
         elsif ($key =~ /^$cf_spec/) {
-            my $cf = RT::CustomField->new( RT->SystemUser );
             $key = $1 || $2;
-            if (not $cf->LoadByName( Name => $key )) {
+
+            my $cf = RT::CustomField->new( $session{CurrentUser} );
+            $cf->LoadByName( Name => $key, Queue => $ticket->Queue );
+            unless ( $cf->id ) {
+                $cf->LoadByName( Name => $key, Queue => 0 );
+            }
+
+            if (not $cf->id) {
                 $n = 0;
                 $s = "Unknown custom field.";
             }
index e2e1830..bf4f257 100755 (executable)
@@ -100,7 +100,8 @@ if ($changes) {
                     my $tick = RT::Ticket->new($session{CurrentUser});
                     $tick->Load($nkey);
                     if ($tick->Id) {
-                        $nkey = $uri->FromObject($tick);
+                        $uri->FromObject($tick);
+                        $nkey = $uri->URI;
                     }
                     else {
                         $n = 0;
index 1ffa2b2..2e45f67 100644 (file)
@@ -49,7 +49,6 @@
 %#
 <%ARGS>
 $id
-$args => undef
 $format => undef
 $fields => undef
 </%ARGS>
@@ -57,8 +56,6 @@ $fields => undef
 my $trans = RT::Transactions->new($session{CurrentUser});
 my ($c, $o, $k, $e) = ("", [], {} , "");
 
-chomp $args;
-my @arglist = split('/', $args);
 my $tid = $id;
 
 $trans->Limit(FIELD => 'Id', OPERATOR => '=', VALUE => $tid);
index aa80b0d..8d3345f 100755 (executable)
@@ -81,10 +81,9 @@ if ($id && $object && $id != $object) {
     goto OUTPUT;
 }
 $id ||= $object;
-unless ($id =~ /^\d+$/ && $to =~ /^\d+$/) {
-    my $bad = ($id !~ /^\d+$/) ? $id : $to;
+unless ($id =~ /^\d+$/) {
     $output = $r->path_info. "\n";
-    $output .= "Invalid ticket id: '$bad'.\n";
+    $output .= "Invalid ticket id: '$id'.\n";
     $status = "400 Bad Request";
     goto OUTPUT;
 }
index ae4c7ba..b200f90 100644 (file)
@@ -78,7 +78,7 @@
 
 
 <div id="pick-criteria">
-    <& Elements/PickCriteria, query => $query{'Query'}, cfqueues => $queues &>
+    <& Elements/PickCriteria, query => $query{'Query'}, queues => $queues &>
 <& /Elements/Submit,  Label => loc('Add these terms'), SubmitId => 'AddClause', Name => 'AddClause'&>
 <& /Elements/Submit, Label => loc('Add these terms and Search'), SubmitId => 'DoSearch', Name => 'DoSearch'&>
 </div>
@@ -275,7 +275,7 @@ my ( $AvailableColumns, $CurrentFormat );
 ( $query{'Format'}, $AvailableColumns, $CurrentFormat ) = $m->comp(
     'Elements/BuildFormatString',
     %ARGS,
-    cfqueues => $queues,
+    queues => $queues,
     Format => $query{'Format'},
 );
 
@@ -308,7 +308,7 @@ if ( $ARGS{'DoSearch'} ) {
         SavedChartSearchId => $ARGS{'SavedChartSearchId'},
         SavedSearchId => $saved_search{'Id'},
     );
-    RT::Interface::Web::Redirect(RT->Config->Get('WebPath') . '/Search/Results.html?' . $redir_query_string);
+    RT::Interface::Web::Redirect(RT->Config->Get('WebURL') . 'Search/Results.html?' . $redir_query_string);
     $m->abort;
 }
 
index 884d183..070ce7c 100644 (file)
@@ -124,7 +124,7 @@ my %query;
 <input type="hidden" class="hidden" name="Query" value="<% $ARGS{Query} %>" />
 <input type="hidden" class="hidden" name="SavedChartSearchId" value="<% $saved_search->{SearchId} || 'new' %>" />
 
-<&|/l, $m->scomp('Elements/SelectChartType', Name => 'ChartStyle', Default => $ChartStyle), $m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $ARGS{Query}, Default => $PrimaryGroupBy) 
+<&|/l_unsafe, $m->scomp('Elements/SelectChartType', Name => 'ChartStyle', Default => $ChartStyle), $m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $ARGS{Query}, Default => $PrimaryGroupBy) 
 &>[_1] chart by [_2]</&><input type="submit" class="button" value="<%loc('Update Chart')%>" />
 </form>
 </&>
index 3769972..a39287b 100644 (file)
@@ -48,7 +48,7 @@
 <%ARGS>
 $Format => RT->Config->Get('DefaultSearchResultFormat')
 
-%cfqueues => ()
+%queues => ()
 
 $Face => undef
 $Size => undef
@@ -111,17 +111,11 @@ my @fields = (
 $m->callback( CallbackOnce => 1, CallbackName => 'SetFieldsOnce', Fields => \@fields );
 
 my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
-foreach my $id (keys %cfqueues) {
+foreach my $id (keys %queues) {
     # Gotta load up the $queue object, since queues get stored by name now. my $id
     my $queue = RT::Queue->new($session{'CurrentUser'});
     $queue->Load($id);
-    unless ($queue->id) {
-        # XXX TODO: This ancient code dates from a former developer
-        # we have no idea what it means or why cfqueues are so encoded.
-        $id =~ s/^.'*(.*).'*$/$1/;
-        $queue->Load($id);
-    }
-    $CustomFields->LimitToQueue($queue->Id);
+    $CustomFields->LimitToQueue($queue->Id) if $queue->Id;
 }
 $CustomFields->LimitToGlobal;
 
index 01b78c7..be05da3 100644 (file)
@@ -130,10 +130,10 @@ my ($i,$total);
                          );
 </%perl>
 <td class="label collection-as-table">
-<a href=<% RT->Config->Get('WebURL') %>Search/Results.html?<%$QueryString%>><%$key%></a>
+<a href=<% RT->Config->Get('WebPath') %>/Search/Results.html?<%$QueryString%>><%$key%></a>
 </td>
 <td class="value collection-as-table">
-<a href=<% RT->Config->Get('WebURL') %>Search/Results.html?<%$QueryString%>><%$value%></a>
+<a href=<% RT->Config->Get('WebPath') %>/Search/Results.html?<%$QueryString%>><%$value%></a>
 </td>
 % } else {
 <td class="label collection-as-table"><% $key %></td>
index 7223b75..db7d9f5 100644 (file)
@@ -103,7 +103,7 @@ my @lines = (
         Value => {
             Type => 'component',
             Path => '/Elements/SelectStatus',
-            Arguments => { SkipDeleted => 1 },
+            Arguments => { SkipDeleted => 1, Queues => \%queues },
         },
     },
     {
@@ -124,7 +124,7 @@ my @lines = (
         Value => {
             Type => 'component',
             Path => '/Elements/SelectOwner',
-            Arguments => { ValueAttribute => 'Name' },
+            Arguments => { ValueAttribute => 'Name', Queues => \%queues },
         },
     },
     {
@@ -214,3 +214,6 @@ my @lines = (
 $m->callback( Conditions => \@lines );
 
 </%INIT>
+<%ARGS>
+%queues => ()
+</%ARGS>
index 4b9a88b..f2dc21f 100644 (file)
 % }
 <%INIT>
 my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
-foreach my $id (keys %cfqueues) {
-    # Gotta load up the $queue object, since queues get stored by name now. my $id
+foreach my $id (keys %queues) {
+    # Gotta load up the $queue object, since queues get stored by name now.
     my $queue = RT::Queue->new($session{'CurrentUser'});
     $queue->Load($id);
-    unless ($queue->id) {
-        # XXX TODO: This ancient code dates from a former developer
-        # we have no idea what it means or why cfqueues are so encoded.
-        $id =~ s/^.'*(.*).'*$/$1/;
-
-        # unescape internal quotes
-        $id =~ s/(\\(.))/$2 eq "'" ? "'" : $1/eg;
-
-        $queue->Load($id);
-    }
-    $CustomFields->LimitToQueue($queue->Id);
+    $CustomFields->LimitToQueue($queue->Id) if $queue->Id;
 }
 $CustomFields->LimitToGlobal;
 $m->callback(
@@ -124,10 +114,10 @@ while ( my $CustomField = $CustomFields->Next ) {
     push @lines, \%line;
 }
 
-$m->callback( Conditions => \@lines, Queues => \%cfqueues );
+$m->callback( Conditions => \@lines, Queues => \%queues );
 
 </%INIT>
 
 <%ARGS>
-%cfqueues => undef
+%queues => ()
 </%ARGS>
index 5d0b8af..74547c7 100644 (file)
@@ -53,7 +53,7 @@
 
 <& PickBasics &>
 <& PickCustomerFields &>
-<& PickCFs, cfqueues => \%cfqueues &>
+<& PickCFs, queues => \%queues &>
 
 <tr class="separator"><td colspan="3"><hr /></td></tr>
 <tr>
@@ -69,5 +69,5 @@
 <%ARGS>
 $addquery => 0
 $query => undef
-%cfqueues => undef
+%queues => ()
 </%ARGS>
index 0040d2a..171b38d 100755 (executable)
@@ -46,7 +46,7 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <& /Elements/Header, Title => $title,
-    Refresh => $session{'tickets_refresh_interval'} || RT->Config->Get('SearchResultsRefreshInterval', $session{'CurrentUser'} ),
+    Refresh => $refresh,
     LinkRel => \%link_rel &>
 <& /Elements/Tabs &>
 <& /Elements/CollectionList, 
@@ -148,6 +148,16 @@ if ($ARGS{'TicketsRefreshInterval'}) {
        $session{'tickets_refresh_interval'} = $ARGS{'TicketsRefreshInterval'};
 }
 
+my $refresh = $session{'tickets_refresh_interval'}
+    || RT->Config->Get('SearchResultsRefreshInterval', $session{'CurrentUser'} );
+
+if (RT->Config->Get('RestrictReferrer') and $refresh and not $m->request_args->{CSRF_Token}) {
+    my $token = RT::Interface::Web::StoreRequestToken( $session{'CurrentSearchHash'} );
+    $m->notes->{RefreshURL} = RT->Config->Get('WebURL')
+        . "Search/Results.html?CSRF_Token="
+            . $token;
+}
+
 my %link_rel;
 my $genpage = sub {
     return $m->comp(
index 07bd2f4..4d7b1e3 100644 (file)
@@ -60,7 +60,7 @@
 
 % my @strong = qw(<strong> </strong>);
 
-<p><&|/l, @strong &>Search for tickets by entering [_1]id[_2] numbers, subject words [_1]"in quotes"[_2], [_1]queues[_2] by name, Owners by [_1]username[_2], Requestors by [_1]email address[_2], and ticket [_1]statuses[_2].</&></p>
+<p><&|/l_unsafe, @strong &>Search for tickets by entering [_1]id[_2] numbers, subject words [_1]"in quotes"[_2], [_1]queues[_2] by name, Owners by [_1]username[_2], Requestors by [_1]email address[_2], and ticket [_1]statuses[_2].</&></p>
 
 <p><&|/l&>Any word not recognized by RT is searched for in ticket subjects.</&></p>
 
@@ -74,7 +74,7 @@
 % }
 % }
 
-<p><&|/l, map { "<strong>$_</strong>" } qw(initial active inactive any) &>Entering [_1], [_2], [_3], or [_4] limits results to tickets with one of the respective types of statuses.  Any individual status name limits results to just the statuses named.</&>
+<p><&|/l_unsafe, map { "<strong>$_</strong>" } qw(initial active inactive any) &>Entering [_1], [_2], [_3], or [_4] limits results to tickets with one of the respective types of statuses.  Any individual status name limits results to just the statuses named.</&>
 
 % if (RT->Config->Get('OnlySearchActiveTicketsInSimpleSearch', $session{'CurrentUser'})) {
 % my $status_str  = join ', ', map { loc($_) } RT::Queue->ActiveStatusArray;
 % }
 </p>
 
-<p><&|/l, map { "<strong>$_</strong>" } 'queue:"Example Queue"', 'owner:email@example.com' &>Start the search term with the name of a supported field followed by a colon, as in [_1] and [_2], to explicitly specify the search type.</&></p>
+<p><&|/l_unsafe, map { "<strong>$_</strong>" } 'queue:"Example Queue"', 'owner:email@example.com' &>Start the search term with the name of a supported field followed by a colon, as in [_1] and [_2], to explicitly specify the search type.</&></p>
 
-<p><&|/l, '<strong>cf.Name:value</strong>' &>CFs may be searched using a similar syntax as above with [_1].</&></p>
+<p><&|/l_unsafe, '<strong>cf.Name:value</strong>' &>CFs may be searched using a similar syntax as above with [_1].</&></p>
 
 % my $link_start  = '<a href="' . RT->Config->Get('WebPath') . '/Search/Build.html">';
 % my $link_end    = '</a>';
-<p><&|/l, $link_start, $link_end &>For the full power of RT's searches, please visit the [_1]search builder interface[_2].</&></p>
+<p><&|/l_unsafe, $link_start, $link_end &>For the full power of RT's searches, please visit the [_1]search builder interface[_2].</&></p>
 
 </form>
 
index 549d458..76accfc 100755 (executable)
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<&| /Widgets/TitleBox, title =>  $title &>
+<&| /Widgets/TitleBox, title => $title &>
 <& /Elements/CollectionList, Title   => $title,
                         Format  => $Format, 
                         Query   => $Query, 
                         Order   => @Order, 
                         OrderBy => @OrderBy,
                         BaseURL => $BaseURL,
-                        GenericQueryArgs => $GenericQueryArgs,
-                        AllowSorting => $AllowSorting,
+                        AllowSorting => 1,
                         Class   => 'RT::Tickets',
              Rows    => $Rows,
                         Page    => $Page &>
 </&>
 
 <%INIT>
+my $title = loc("My [_1] tickets", $friendly_status);
 my $id = $session{'CurrentUser'}->id;
-my $Query = "( "
-    . join( ' OR ', map "$_.id = $id", @roles )
-    . ")";
+my $Query = "( Watcher.id = $id )";
 if ( @status ) {
-    $Query .= " AND ( "
-        . join( ' OR ', map "Status = '$_'", @status )
-        . " )";
+    @status = map {s/(['\\])/\\$1/g; "Status = '$_'"} @status;
+    $Query .= " AND ( " . join(' OR ', @status ) . " )";
 }
 my $Format = RT->Config->Get('DefaultSelfServiceSearchResultFormat');
-
 </%INIT>
 <%ARGS>
 $friendly_status => loc('open')
-$title => loc("My [_1] tickets", $friendly_status)
-@roles => ('Watcher')
-@status => RT::Queue->ActiveStatusArray()
+@status => ()
 $BaseURL => undef
 $Page => 1
-$GenericQueryArgs => undef
-$AllowSorting => 1
 @Order => ('ASC')
 @OrderBy => ('Created')
 $Rows => 50
index a89296b..29accf5 100755 (executable)
@@ -48,6 +48,8 @@
 <& /SelfService/Elements/Header, Title => loc('Open tickets') &>
 <& /SelfService/Elements/MyRequests,
     %ARGS,
+    status          => [ RT::Queue->ActiveStatusArray() ],
+    friendly_status => loc('open'),
     BaseURL => RT->Config->Get('WebPath') ."/SelfService/?",
     Page    => $Page, 
 &>
index 9fb2b2b..f29dfbb 100755 (executable)
       <& /Ticket/Elements/EditTransactionCustomFields, %ARGS, QueueObj => $QueueObj, InTable => 1 &>
     </table>
   </&>
-% $m->callback( CallbackName => 'AfterBasics', QueueObj => $QueueObj );
+% $m->callback( CallbackName => 'AfterBasics', QueueObj => $QueueObj, ARGSRef => \%ARGS );
 </div>
 
 <div id="ticket-create-message">
@@ -304,41 +304,30 @@ if ($CloneTicket) {
     my $members = $CloneTicketObj->Members;
     my ( @members, @members_of, @refers, @refers_by, @depends, @depends_by );
     my $refers = $CloneTicketObj->RefersTo;
+    my $get_link_value = sub {
+        my ($link, $type) = @_;
+        my $uri_method = $type . 'URI';
+        my $local_method = 'Local' . $type;
+        my $uri = $link->$uri_method;
+        return if $uri->IsLocal and
+                $uri->Object and
+                $uri->Object->isa('RT::Ticket') and
+                $uri->Object->Type eq 'reminder';
+
+        return $link->$local_method || $uri->URI;
+    };
     while ( my $refer = $refers->Next ) {
-        push @refers, $refer->LocalTarget;
+        my $refer_value = $get_link_value->($refer, 'Target');
+        push @refers, $refer_value if defined $refer_value;
     }
     $clone->{'new-RefersTo'} = join ' ', @refers;
 
     my $refers_by = $CloneTicketObj->ReferredToBy;
     while ( my $refer_by = $refers_by->Next ) {
-        push @refers_by, $refer_by->LocalBase;
+        my $refer_by_value = $get_link_value->($refer_by, 'Base');
+        push @refers_by, $refer_by_value if defined $refer_by_value;
     }
     $clone->{'RefersTo-new'} = join ' ', @refers_by;
-    if (0) {    # Temporarily disabled
-        my $depends = $CloneTicketObj->DependsOn;
-        while ( my $depend = $depends->Next ) {
-            push @depends, $depend->LocalTarget;
-        }
-        $clone->{'new-DependsOn'} = join ' ', @depends;
-
-        my $depends_by = $CloneTicketObj->DependedOnBy;
-        while ( my $depend_by = $depends_by->Next ) {
-            push @depends_by, $depend_by->LocalBase;
-        }
-        $clone->{'DependsOn-new'} = join ' ', @depends_by;
-
-        while ( my $member = $members->Next ) {
-            push @members, $member->LocalBase;
-        }
-        $clone->{'MemberOf-new'} = join ' ', @members;
-
-        my $members_of = $CloneTicketObj->MemberOf;
-        while ( my $member_of = $members_of->Next ) {
-            push @members_of, $member_of->LocalTarget;
-        }
-        $clone->{'new-MemberOf'} = join ' ', @members_of;
-
-    }
 
     my $cfs = $CloneTicketObj->QueueObj->TicketCustomFields();
     while ( my $cf = $cfs->Next ) {
index 6deb65c..0a29c97 100755 (executable)
@@ -55,7 +55,7 @@
 <& /Elements/ListActions, actions => \@Actions &>
 <& Elements/ShowUpdateStatus, Ticket => $TicketObj &>
 
-% $m->callback( %ARGS, Ticket => $TicketObj, CallbackName => 'BeforeShowSummary' );
+% $m->callback( %ARGS, Ticket => $TicketObj, Transactions => $transactions, Attachments => $attachments, CallbackName => 'BeforeShowSummary' );
 <div class="summary">
 <&| /Widgets/TitleBox, title => loc('Ticket metadata') &>
 <& /Ticket/Elements/ShowSummary,  Ticket => $TicketObj, Attachments => $attachments &>
@@ -63,7 +63,7 @@
 </div>
 <br />
 
-% $m->callback( Ticket => $TicketObj, %ARGS, CallbackName => 'BeforeShowHistory' );
+% $m->callback( Ticket => $TicketObj, %ARGS, Transactions => $transactions, Attachments => $attachments, CallbackName => 'BeforeShowHistory' );
 
 % if (not $ForceShowHistory and RT->Config->Get( 'DeferTransactionLoading', $session{'CurrentUser'} )) {
     <& /Ticket/Elements/ClickToShowHistory,
@@ -83,6 +83,8 @@
 
 % $m->callback( %ARGS,
 %     Ticket       => $TicketObj,
+%     Transactions => $transactions,
+%     Attachments  => $attachments,
 %     CallbackName => 'AfterShowHistory',
 % );
 
index 8393191..30c9a43 100644 (file)
@@ -83,7 +83,7 @@ $Toggle => 0
 </%ARGS>
 <span class="toggle-bookmark-<% $id %>">
 % my $url = RT->Config->Get('WebPath') ."/Helpers/Toggle/TicketBookmark?id=". $id;
-<a align="right" href="<% $url %>" onclick="jQuery('.toggle-bookmark-<% $id |n%>').load('<% $url |n %>'); return false;" >
+<a align="right" href="<% $url %>" onclick="jQuery('.toggle-bookmark-'+<% $id |n,j%>).load(<% $url |n,j %>); return false;" >
 % if ( $bookmarked ) {
 <img src="<% RT->Config->Get('WebPath') %>/NoAuth/images/star.gif" alt="<% loc('Remove Bookmark') %>" style="border-style: none" />
 % } else {
index 4461b9a..5a9a477 100644 (file)
@@ -47,7 +47,7 @@
 %# END BPS TAGGED BLOCK }}}
 <div id="deferred_ticket_history">
     <& /Widgets/TitleBoxStart, title => 'History' &>
-        <a href="<% $display %>" onclick="jQuery('#deferred_ticket_history').text('<% loc('Loading...') %>').load('<% $url |n %>'); return false;" ><% loc('Show ticket history') %></a>
+        <a href="<% $display %>" onclick="jQuery('#deferred_ticket_history').text(<% loc('Loading...') |n,j%>).load(<% $url |n,j %>); return false;" ><% loc('Show ticket history') %></a>
     <& /Widgets/TitleBoxEnd &>
 </div>
 <%ARGS>
index be6b264..4b0b4c4 100644 (file)
@@ -47,4 +47,4 @@
 %# END BPS TAGGED BLOCK }}}
 <span
     class="message-stanza-folder closed"
-    onclick="fold_message_stanza(this, '<%loc('Show quoted text')%>', '<%loc('Hide quoted text')%>');"><%loc('Show quoted text')%></span><br />\
+    onclick="fold_message_stanza(this, <%loc('Show quoted text') |n,j%>, <%loc('Hide quoted text') |n,j%>);"><%loc('Show quoted text')%></span><br />\
index 563b0f0..36d0d8e 100644 (file)
@@ -54,13 +54,14 @@ $Edit => 0
 <%init>
 
 $Ticket = LoadTicket($id) if ($id);
+my $resolve_status = $Ticket->QueueObj->Lifecycle->ReminderStatusOnResolve;
 
 my $count_reminders = RT::Reminders->new($session{'CurrentUser'});
 $count_reminders->Ticket($Ticket->id);
 my $count_tickets = $count_reminders->Collection;
 if (!$ShowCompleted) {
     # XXX: don't break encapsulation if we can avoid it
-    $count_tickets->FromSQL('Type = "reminder" AND RefersTo = "'.$Ticket->id.'" AND Status != "resolved"');
+    $count_tickets->FromSQL(q{Type = "reminder" AND RefersTo = "} .  $Ticket->id . qq{" AND Status != "$resolve_status" });
 }
 my $has_reminders = $count_tickets->Count;
 
@@ -85,7 +86,7 @@ my $reminder_collection = $count_reminders->Collection;
 % my $visible = 0;
 % while ( my $reminder = $reminder_collection->Next ) {
 % $i++;
-% if ( $reminder->Status eq 'resolved' && !$ShowCompleted ) {
+% if ( $reminder->Status eq $resolve_status && !$ShowCompleted ) {
 <tr class="hidden"><td><input type="hidden" class="hidden" name="Complete-Reminder-<% $reminder->id %>" value="1" /></td></tr>
 % $i++;
 % } elsif ($Edit) {
@@ -105,7 +106,7 @@ my $reminder_collection = $count_reminders->Collection;
 %# we must always include resolved reminders due to the browser
 %# checkbox-with-false-value issue
 % while ( my $reminder = $reminder_collection->Next ) {
-% if ( $reminder->Status eq 'resolved' && !$ShowCompleted ) {
+% if ( $reminder->Status eq $resolve_status && !$ShowCompleted ) {
 <input type="hidden" class="hidden" name="Complete-Reminder-<% $reminder->id %>" value="1" />
 % }
 % }
@@ -139,7 +140,7 @@ $Ticket
 $Index
 </%args>
 <tr class="<% $Index%2 ? 'oddline' : 'evenline' %>">
-<td class="entry"><input type="checkbox" value="1" name="Complete-Reminder-<% $Reminder->id %>" <% $Reminder->Status eq 'resolved' ? 'checked="checked"' : '' |n %> /></td>
+<td class="entry"><input type="checkbox" value="1" name="Complete-Reminder-<% $Reminder->id %>" <% $Reminder->Status eq $Reminder->QueueObj->Lifecycle->ReminderStatusOnResolve ? 'checked="checked"' : '' |n %> /></td>
 <td class="label"><&|/l&>Subject</&>:</td>
 <td class="entry" colspan="3"><input type="text" size="50" name="Reminder-Subject-<% $Reminder->id %>" value="<% $Reminder->Subject %>" /></td>
 </tr>
@@ -160,7 +161,7 @@ $Index
 % my $dueobj = $Reminder->DueObj;
 % my $overdue = $dueobj->Unix > 0 && $dueobj->Diff < 0 ? 1 : 0;
 <tr class="<% $Index%2 ? 'oddline' : 'evenline' %>">
-<td class="collection-as-table"><input type="checkbox" value="1" name="Complete-Reminder-<% $Reminder->id %>" <% $Reminder->Status eq 'resolved' ? 'checked="checked"' : '' |n %> /></td>
+<td class="collection-as-table"><input type="checkbox" value="1" name="Complete-Reminder-<% $Reminder->id %>" <% $Reminder->Status eq $Reminder->QueueObj->Lifecycle->ReminderStatusOnResolve ? 'checked="checked"' : '' |n %> /></td>
 <td class="collection-as-table"><% $Reminder->Subject %></td>
 <td class="collection-as-table"><% $overdue ? '<span class="overdue">' : '' |n %><% $dueobj->AgeAsString || loc('Not set') %><% $overdue ? '</span>' : '' |n %></td>
 <td class="collection-as-table"><& /Elements/ShowUser, User => $Reminder->OwnerObj &></td>
index b5e009c..909ea01 100755 (executable)
@@ -60,11 +60,12 @@ if ($ShowDisplayModes or $ShowTitle) {
     if ($ShowDisplayModes) {
         $titleright = '';
         
-        my $open_all  = $m->interp->apply_escapes( loc("Show all quoted text"), 'h' );
-        my $close_all = $m->interp->apply_escapes( loc("Hide all quoted text"), 'h' );
+        my $open_all  = $m->interp->apply_escapes( loc("Show all quoted text"), 'j' );
+        my $open_html = $m->interp->apply_escapes( loc("Show all quoted text"), 'h' );
+        my $close_all = $m->interp->apply_escapes( loc("Hide all quoted text"), 'j' );
         $titleright .=    '<a href="#" data-direction="open" '
-                        . qq{onclick='return toggle_all_folds(this, "$open_all", "$close_all");'}
-                        . ">$open_all</a> &mdash; ";
+                        . qq{onclick="return toggle_all_folds(this, $open_all, $close_all);"}
+                        . ">$open_html</a> &mdash; ";
 
         if ($ShowHeaders) {
             $titleright .= qq{<a href="$URIFile?id=} .
index 3e5f41f..8a8eef6 100755 (executable)
@@ -175,7 +175,9 @@ unless ( $DefaultTicketsTab eq 'None' ) {
 }
 
 my $TicketTemplate = "ShowRequestorTickets$DefaultTicketsTab";
-$TicketTemplate = "ShowRequestorTicketsActive" unless $m->comp_exists($TicketTemplate);
+$TicketTemplate = "ShowRequestorTicketsActive"
+    unless RT::Interface::Web->ComponentPathIsSafe($TicketTemplate)
+       and $m->comp_exists($TicketTemplate);
 </%INIT>
 <%ARGS>
 $Ticket=>undef
index 392ee86..d062156 100644 (file)
@@ -61,8 +61,7 @@
     class="onetime onetimecc"
     type="checkbox"
 % my $clean_addr = $txn_addresses{$addr}->format;
-% $clean_addr =~ s/'/\\'/g;
-    onClick="checkboxToInput('UpdateCc', 'UpdateCc-<%$addr%>','<%$clean_addr%>' );"
+    onClick="checkboxToInput('UpdateCc', <% "UpdateCc-$addr" |n,j%>, <%$clean_addr|n,j%> );"
     <% $ARGS{'UpdateCc-'.$addr} ? 'checked="checked"' : ''%> > <& /Elements/ShowUser, Address => $txn_addresses{$addr}&>
 %}
 </td></tr>
@@ -77,8 +76,7 @@
     class="onetime onetimebcc"
     type="checkbox"
 % my $clean_addr = $txn_addresses{$addr}->format;
-% $clean_addr =~ s/'/\\'/g;
-    onClick="checkboxToInput('UpdateBcc', 'UpdateBcc-<%$addr%>','<%$clean_addr%>' );"
+    onClick="checkboxToInput('UpdateBcc', <% "UpdateBcc-$addr" |n,j%>, <%$clean_addr|n,j%> );"
         <% $ARGS{'UpdateBcc-'.$addr} ? 'checked="checked"' : ''%>> 
 <& /Elements/ShowUser, Address => $txn_addresses{$addr}&>
 %}
index 6269907..d15ce72 100644 (file)
@@ -51,7 +51,7 @@
 % $m->callback( CallbackName => 'BeforeActionList', %ARGS, Actions => \@results, ARGSRef => \%ARGS );
 <& /Elements/ListActions, actions => \@results &>
 <form method="post" action="<% RT->Config->Get('WebPath') . $m->request_comp->path %>?id=<% $id %>">
-<a href="<% RT->Config->Get('WebURL') %>Ticket/Display.html?id=<% $txn->Ticket %>#txn-<% $id %>">
+<a href="<% RT->Config->Get('WebPath') %>/Ticket/Display.html?id=<% $txn->Ticket %>#txn-<% $id %>">
 <% loc('Return back to the ticket') %>
 </a>
 <& /Elements/Submit,
index e68aaed..c5479e3 100644 (file)
@@ -151,7 +151,7 @@ my $class = '';
 $class = 'class="hidden"' if $Level != 1 && !@Default;
 </%INIT>
 <% loc('Show Tickets Properties on [_1] level', $Level) %>
-(<small><a href="#" onclick="hideshow('<% $id %>'); return false;"><% loc('open/close') %></a></small>):
+(<small><a href="#" onclick="hideshow(<% $id |n,j%>); return false;"><% loc('open/close') %></a></small>):
 <table id="<% $id %>" <% $class |n %>>
 % while ( my ($group, $list) = (splice @Available, 0, 2) ) {
 <tr><td><% loc($group) %>:</td><td>
index 967a5e4..2163b81 100644 (file)
@@ -66,6 +66,7 @@ $ARGS{'id'} = $id = $ticket->id;
 require RT::Graph::Tickets;
 my $graph = RT::Graph::Tickets->TicketLinks(
     %ARGS,
+    Graph  => undef,
     Ticket => $ticket,
 );
 </%INIT>
index ba41445..89b1f37 100644 (file)
@@ -65,6 +65,7 @@ unless ( $ticket->id ) {
 require RT::Graph::Tickets;
 my $graph = RT::Graph::Tickets->TicketLinks(
     %ARGS,
+    Graph  => undef,
     Ticket => $ticket,
 );
 
index 28942e0..9dceb2a 100755 (executable)
@@ -51,7 +51,7 @@
 % $m->callback(CallbackName => 'BeforeActionList', Actions => \@results, ARGSRef => \%ARGS, Ticket => $Ticket);
 <& /Elements/ListActions, actions => \@results &>
 
-<form action="ModifyLinks.html" method="post">
+<form action="ModifyLinks.html" name="ModifyLinks" method="post">
 <input type="hidden" class="hidden" name="id" value="<%$Ticket->id%>" />
 % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS );
 % my (@extra);
index 238087f..69ac079 100644 (file)
@@ -56,7 +56,7 @@ my $z_index = 9999;
 
 <div id="<% $Name %>_Container" class="combobox <%$Class%>" style="z-index: <%$z_index--%>">
 <input name="<% $Name %>" id="<% $Name %>" class="combo-text" value="<% $Default || '' %>" type="text" <% $Size ? "size='$Size'" : '' |n %> autocomplete="off" />
-<br style="display: none" /><span id="<% $Name %>_Button" class="combo-button">&#9660;</span><select name="List-<% $Name %>" id="<% $Name %>_List" class="combo-list" onchange="ComboBox_SimpleAttach(this, this.form['<% $Name %>']); " size="<% $Rows %>">
+<br style="display: none" /><span id="<% $Name %>_Button" class="combo-button">&#9660;</span><select name="List-<% $Name %>" id="<% $Name %>_List" class="combo-list" onchange="ComboBox_SimpleAttach(this, this.form[<% $Name |n,j%>]); " size="<% $Rows %>">
 <option style="display: none" value="">-</option>
 % foreach my $value (@Values) {
         <option value="<%$value%>"><% $value%></option>
@@ -64,7 +64,7 @@ my $z_index = 9999;
 </select>
 </div>
 <script language="javascript"><!--
-ComboBox_InitWith('<% $Name %>');
+ComboBox_InitWith(<% $Name |n,j %>);
 //--></script>
 </nobr>
 <%ARGS>
index a031477..cbcc5c3 100755 (executable)
@@ -48,7 +48,7 @@
 <div class="titlebox<% $class ? " $class " : '' %><% $rolledup ? " rolled-up" : ""%>" id="<% $id %>">
   <div class="titlebox-title<% $title_class ? " $title_class" : ''%>">
 % if ($hideable) {
-    <span class="widget"><a href="#" onclick="return rollup('<%$tid%>');" title="Toggle visibility"></a></span>
+    <span class="widget"><a href="#" onclick="return rollup(<%$tid|n,j%>);" title="Toggle visibility"></a></span>
 % }
     <span class="left"><%
             $title_href ? qq[<a href="$title_href">] : '' | n
index c24b6cd..d6e0b79 100755 (executable)
@@ -126,7 +126,7 @@ if ( $ARGS{'QuickCreate'} ) {
 
 
 if ( $ARGS{'q'} ) {
-    RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."Search/Simple.html?q=".$m->interp->apply_escapes($ARGS{q}));
+    RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."Search/Simple.html?q=".$m->interp->apply_escapes($ARGS{q}, 'u'));
 }
 
 </%init>
index 6396bc6..9f1b343 100755 (executable)
@@ -47,6 +47,6 @@
 %# END BPS TAGGED BLOCK }}}
 <%init>
  my $hand = ($session{'CurrentUser'} ||= RT::CurrentUser->new)->LanguageHandle;
- $m->print($hand->maketext($m->content,@_));
+ $m->print($hand->maketext($m->content,map { $m->interp->apply_escapes($_, 'h') } @_));
  return(1);
 </%init>
index f3e0837..e0c023c 100644 (file)
@@ -1,11 +1 @@
 <& /elements/footer.html &>
-% if ( 0 ) {
-  <div id="bpscredits">
-    <& /Elements/Logo, ShowName => 0 &>
-    <div id="copyright">
-<&|/l,     '', '', '2010', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>', &>[_1] RT [_2] Copyright 1996-[_3] [_4].</&>
-</div>
-</div>
-</body>
-</html>
-% }
index 7c23194..052a1a5 100644 (file)
@@ -6,6 +6,7 @@ $CloneTicket => undef
 $m->callback( CallbackName => "Init", ARGSRef => \%ARGS );
 my $Queue = $ARGS{Queue};
 
+my $escape = sub { $m->interp->apply_escapes(shift, 'h') };
 
 my $showrows = sub {
     my @pairs = @_;
@@ -45,41 +46,30 @@ if ($CloneTicket) {
     my $members = $CloneTicketObj->Members;
     my ( @members, @members_of, @refers, @refers_by, @depends, @depends_by );
     my $refers = $CloneTicketObj->RefersTo;
+    my $get_link_value = sub {
+        my ($link, $type) = @_;
+        my $uri_method = $type . 'URI';
+        my $local_method = 'Local' . $type;
+        my $uri = $link->$uri_method;
+        return if $uri->IsLocal and
+                $uri->Object and
+                $uri->Object->isa('RT::Ticket') and
+                $uri->Object->Type eq 'reminder';
+
+        return $link->$local_method || $uri->URI;
+    };
     while ( my $refer = $refers->Next ) {
-        push @refers, $refer->LocalTarget;
+        my $refer_value = $get_link_value->($refer, 'Target');
+        push @refers, $refer_value if defined $refer_value;
     }
     $clone->{'new-RefersTo'} = join ' ', @refers;
 
     my $refers_by = $CloneTicketObj->ReferredToBy;
     while ( my $refer_by = $refers_by->Next ) {
-        push @refers_by, $refer_by->LocalBase;
+        my $refer_by_value = $get_link_value->($refer_by, 'Base');
+        push @refers_by, $refer_by_value if defined $refer_by_value;
     }
     $clone->{'RefersTo-new'} = join ' ', @refers_by;
-    if (0) {    # Temporarily disabled
-        my $depends = $CloneTicketObj->DependsOn;
-        while ( my $depend = $depends->Next ) {
-            push @depends, $depend->LocalTarget;
-        }
-        $clone->{'new-DependsOn'} = join ' ', @depends;
-
-        my $depends_by = $CloneTicketObj->DependedOnBy;
-        while ( my $depend_by = $depends_by->Next ) {
-            push @depends_by, $depend_by->LocalBase;
-        }
-        $clone->{'DependsOn-new'} = join ' ', @depends_by;
-
-        while ( my $member = $members->Next ) {
-            push @members, $member->LocalBase;
-        }
-        $clone->{'MemberOf-new'} = join ' ', @members;
-
-        my $members_of = $CloneTicketObj->MemberOf;
-        while ( my $member_of = $members_of->Next ) {
-            push @members_of, $member_of->LocalTarget;
-        }
-        $clone->{'new-MemberOf'} = join ' ', @members_of;
-
-    }
 
     my $cfs = $CloneTicketObj->QueueObj->TicketCustomFields();
     while ( my $cf = $cfs->Next ) {
@@ -218,7 +208,7 @@ if ((!exists $ARGS{'AddMoreAttach'}) and (defined($ARGS{'id'}) and $ARGS{'id'} e
 
 <%perl>
 $showrows->(
-    loc("Subject") => '<input name="Subject" size="30" maxsize="200" value="'.($ARGS{Subject} || '').'" />');
+    loc("Subject") => '<input name="Subject" size="30" maxsize="200" value="'.$escape->($ARGS{Subject} || '').'" />');
 </%perl>
     <span class="content-label label"><%loc("Describe the issue below")%></span>
         <& /Elements/MessageBox, exists $ARGS{Content}  ? (Default => $ARGS{Content}, IncludeSignature => 0 ) : ( QuoteTransaction => $QuoteTransaction ), Height => 5  &>
@@ -382,12 +372,12 @@ $showrows->(
 
 <%perl>
 $showrows->(
-    loc("Depends on")     => '<input size="10" name="new-DependsOn" value="' . ($ARGS{'new-DependsOn'} || '' ). '" />',
-    loc("Depended on by") => '<input size="10" name="DependsOn-new" value="' . ($ARGS{'DependsOn-new'} || '' ) . '" />',
-    loc("Parents")        => '<input size="10" name="new-MemberOf" value="' . ($ARGS{'new-MemberOf'} || '') . '" />',
-    loc("Children")       => '<input size="10" name="MemberOf-new" value="' . ($ARGS{'MemberOf-new'} || '') . '" />',
-    loc("Refers to")      => '<input size="10" name="new-RefersTo" value="' . ($ARGS{'new-RefersTo'} || '') . '" />',
-    loc("Referred to by") => '<input size="10" name="RefersTo-new" value="' . ($ARGS{'RefersTo-new'} || ''). '" />'
+    loc("Depends on")     => '<input type="text" size="10" name="new-DependsOn" value="' . $escape->($ARGS{'new-DependsOn'} || '' ). '" />',
+    loc("Depended on by") => '<input type="text" size="10" name="DependsOn-new" value="' . $escape->($ARGS{'DependsOn-new'} || '' ) . '" />',
+    loc("Parents")        => '<input type="text" size="10" name="new-MemberOf" value="' . $escape->($ARGS{'new-MemberOf'} || '') . '" />',
+    loc("Children")       => '<input type="text" size="10" name="MemberOf-new" value="' . $escape->($ARGS{'MemberOf-new'} || '') . '" />',
+    loc("Refers to")      => '<input type="text" size="10" name="new-RefersTo" value="' . $escape->($ARGS{'new-RefersTo'} || '') . '" />',
+    loc("Referred to by") => '<input type="text" size="10" name="RefersTo-new" value="' . $escape->($ARGS{'RefersTo-new'} || ''). '" />'
 );
 </%perl>
 
index e979da3..4190bd3 100644 (file)
@@ -145,18 +145,18 @@ my $print_value = sub {
     }
     $m->out('</a>') if defined $linked && length $linked;
 
-    # This section automatically populates a<div with the "IncludeContentForValue" for this custom
+    # This section automatically populates a div with the "IncludeContentForValue" for this custom
     # field if it's been defined
     if ( $cf->IncludeContentForValue ) {
        my $vid = $value->id;
        $m->out(   '<div class="object_cf_value_include" id="object_cf_value_'. $vid .'">' );
        $m->print( loc("See also:") );
-       $m->out(   '<a href="'. $value->IncludeContentForValue .'">' );
-       $m->print( $value->IncludeContentForValue );
+       $m->out(   '<a href="'. $m->interp->apply_escapes($value->IncludeContentForValue, 'h') .'">' );
+       $m->out( $m->interp->apply_escapes($value->IncludeContentForValue, 'h') );
        $m->out(   qq{</a></div>\n} );
-       $m->out(   qq{<script><!--\nahah('} );
-       $m->print( $value->IncludeContentForValue );
-       $m->out(   qq{', 'object_cf_value_$vid');\n--></script>\n} );
+       $m->out(   qq{<script><!--\njQuery('#object_cf_value_$vid').load(} );
+       $m->out(   $m->interp->apply_escapes($value->IncludeContentForValue, 'j') );
+       $m->out(   qq{);\n--></script>\n} );
     }
 };
 
index 16864b4..e688ea8 100644 (file)
@@ -31,7 +31,7 @@ my $search;
                 if ( $custom->Description eq $name ) { $search = $custom; last }
             }
             unless ( $search && $search->id ) {
-                $m->out("Predefined search $name not found");
+                $m->out(loc("Predefined search [_1] not found", $m->interp->apply_escapes($name, 'h')));
                 return;
             }
         }
index 9756e51..6fcaa49 100644 (file)
@@ -4,7 +4,7 @@ use Test::MockTime qw(set_fixed_time restore_time);
 use DateTime;
 
 use warnings; use strict;
-use RT::Test tests => 172;
+use RT::Test tests => 173;
 use RT::User;
 use Test::Warn;
 
@@ -85,9 +85,11 @@ my $current_user;
     my $date = RT::Date->new(RT->SystemUser);
     is($date->Unix, 0, "new date returns 0 in Unix format");
     is($date->Get, '1970-01-01 00:00:00', "default is ISO format");
-    is($date->Get(Format =>'SomeBadFormat'),
-       '1970-01-01 00:00:00',
-       "don't know format, return ISO format");
+    warning_like {
+        is($date->Get(Format =>'SomeBadFormat'),
+           '1970-01-01 00:00:00',
+           "don't know format, return ISO format");
+    } qr/Invalid date formatter/;
     is($date->Get(Format =>'W3CDTF'),
        '1970-01-01T00:00:00Z',
        "W3CDTF format with defaults");
index cabb00e..50d08f7 100644 (file)
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 use RT;
-use RT::Test tests => 16;
+use RT::Test tests => 18;
 
 
 {
@@ -101,3 +101,16 @@ ok( $unlimittickets->Count > 0, "UnLimited tickets object should return tickets"
 
 }
 
+
+{
+    my $tickets = RT::Tickets->new( RT->SystemUser );
+    $tickets->Limit( FIELD => 'id', OPERATOR => '>', VALUE => 0 );
+    my $count = $tickets->Count();
+    ok $count > 1, "found more than one ticket";
+    undef $count;
+
+    $tickets->Limit( FIELD => 'id', OPERATOR => '=', VALUE => 1, ENTRYAGGREGATOR => 'none' );
+    $count = $tickets->Count();
+    ok $count == 1, "found one ticket";
+}
+
index 40e2391..5825a10 100644 (file)
@@ -177,7 +177,7 @@ diag "deleted -> X via modify, only open is available";
     my @form_values = $input->possible_values;
     ok scalar @form_values, 'some options in the UI';
 
-    is join('-', @form_values), '-open', 'only open and default available';
+    is join('-', @form_values), '-deleted-open', 'only default, current and open available';
 }
 
 diag "check illegal values and transitions";
index b02f979..7515e2c 100644 (file)
@@ -1,7 +1,7 @@
 #!/usr/bin/perl
 use strict;
 use warnings;
-use RT::Test nodb => 1, tests => 8;
+use RT::Test nodb => 1, tests => 9;
 
 use_ok('RT::I18N');
 
@@ -59,11 +59,29 @@ diag q{newline and encoded file name};
 diag q{rfc2231};
 {
     my $str =
-"filename*=ISO-8859-1''%74%E9%73%74%2E%74%78%74 filename*=ISO-8859-1''%74%E9%73%74%2E%74%78%74";
+"attachment; filename*=ISO-8859-1''%74%E9%73%74%2E%74%78%74";
     is(
-        RT::I18N::DecodeMIMEWordsToEncoding( $str, 'utf-8' ),
-        'filename=tést.txt filename=tést.txt',
-        'right decodig'
+        RT::I18N::DecodeMIMEWordsToEncoding( $str, 'utf-8', 'Content-Disposition' ),
+        'attachment; filename="tést.txt"',
+        'right decoding'
+    );
+}
+
+diag q{rfc2231 param continuations};
+{
+    # XXX TODO: test various forms of the continuation stuff
+    #       quotes around the values
+    my $hdr = <<'.';
+inline;
+ filename*0*=ISO-2022-JP'ja'%1b$B%3f7$7$$%25F%25%2d%259%25H%1b%28B;
+ filename*1*=%20;
+ filename*2*=%1b$B%25I%25%2d%25e%25a%25s%25H%1b%28B;
+ filename*3=.txt
+.
+    is(
+        RT::I18N::DecodeMIMEWordsToEncoding( $hdr, 'utf-8', 'Content-Disposition' ),
+        'inline; filename="新しいテキスト ドキュメント.txt"',
+        'decoded continuations as one string'
     );
 }
 
index 276b761..f984bf3 100644 (file)
@@ -75,7 +75,7 @@ my $cf;
 
 # test custom field values auto completer
 {
-    $m->get_ok('/Helpers/Autocomplete/CustomFieldValues?term=eNo&Object---CustomField-'. $cf->id .'-Value');
+    $m->get_ok('/Helpers/Autocomplete/CustomFieldValues?term=eNo&Object---CustomField-'. $cf->id .'-Value&ContextId=1&ContextType=RT::Queue');
     require JSON;
     is_deeply(
         JSON::from_json( $m->content ),
index 0abbfac..5a64d46 100644 (file)
@@ -5,7 +5,7 @@ use HTTP::Request::Common;
 use HTTP::Cookies;
 use LWP;
 use Encode;
-use RT::Test tests => 56;
+use RT::Test tests => 70;
 
 my $cookie_jar = HTTP::Cookies->new;
 my ($baseurl, $agent) = RT::Test->started_ok;
@@ -295,3 +295,34 @@ diag "click advanced, enter a valid SQL, but the field is lower cased";
     );
 }
 
+diag "make sure skipped order by field doesn't break search";
+{
+    my $t = RT::Test->create_ticket( Queue => 'General', Subject => 'test' );
+    ok $t && $t->id, 'created a ticket';
+
+    $agent->get_ok($url."Search/Edit.html");
+    ok($agent->form_name('BuildQueryAdvanced'), "found the form");
+    $agent->field("Query", "id = ". $t->id);
+    $agent->submit;
+
+    $agent->follow_link_ok({id => 'page-results'});
+    ok( $agent->find_link(
+        text      => $t->id,
+        url_regex => qr{/Ticket/Display\.html},
+    ), "link to the ticket" );
+
+    $agent->follow_link_ok({id => 'page-edit_search'});
+    $agent->form_name('BuildQuery');
+    $agent->field("OrderBy", 'Requestor.EmailAddress', 3);
+    $agent->submit;
+    $agent->form_name('BuildQuery');
+    is $agent->value('OrderBy', 1), 'id';
+    is $agent->value('OrderBy', 2), '';
+    is $agent->value('OrderBy', 3), 'Requestor.EmailAddress';
+
+    $agent->follow_link_ok({id => 'page-results'});
+    ok( $agent->find_link(
+        text      => $t->id,
+        url_regex => qr{/Ticket/Display\.html},
+    ), "link to the ticket" );
+}
index d429d30..835b24c 100644 (file)
@@ -196,16 +196,17 @@ for my $path (qw(Prefs/Other.html /Prefs/Other.html)) {
 
 # test REST login response
 {
+    $agent = RT::Test::Web->new;
     my $requested = $url."REST/1.0/?user=root;pass=password";
     $agent->get($requested);
     is($agent->status, 200, "Loaded a page");
     is($agent->uri, $requested, "didn't redirect to /NoAuth/Login.html for REST");
-    $agent->get_ok($url);
-    $agent->logout();
+    $agent->get_ok($url."REST/1.0");
 }
 
 # test REST login response for wrong pass
 {
+    $agent = RT::Test::Web->new;
     my $requested = $url."REST/1.0/?user=root;pass=passwrong";
     $agent->get_ok($requested);
     is($agent->status, 200, "Loaded a page");
@@ -217,6 +218,7 @@ for my $path (qw(Prefs/Other.html /Prefs/Other.html)) {
 
 # test REST login response for no creds
 {
+    $agent = RT::Test::Web->new;
     my $requested = $url."REST/1.0/";
     $agent->get_ok($requested);
     is($agent->status, 200, "Loaded a page");
index 5e7194c..e38f201 100644 (file)
@@ -1,7 +1,9 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use RT::Test tests => 18;
+use RT::Interface::REST;
+
+use RT::Test tests => 22;
 
 my ($baseurl, $m) = RT::Test->started_ok;
 
@@ -69,3 +71,82 @@ for ("id: ticket/1",
         $m->content_contains($_);
 }
 
+# Create ticket 2 for testing ticket links
+for (2 .. 3) {
+    $m->post("$baseurl/REST/1.0/ticket/edit", [
+        user    => 'root',
+        pass    => 'password',
+        content => $text,
+    ], Content_Type => 'form-data');
+
+    $m->post(
+        "$baseurl/REST/1.0/ticket/1/links",
+        [
+            user    => 'root',
+            pass    => 'password',
+        ],
+        Content_Type => 'form-data',
+    );
+
+    my $link_data = form_parse($m->content);
+
+    push @{$link_data->[0]->[1]}, 'DependsOn';
+    vpush($link_data->[0]->[2], 'DependsOn', $_);
+
+    $m->post(
+        "$baseurl/REST/1.0/ticket/1/links",
+        [
+            user    => 'root',
+            pass    => 'password',
+            content => form_compose($link_data),
+        ],
+        Content_Type => 'form-data',
+    );
+
+}
+
+# See what links get reported for ticket 1.
+$m->post(
+    "$baseurl/REST/1.0/ticket/1/links/show",
+    [
+        user    => 'root',
+        pass    => 'password',
+    ],
+    Content_Type => 'form-data',
+);
+
+# Verify that the link was added correctly.
+my $content = form_parse($m->content);
+my $depends_on = vsplit($content->[0]->[2]->{DependsOn});
+@$depends_on = sort @$depends_on;
+like(
+    $depends_on->[0], qr{/ticket/2$},
+    "Check ticket link.",
+) or diag("'content' obtained:\n", $m->content);
+
+like(
+    $depends_on->[1], qr{/ticket/3$},
+    "Check ticket link.",
+) or diag("'content' obtained:\n", $m->content);
+
+$m->post(
+    "$baseurl/REST/1.0/ticket/2/links/show",
+    [
+        user    => 'root',
+        pass    => 'password',
+    ],
+    Content_Type => 'form-data',
+);
+my ($link) = $m->content =~ m|DependedOnBy:.*ticket/(\d+)|;
+is($link, 1, "Check ticket link.") or diag("'content' obtained:\n", $m->content);
+
+$m->post(
+    "$baseurl/REST/1.0/ticket/3/links/show",
+    [
+        user    => 'root',
+        pass    => 'password',
+    ],
+    Content_Type => 'form-data',
+);
+($link) = $m->content =~ m|DependedOnBy:.*ticket/(\d+)|;
+is($link, 1, "Check ticket link.") or diag("'content' obtained:\n", $m->content);
index 6483a75..612c6e2 100644 (file)
@@ -30,13 +30,13 @@ use Test::LongString;
 
 {
     my $html = q[<span lang=EN-US style='font-family:"Century Gothic","sans-serif";'>oh hai I'm some text</span>];
-    my $expected = q[<span style="font-family:&quot;Century Gothic&quot;,&quot;sans-serif&quot;;">oh hai I'm some text</span>];
+    my $expected = q[<span lang="EN-US" style="font-family:&quot;Century Gothic&quot;,&quot;sans-serif&quot;;">oh hai I'm some text</span>];
     is_string(scrub_html($html), $expected, "font lists");
 }
 
 {
     my $html = q[<span lang=EN-US style='font-size:7.5pt;font-family:"Century Gothic","sans-serif";color:#666666;mso-fareast-language:IT'>oh hai I'm some text</span>];
-    my $expected = q[<span style="font-size:7.5pt;font-family:&quot;Century Gothic&quot;,&quot;sans-serif&quot;;color:#666666;mso-fareast-language:IT">oh hai I'm some text</span>];
+    my $expected = q[<span lang="EN-US" style="font-size:7.5pt;font-family:&quot;Century Gothic&quot;,&quot;sans-serif&quot;;color:#666666;mso-fareast-language:IT">oh hai I'm some text</span>];
     is_string(scrub_html($html), $expected, "outlook html");
 }
 
index be06ad9..1d74673 100644 (file)
@@ -227,6 +227,41 @@ diag "Forward Transaction with attachments but no 'content' part" if $ENV{TEST_V
         like( $mail, qr/image\/png/,                       'att image content type' );
     }
 }
+RT::Test->clean_caught_mails;
+
+diag "Forward Ticket Template with a Subject: line" if $ENV{TEST_VERBOSE};
+{
+
+    require RT::Template;
+    my $template = RT::Template->new($RT::SystemUser);
+    $template->Load('Forward Ticket');
+
+    # prepend a Subject: line
+    $template->SetContent("Subject: OVERRIDING SUBJECT\n\n" . $template->Content);
+
+    my $ticket = RT::Test->create_ticket(
+        Subject => 'test ticket',
+        Queue   => 1,
+    );
+
+    $m->goto_ticket($ticket->Id);
+
+    $m->follow_link_ok(
+        { id => 'page-actions-forward' },
+        'follow 1st Forward to forward ticket'
+    );
+
+    $m->submit_form(
+        form_name => 'ForwardMessage',
+        fields    => {
+            To => 'rt-to@example.com',
+        },
+        button => 'ForwardAndReturn'
+    );
+
+    my ($mail) = RT::Test->fetch_caught_mails;
+    like($mail, qr/Subject: OVERRIDING SUBJECT/);
+}
 
 undef $m;
 done_testing;
index cb30e92..efb6151 100644 (file)
@@ -1,6 +1,6 @@
 use strict;
 use warnings;
-use RT::Test tests => 106;
+use RT::Test tests => 146;
 
 my ( $baseurl, $m ) = RT::Test->started_ok;
 ok( $m->login, "Logged in" );
@@ -24,6 +24,16 @@ is( $deleted->Status, 'deleted', "deleted $deleted_id" );
 $inactive->SetStatus('resolved');
 is( $inactive->Status, 'resolved', 'resolved $inactive_id' );
 
+# Create an article for linking
+require RT::Class;
+my $class = RT::Class->new($RT::SystemUser);
+$class->Create(Name => 'test class');
+
+require RT::Article;
+my $article = RT::Article->new($RT::SystemUser);
+
+$article->Create(Class => $class->Id, Name => 'test article');
+
 for my $type ( "DependsOn", "MemberOf", "RefersTo" ) {
     for my $c (qw/base target/) {
         my $id;
@@ -105,6 +115,59 @@ for my $type ( "DependsOn", "MemberOf", "RefersTo" ) {
         );
         $m->content_unlike( qr{$deleted_id.*?\[deleted\]}, "no deleted ticket",
         );
+
+        diag "[$type]: Testing that reminders don't get copied for $c tickets";
+        {
+            my $ticket = RT::Test->create_ticket(
+                Subject => 'test ticket',
+                Queue   => 1,
+            );
+
+            $m->goto_ticket($ticket->Id);
+            $m->form_name('UpdateReminders');
+            $m->field('NewReminder-Subject' => 'hello test reminder subject');
+            $m->click_button(value => 'Save');
+            $m->text_contains('hello test reminder subject');
+
+            my $id = $ticket->Id;
+            my $type_value = my $link_field = $type;
+            if ($c eq 'base') {
+                $type_value = "new-$type_value";
+                $link_field    = "$link_field-$id";
+            }
+            else {
+                $type_value = "$type_value-new";
+                $link_field = "$id-$link_field";
+            }
+
+            if ($type eq 'RefersTo') {
+                $m->goto_ticket($ticket->Id);
+                $m->follow_link(id => 'page-links');
+
+                # add $baseurl as a link
+                $m->form_name('ModifyLinks');
+                $m->field($link_field => "$baseurl/test_ticket_reference");
+                $m->click('SubmitTicket');
+
+                # add an article as a link
+                $m->form_name('ModifyLinks');
+                $m->field($link_field => 'a:' . $article->Id);
+                $m->click('SubmitTicket');
+            }
+
+            my $depends_on_url = sprintf(
+                '%s/Ticket/Create.html?Queue=%s&CloneTicket=%s&%s=%s',
+                $baseurl, '1', $id, $type_value, $id,
+            );
+            $m->get_ok($depends_on_url);
+            $m->form_name('TicketCreate');
+            $m->click_button(value => 'Create');
+            $m->content_lacks('hello test reminder subject');
+            if ($type eq 'RefersTo') {
+                $m->text_contains("$baseurl/test_ticket_reference");
+                $m->text_contains("Article " . $article->Id . ': test article');
+            }
+        }
     }
 }