summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2012-06-07 16:55:45 -0700
committerIvan Kohler <ivan@freeside.biz>2012-06-07 16:55:45 -0700
commitc24d6e2242ae0e026684b8f95decf156aba6e75e (patch)
tree8597d00e2e6bf2cf400437b9344f43b1500da412
parent6686c29104e555ea23446fe1db330664fa110bc0 (diff)
rt 4.0.6
-rw-r--r--FS/FS/Mason.pm2
-rw-r--r--rt/Makefile.in2
-rwxr-xr-xrt/bin/rt7
-rw-r--r--rt/bin/rt-mailgate.in20
-rw-r--r--rt/bin/rt.in7
-rwxr-xr-xrt/configure149
-rw-r--r--rt/docs/UPGRADING-4.023
-rw-r--r--rt/docs/hacking.pod10
-rw-r--r--rt/docs/security.pod15
-rw-r--r--rt/docs/web_deployment.pod13
-rw-r--r--rt/etc/RT_Config.pm.in72
-rw-r--r--rt/etc/schema.mysql2
-rwxr-xr-xrt/etc/upgrade/vulnerable-passwords.in3
-rwxr-xr-xrt/lib/RT/ACL.pm3
-rw-r--r--rt/lib/RT/Action/CreateTickets.pm13
-rwxr-xr-xrt/lib/RT/Action/SendEmail.pm9
-rw-r--r--rt/lib/RT/Article.pm11
-rwxr-xr-xrt/lib/RT/Attachments.pm11
-rw-r--r--rt/lib/RT/Class.pm1
-rw-r--r--rt/lib/RT/Config.pm1
-rw-r--r--rt/lib/RT/CustomField.pm80
-rw-r--r--rt/lib/RT/Dashboard/Mailer.pm5
-rw-r--r--rt/lib/RT/Date.pm30
-rw-r--r--rt/lib/RT/Generated.pm2
-rw-r--r--rt/lib/RT/Graph/Tickets.pm10
-rwxr-xr-xrt/lib/RT/Group.pm10
-rwxr-xr-xrt/lib/RT/Groups.pm8
-rw-r--r--rt/lib/RT/Handle.pm20
-rw-r--r--rt/lib/RT/I18N.pm51
-rw-r--r--rt/lib/RT/Installer.pm2
-rwxr-xr-xrt/lib/RT/Interface/Email.pm44
-rw-r--r--rt/lib/RT/Interface/Web.pm364
-rw-r--r--rt/lib/RT/Interface/Web/Handler.pm12
-rwxr-xr-xrt/lib/RT/Interface/Web/QueryBuilder/Tree.pm14
-rw-r--r--rt/lib/RT/Lifecycle.pm22
-rw-r--r--rt/lib/RT/Links.pm14
-rw-r--r--rt/lib/RT/ObjectCustomField.pm12
-rw-r--r--rt/lib/RT/ObjectCustomFieldValue.pm8
-rwxr-xr-xrt/lib/RT/Queue.pm12
-rw-r--r--rt/lib/RT/Reminders.pm6
-rw-r--r--rt/lib/RT/Report/Tickets.pm8
-rw-r--r--rt/lib/RT/Report/Tickets/Entry.pm4
-rwxr-xr-xrt/lib/RT/Scrip.pm24
-rw-r--r--rt/lib/RT/SearchBuilder.pm19
-rw-r--r--rt/lib/RT/Shredder.pm2
-rw-r--r--rt/lib/RT/Shredder/Plugin.pm1
-rw-r--r--rt/lib/RT/Shredder/Queue.pm1
-rwxr-xr-xrt/lib/RT/Template.pm24
-rw-r--r--rt/lib/RT/Test.pm52
-rw-r--r--rt/lib/RT/Test/Web.pm15
-rwxr-xr-xrt/lib/RT/Ticket.pm16
-rwxr-xr-xrt/lib/RT/Tickets.pm30
-rwxr-xr-xrt/lib/RT/Transaction.pm18
-rw-r--r--rt/lib/RT/URI.pm2
-rw-r--r--rt/lib/RT/URI/fsck_com_article.pm2
-rwxr-xr-xrt/lib/RT/User.pm74
-rwxr-xr-xrt/lib/RT/Users.pm8
-rw-r--r--rt/sbin/rt-server.fcgi.in1
-rw-r--r--rt/sbin/rt-server.in1
-rwxr-xr-xrt/sbin/rt-shredder.in2
-rw-r--r--rt/sbin/rt-test-dependencies.in7
-rwxr-xr-xrt/sbin/standalone_httpd1
-rw-r--r--rt/sbin/standalone_httpd.in1
-rw-r--r--rt/share/html/Admin/Articles/Elements/Topics2
-rw-r--r--rt/share/html/Admin/CustomFields/Modify.html4
-rwxr-xr-xrt/share/html/Admin/Elements/EditCustomFields3
-rw-r--r--rt/share/html/Admin/Elements/EditRights6
-rw-r--r--rt/share/html/Admin/Elements/Portal2
-rwxr-xr-xrt/share/html/Admin/Elements/SelectNewGroupMembers8
-rwxr-xr-xrt/share/html/Admin/Groups/index.html2
-rw-r--r--rt/share/html/Admin/Tools/Queries.html4
-rw-r--r--rt/share/html/Admin/Tools/Shredder/Dumps/dhandler5
-rw-r--r--rt/share/html/Admin/Tools/Shredder/Elements/Error/NoStorage2
-rw-r--r--rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Attachment2
-rw-r--r--rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Ticket2
-rw-r--r--rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--User2
-rwxr-xr-xrt/share/html/Admin/Users/Modify.html8
-rwxr-xr-xrt/share/html/Admin/Users/index.html2
-rwxr-xr-xrt/share/html/Approvals/Elements/PendingMyApproval4
-rw-r--r--rt/share/html/Articles/Article/Edit.html1
-rw-r--r--rt/share/html/Articles/Article/Elements/EditTopics55
-rw-r--r--rt/share/html/Articles/Article/ExtractIntoClass.html2
-rw-r--r--rt/share/html/Articles/Topics.html249
-rw-r--r--rt/share/html/Elements/CollectionAsTable/Header4
-rw-r--r--rt/share/html/Elements/CollectionList2
-rw-r--r--rt/share/html/Elements/CollectionListPaging12
-rw-r--r--rt/share/html/Elements/ColumnMap10
-rwxr-xr-xrt/share/html/Elements/CreateTicket2
-rw-r--r--rt/share/html/Elements/EditCustomField2
-rw-r--r--rt/share/html/Elements/EditCustomFieldAutocomplete13
-rw-r--r--rt/share/html/Elements/EditCustomFieldSelect6
-rwxr-xr-xrt/share/html/Elements/Error2
-rwxr-xr-xrt/share/html/Elements/Header3
-rw-r--r--rt/share/html/Elements/HeaderJavascript4
-rwxr-xr-xrt/share/html/Elements/MessageBox15
-rw-r--r--rt/share/html/Elements/RT__CustomField/ColumnMap8
-rw-r--r--rt/share/html/Elements/RT__Dashboard/ColumnMap2
-rw-r--r--rt/share/html/Elements/RT__Queue/ColumnMap6
-rwxr-xr-xrt/share/html/Elements/SelectOwner10
-rw-r--r--rt/share/html/Elements/SelectOwnerAutocomplete4
-rwxr-xr-xrt/share/html/Elements/SelectStatus14
-rw-r--r--rt/share/html/Elements/ShowCustomFields10
-rw-r--r--rt/share/html/Elements/ShowLink11
-rw-r--r--rt/share/html/Elements/ShowSearch8
-rw-r--r--rt/share/html/Elements/ShowUser2
-rwxr-xr-xrt/share/html/Elements/Submit14
-rwxr-xr-xrt/share/html/Elements/Tabs3
-rw-r--r--rt/share/html/Helpers/Autocomplete/CustomFieldValues44
-rw-r--r--rt/share/html/Helpers/Toggle/ShowRequestor4
-rw-r--r--rt/share/html/Install/DatabaseType.html2
-rw-r--r--rt/share/html/Install/Finish.html2
-rw-r--r--rt/share/html/Install/Initialize.html2
-rw-r--r--rt/share/html/Install/index.html2
-rwxr-xr-xrt/share/html/NoAuth/Logout.html2
-rw-r--r--rt/share/html/NoAuth/css/aileron/InHeader3
-rw-r--r--rt/share/html/NoAuth/css/aileron/boxes.css4
-rw-r--r--rt/share/html/NoAuth/css/aileron/msie-pie.css58
-rw-r--r--rt/share/html/NoAuth/css/images/PIE.htc77
-rw-r--r--rt/share/html/NoAuth/css/web2/InHeader3
-rw-r--r--rt/share/html/NoAuth/css/web2/msie-pie.css60
-rw-r--r--rt/share/html/NoAuth/js/titlebox-state.js2
-rw-r--r--rt/share/html/NoAuth/js/userautocomplete.js2
-rw-r--r--rt/share/html/NoAuth/js/util.js4
-rw-r--r--rt/share/html/Prefs/Search.html2
-rwxr-xr-xrt/share/html/REST/1.0/Forms/ticket/default24
-rwxr-xr-xrt/share/html/REST/1.0/Forms/ticket/links3
-rw-r--r--rt/share/html/REST/1.0/Forms/transaction/default3
-rwxr-xr-xrt/share/html/REST/1.0/ticket/link5
-rw-r--r--rt/share/html/Search/Build.html6
-rw-r--r--rt/share/html/Search/Chart.html2
-rw-r--r--rt/share/html/Search/Elements/BuildFormatString12
-rw-r--r--rt/share/html/Search/Elements/Chart4
-rw-r--r--rt/share/html/Search/Elements/PickBasics7
-rw-r--r--rt/share/html/Search/Elements/PickCFs20
-rw-r--r--rt/share/html/Search/Elements/PickCriteria4
-rwxr-xr-xrt/share/html/Search/Results.html12
-rw-r--r--rt/share/html/Search/Simple.html10
-rwxr-xr-xrt/share/html/SelfService/Elements/MyRequests22
-rwxr-xr-xrt/share/html/SelfService/index.html2
-rwxr-xr-xrt/share/html/Ticket/Create.html45
-rwxr-xr-xrt/share/html/Ticket/Display.html6
-rw-r--r--rt/share/html/Ticket/Elements/Bookmark2
-rw-r--r--rt/share/html/Ticket/Elements/ClickToShowHistory2
-rw-r--r--rt/share/html/Ticket/Elements/FoldStanzaJS2
-rw-r--r--rt/share/html/Ticket/Elements/Reminders11
-rwxr-xr-xrt/share/html/Ticket/Elements/ShowHistory9
-rwxr-xr-xrt/share/html/Ticket/Elements/ShowRequestor4
-rw-r--r--rt/share/html/Ticket/Elements/UpdateCc6
-rw-r--r--rt/share/html/Ticket/GnuPG.html2
-rw-r--r--rt/share/html/Ticket/Graphs/Elements/EditGraphProperties2
-rw-r--r--rt/share/html/Ticket/Graphs/Elements/ShowGraph1
-rw-r--r--rt/share/html/Ticket/Graphs/dhandler1
-rwxr-xr-xrt/share/html/Ticket/ModifyLinks.html2
-rw-r--r--rt/share/html/Widgets/ComboBox4
-rwxr-xr-xrt/share/html/Widgets/TitleBoxStart2
-rwxr-xr-xrt/share/html/index.html2
-rwxr-xr-xrt/share/html/l2
-rw-r--r--rt/share/html/m/_elements/footer10
-rw-r--r--rt/share/html/m/ticket/create58
-rw-r--r--rt/share/html/m/ticket/show12
-rw-r--r--rt/share/html/m/tickets/search2
-rw-r--r--rt/t/api/date.t10
-rw-r--r--rt/t/api/tickets.t15
-rw-r--r--rt/t/lifecycles/basics.t2
-rw-r--r--rt/t/mail/mime_decoding.t28
-rw-r--r--rt/t/web/case-sensitivity.t2
-rw-r--r--rt/t/web/query_builder.t33
-rw-r--r--rt/t/web/redirect-after-login.t6
-rw-r--r--rt/t/web/rest.t83
-rw-r--r--rt/t/web/scrub.t4
-rw-r--r--rt/t/web/ticket_forward.t35
-rw-r--r--rt/t/web/ticket_links.t65
172 files changed, 1804 insertions, 1020 deletions
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 4556b0e..f6ad714 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -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)],
),
);
diff --git a/rt/Makefile.in b/rt/Makefile.in
index 98c2c30..b415a06 100644
--- a/rt/Makefile.in
+++ b/rt/Makefile.in
@@ -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 ''
diff --git a/rt/bin/rt b/rt/bin/rt
index 01e4a19..32f459a 100755
--- 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};
}
diff --git a/rt/bin/rt-mailgate.in b/rt/bin/rt-mailgate.in
index b125a94..72cada6 100644
--- a/rt/bin/rt-mailgate.in
+++ b/rt/bin/rt-mailgate.in
@@ -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();
}
diff --git a/rt/bin/rt.in b/rt/bin/rt.in
index 5e1c053..e54a07a 100644
--- a/rt/bin/rt.in
+++ b/rt/bin/rt.in
@@ -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};
}
diff --git a/rt/configure b/rt/configure
index 4decdbd..1862c5f 100755
--- a/rt/configure
+++ b/rt/configure
@@ -1,7 +1,7 @@
#! /bin/sh
# From configure.ac Revision.
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.68 for RT rt-4.0.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 = ""
@@ -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
;;
diff --git a/rt/docs/UPGRADING-4.0 b/rt/docs/UPGRADING-4.0
index a930134..4b64d2e 100644
--- a/rt/docs/UPGRADING-4.0
+++ b/rt/docs/UPGRADING-4.0
@@ -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
diff --git a/rt/docs/hacking.pod b/rt/docs/hacking.pod
index 8aa84fd..396c562 100644
--- a/rt/docs/hacking.pod
+++ b/rt/docs/hacking.pod
@@ -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.
diff --git a/rt/docs/security.pod b/rt/docs/security.pod
index b8650e0..620f868 100644
--- a/rt/docs/security.pod
+++ b/rt/docs/security.pod
@@ -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
diff --git a/rt/docs/web_deployment.pod b/rt/docs/web_deployment.pod
index 65065c5..4c3f73f 100644
--- a/rt/docs/web_deployment.pod
+++ b/rt/docs/web_deployment.pod
@@ -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
diff --git a/rt/etc/RT_Config.pm.in b/rt/etc/RT_Config.pm.in
index 925f0ca..de7660a 100644
--- a/rt/etc/RT_Config.pm.in
+++ b/rt/etc/RT_Config.pm.in
@@ -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:#'}
diff --git a/rt/etc/schema.mysql b/rt/etc/schema.mysql
index c313aaf..9ed0337 100644
--- a/rt/etc/schema.mysql
+++ b/rt/etc/schema.mysql
@@ -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
diff --git a/rt/etc/upgrade/vulnerable-passwords.in b/rt/etc/upgrade/vulnerable-passwords.in
index 728786f..a3d719c 100755
--- a/rt/etc/upgrade/vulnerable-passwords.in
+++ b/rt/etc/upgrade/vulnerable-passwords.in
@@ -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";
diff --git a/rt/lib/RT/ACL.pm b/rt/lib/RT/ACL.pm
index d7c9ef2..49a7f1d 100755
--- a/rt/lib/RT/ACL.pm
+++ b/rt/lib/RT/ACL.pm
@@ -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 => '=',
diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm
index c26e2eb..31489c8 100644
--- a/rt/lib/RT/Action/CreateTickets.pm
+++ b/rt/lib/RT/Action/CreateTickets.pm
@@ -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;
diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm
index e2aa00b..4ae1a8b 100755
--- a/rt/lib/RT/Action/SendEmail.pm
+++ b/rt/lib/RT/Action/SendEmail.pm
@@ -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(
diff --git a/rt/lib/RT/Article.pm b/rt/lib/RT/Article.pm
index 7310241..24b952a 100644
--- a/rt/lib/RT/Article.pm
+++ b/rt/lib/RT/Article.pm
@@ -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
diff --git a/rt/lib/RT/Attachments.pm b/rt/lib/RT/Attachments.pm
index c640b20..2bdbc24 100755
--- a/rt/lib/RT/Attachments.pm
+++ b/rt/lib/RT/Attachments.pm
@@ -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;
}
diff --git a/rt/lib/RT/Class.pm b/rt/lib/RT/Class.pm
index bb694ce..3906b9f 100644
--- a/rt/lib/RT/Class.pm
+++ b/rt/lib/RT/Class.pm
@@ -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;
diff --git a/rt/lib/RT/Config.pm b/rt/lib/RT/Config.pm
index cc47df3..c56d4c6 100644
--- a/rt/lib/RT/Config.pm
+++ b/rt/lib/RT/Config.pm
@@ -620,6 +620,7 @@ our %META = (
}
}
},
+ ReferrerWhitelist => { Type => 'ARRAY' },
ResolveDefaultUpdateType => {
PostLoadCheck => sub {
my $self = shift;
diff --git a/rt/lib/RT/CustomField.pm b/rt/lib/RT/CustomField.pm
index 095caa5..263bde8 100644
--- a/rt/lib/RT/CustomField.pm
+++ b/rt/lib/RT/CustomField.pm
@@ -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 );
}
diff --git a/rt/lib/RT/Dashboard/Mailer.pm b/rt/lib/RT/Dashboard/Mailer.pm
index 8558978..40b53b1 100644
--- a/rt/lib/RT/Dashboard/Mailer.pm
+++ b/rt/lib/RT/Dashboard/Mailer.pm
@@ -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;
}
diff --git a/rt/lib/RT/Date.pm b/rt/lib/RT/Date.pm
index 7a97fbe..442c770 100644
--- a/rt/lib/RT/Date.pm
+++ b/rt/lib/RT/Date.pm
@@ -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;
diff --git a/rt/lib/RT/Generated.pm b/rt/lib/RT/Generated.pm
index b02a413..2abcf3b 100644
--- a/rt/lib/RT/Generated.pm
+++ b/rt/lib/RT/Generated.pm
@@ -50,7 +50,7 @@ package RT;
use warnings;
use strict;
-our $VERSION = '4.0.5';
+our $VERSION = '4.0.6';
diff --git a/rt/lib/RT/Graph/Tickets.pm b/rt/lib/RT/Graph/Tickets.pm
index 112934e..b839824 100644
--- a/rt/lib/RT/Graph/Tickets.pm
+++ b/rt/lib/RT/Graph/Tickets.pm
@@ -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,
diff --git a/rt/lib/RT/Group.pm b/rt/lib/RT/Group.pm
index 779c026..b367b2f 100755
--- a/rt/lib/RT/Group.pm
+++ b/rt/lib/RT/Group.pm
@@ -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
diff --git a/rt/lib/RT/Groups.pm b/rt/lib/RT/Groups.pm
index 46f1c23..578109c 100755
--- a/rt/lib/RT/Groups.pm
+++ b/rt/lib/RT/Groups.pm
@@ -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',
diff --git a/rt/lib/RT/Handle.pm b/rt/lib/RT/Handle.pm
index bb19aa9..99d10e3 100644
--- a/rt/lib/RT/Handle.pm
+++ b/rt/lib/RT/Handle.pm
@@ -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.");
diff --git a/rt/lib/RT/I18N.pm b/rt/lib/RT/I18N.pm
index 971eaa1..cadf7cc 100644
--- a/rt/lib/RT/I18N.pm
+++ b/rt/lib/RT/I18N.pm
@@ -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;
diff --git a/rt/lib/RT/Installer.pm b/rt/lib/RT/Installer.pm
index 3976ade..d12abb6 100644
--- a/rt/lib/RT/Installer.pm
+++ b/rt/lib/RT/Installer.pm
@@ -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 {
diff --git a/rt/lib/RT/Interface/Email.pm b/rt/lib/RT/Interface/Email.pm
index b9145d6..02a1ec0 100755
--- a/rt/lib/RT/Interface/Email.pm
+++ b/rt/lib/RT/Interface/Email.pm
@@ -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 )
diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm
index 7c9d578..814a829 100644
--- a/rt/lib/RT/Interface/Web.pm
+++ b/rt/lib/RT/Interface/Web.pm
@@ -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;
diff --git a/rt/lib/RT/Interface/Web/Handler.pm b/rt/lib/RT/Interface/Web/Handler.pm
index 69eee60..a740167 100644
--- a/rt/lib/RT/Interface/Web/Handler.pm
+++ b/rt/lib/RT/Interface/Web/Handler.pm
@@ -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;
diff --git a/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm b/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm
index e2ec1e5..2cfc889 100755
--- a/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm
+++ b/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm
@@ -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'";
}
diff --git a/rt/lib/RT/Lifecycle.pm b/rt/lib/RT/Lifecycle.pm
index edb1795..056599e 100644
--- a/rt/lib/RT/Lifecycle.pm
+++ b/rt/lib/RT/Lifecycle.pm
@@ -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
diff --git a/rt/lib/RT/Links.pm b/rt/lib/RT/Links.pm
index 0d8ed2f..ccc72d7 100644
--- a/rt/lib/RT/Links.pm
+++ b/rt/lib/RT/Links.pm
@@ -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' },
+ );
}
diff --git a/rt/lib/RT/ObjectCustomField.pm b/rt/lib/RT/ObjectCustomField.pm
index 0b815ae..61bc355 100644
--- a/rt/lib/RT/ObjectCustomField.pm
+++ b/rt/lib/RT/ObjectCustomField.pm
@@ -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;
}
diff --git a/rt/lib/RT/ObjectCustomFieldValue.pm b/rt/lib/RT/ObjectCustomFieldValue.pm
index 0fd9d73..98714a0 100644
--- a/rt/lib/RT/ObjectCustomFieldValue.pm
+++ b/rt/lib/RT/ObjectCustomFieldValue.pm
@@ -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;
diff --git a/rt/lib/RT/Queue.pm b/rt/lib/RT/Queue.pm
index 3cb87c4..406df92 100755
--- a/rt/lib/RT/Queue.pm
+++ b/rt/lib/RT/Queue.pm
@@ -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
diff --git a/rt/lib/RT/Reminders.pm b/rt/lib/RT/Reminders.pm
index e7c28a8..2b66325 100644
--- a/rt/lib/RT/Reminders.pm
+++ b/rt/lib/RT/Reminders.pm
@@ -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',
diff --git a/rt/lib/RT/Report/Tickets.pm b/rt/lib/RT/Report/Tickets.pm
index af6e4ed..de40dbd 100644
--- a/rt/lib/RT/Report/Tickets.pm
+++ b/rt/lib/RT/Report/Tickets.pm
@@ -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 ) {
diff --git a/rt/lib/RT/Report/Tickets/Entry.pm b/rt/lib/RT/Report/Tickets/Entry.pm
index f3eeb4d..87754c4 100644
--- a/rt/lib/RT/Report/Tickets/Entry.pm
+++ b/rt/lib/RT/Report/Tickets/Entry.pm
@@ -83,6 +83,10 @@ sub LabelValue {
return $value;
}
+sub ObjectType {
+ return 'RT::Ticket';
+}
+
RT::Base->_ImportOverlays();
1;
diff --git a/rt/lib/RT/Scrip.pm b/rt/lib/RT/Scrip.pm
index d513e0a..9506616 100755
--- a/rt/lib/RT/Scrip.pm
+++ b/rt/lib/RT/Scrip.pm
@@ -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(@_);
diff --git a/rt/lib/RT/SearchBuilder.pm b/rt/lib/RT/SearchBuilder.pm
index 02d4c50..3e98551 100644
--- a/rt/lib/RT/SearchBuilder.pm
+++ b/rt/lib/RT/SearchBuilder.pm
@@ -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
diff --git a/rt/lib/RT/Shredder.pm b/rt/lib/RT/Shredder.pm
index 10d3536..40c73b36 100644
--- a/rt/lib/RT/Shredder.pm
+++ b/rt/lib/RT/Shredder.pm
@@ -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 $@;
diff --git a/rt/lib/RT/Shredder/Plugin.pm b/rt/lib/RT/Shredder/Plugin.pm
index e70d207..ad9af6a 100644
--- a/rt/lib/RT/Shredder/Plugin.pm
+++ b/rt/lib/RT/Shredder/Plugin.pm
@@ -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";
diff --git a/rt/lib/RT/Shredder/Queue.pm b/rt/lib/RT/Shredder/Queue.pm
index c2ec445..2c0d068 100644
--- a/rt/lib/RT/Shredder/Queue.pm
+++ b/rt/lib/RT/Shredder/Queue.pm
@@ -91,6 +91,7 @@ sub __DependsOn
# Custom Fields
$objs = RT::CustomFields->new( $self->CurrentUser );
+ $objs->SetContextObject( $self );
$objs->LimitToQueue( $self->id );
push( @$list, $objs );
diff --git a/rt/lib/RT/Template.pm b/rt/lib/RT/Template.pm
index 158547a..117cc3f 100755
--- a/rt/lib/RT/Template.pm
+++ b/rt/lib/RT/Template.pm
@@ -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( @_ );
}
diff --git a/rt/lib/RT/Test.pm b/rt/lib/RT/Test.pm
index 0d6da1b..7d69dd6 100644
--- a/rt/lib/RT/Test.pm
+++ b/rt/lib/RT/Test.pm
@@ -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);
}
diff --git a/rt/lib/RT/Test/Web.pm b/rt/lib/RT/Test/Web.pm
index 28ca3b8..c2d9ac3 100644
--- a/rt/lib/RT/Test/Web.pm
+++ b/rt/lib/RT/Test/Web.pm
@@ -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;
diff --git a/rt/lib/RT/Ticket.pm b/rt/lib/RT/Ticket.pm
index 27bdc48..de8a396 100755
--- a/rt/lib/RT/Ticket.pm
+++ b/rt/lib/RT/Ticket.pm
@@ -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;
}
diff --git a/rt/lib/RT/Tickets.pm b/rt/lib/RT/Tickets.pm
index db54b52..4e2415b 100755
--- a/rt/lib/RT/Tickets.pm
+++ b/rt/lib/RT/Tickets.pm
@@ -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;
}
diff --git a/rt/lib/RT/Transaction.pm b/rt/lib/RT/Transaction.pm
index d0e6456..1f1bab1 100755
--- a/rt/lib/RT/Transaction.pm
+++ b/rt/lib/RT/Transaction.pm
@@ -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);
diff --git a/rt/lib/RT/URI.pm b/rt/lib/RT/URI.pm
index 4af1cb0..fce0459 100644
--- a/rt/lib/RT/URI.pm
+++ b/rt/lib/RT/URI.pm
@@ -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 {
diff --git a/rt/lib/RT/URI/fsck_com_article.pm b/rt/lib/RT/URI/fsck_com_article.pm
index 503434a..0c09b7c 100644
--- a/rt/lib/RT/URI/fsck_com_article.pm
+++ b/rt/lib/RT/URI/fsck_com_article.pm
@@ -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);
diff --git a/rt/lib/RT/User.pm b/rt/lib/RT/User.pm
index 2a14cd1..9b4a826 100755
--- a/rt/lib/RT/User.pm
+++ b/rt/lib/RT/User.pm
@@ -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
diff --git a/rt/lib/RT/Users.pm b/rt/lib/RT/Users.pm
index f3b1b5c..2784fc7 100755
--- a/rt/lib/RT/Users.pm
+++ b/rt/lib/RT/Users.pm
@@ -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;
}
diff --git a/rt/sbin/rt-server.fcgi.in b/rt/sbin/rt-server.fcgi.in
index b438202..45c3770 100644
--- a/rt/sbin/rt-server.fcgi.in
+++ b/rt/sbin/rt-server.fcgi.in
@@ -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;
diff --git a/rt/sbin/rt-server.in b/rt/sbin/rt-server.in
index b438202..45c3770 100644
--- a/rt/sbin/rt-server.in
+++ b/rt/sbin/rt-server.in
@@ -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;
diff --git a/rt/sbin/rt-shredder.in b/rt/sbin/rt-shredder.in
index c52116f..c0655db 100755
--- a/rt/sbin/rt-shredder.in
+++ b/rt/sbin/rt-shredder.in
@@ -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
diff --git a/rt/sbin/rt-test-dependencies.in b/rt/sbin/rt-test-dependencies.in
index ebea86c..37ef32f 100644
--- a/rt/sbin/rt-test-dependencies.in
+++ b/rt/sbin/rt-test-dependencies.in
@@ -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( << '.') ];
diff --git a/rt/sbin/standalone_httpd b/rt/sbin/standalone_httpd
index 78533c6..3386cd1 100755
--- a/rt/sbin/standalone_httpd
+++ b/rt/sbin/standalone_httpd
@@ -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;
diff --git a/rt/sbin/standalone_httpd.in b/rt/sbin/standalone_httpd.in
index b438202..45c3770 100644
--- a/rt/sbin/standalone_httpd.in
+++ b/rt/sbin/standalone_httpd.in
@@ -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;
diff --git a/rt/share/html/Admin/Articles/Elements/Topics b/rt/share/html/Admin/Articles/Elements/Topics
index 96ddaf0..43ca956 100644
--- a/rt/share/html/Admin/Articles/Elements/Topics
+++ b/rt/share/html/Admin/Articles/Elements/Topics
@@ -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>
diff --git a/rt/share/html/Admin/CustomFields/Modify.html b/rt/share/html/Admin/CustomFields/Modify.html
index 20c3e9c..4ed86b6 100644
--- a/rt/share/html/Admin/CustomFields/Modify.html
+++ b/rt/share/html/Admin/CustomFields/Modify.html
@@ -113,7 +113,7 @@
<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>
@@ -121,7 +121,7 @@
<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>
diff --git a/rt/share/html/Admin/Elements/EditCustomFields b/rt/share/html/Admin/Elements/EditCustomFields
index aa7b622..d9d9134 100755
--- a/rt/share/html/Admin/Elements/EditCustomFields
+++ b/rt/share/html/Admin/Elements/EditCustomFields
@@ -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'} );
diff --git a/rt/share/html/Admin/Elements/EditRights b/rt/share/html/Admin/Elements/EditRights
index e5b9908..e673593 100644
--- a/rt/share/html/Admin/Elements/EditRights
+++ b/rt/share/html/Admin/Elements/EditRights
@@ -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
});
diff --git a/rt/share/html/Admin/Elements/Portal b/rt/share/html/Admin/Elements/Portal
index d5e75c5..821ed57 100644
--- a/rt/share/html/Admin/Elements/Portal
+++ b/rt/share/html/Admin/Elements/Portal
@@ -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>
diff --git a/rt/share/html/Admin/Elements/SelectNewGroupMembers b/rt/share/html/Admin/Elements/SelectNewGroupMembers
index f386ba5..8778dae 100755
--- a/rt/share/html/Admin/Elements/SelectNewGroupMembers
+++ b/rt/share/html/Admin/Elements/SelectNewGroupMembers
@@ -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);
diff --git a/rt/share/html/Admin/Groups/index.html b/rt/share/html/Admin/Groups/index.html
index bd07b73..ef7395f 100755
--- a/rt/share/html/Admin/Groups/index.html
+++ b/rt/share/html/Admin/Groups/index.html
@@ -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);
diff --git a/rt/share/html/Admin/Tools/Queries.html b/rt/share/html/Admin/Tools/Queries.html
index 2fe2d5a..dbc6fc5 100644
--- a/rt/share/html/Admin/Tools/Queries.html
+++ b/rt/share/html/Admin/Tools/Queries.html
@@ -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>
diff --git a/rt/share/html/Admin/Tools/Shredder/Dumps/dhandler b/rt/share/html/Admin/Tools/Shredder/Dumps/dhandler
index 8b84cf4..0d24fa0 100644
--- a/rt/share/html/Admin/Tools/Shredder/Dumps/dhandler
+++ b/rt/share/html/Admin/Tools/Shredder/Dumps/dhandler
@@ -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>
diff --git a/rt/share/html/Admin/Tools/Shredder/Elements/Error/NoStorage b/rt/share/html/Admin/Tools/Shredder/Elements/Error/NoStorage
index ce93411..ae3b96e 100644
--- a/rt/share/html/Admin/Tools/Shredder/Elements/Error/NoStorage
+++ b/rt/share/html/Admin/Tools/Shredder/Elements/Error/NoStorage
@@ -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>
diff --git a/rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Attachment b/rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Attachment
index 11b876b..0da910d 100644
--- a/rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Attachment
+++ b/rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Attachment
@@ -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>
diff --git a/rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Ticket b/rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Ticket
index 13547ad..35f1aa8 100644
--- a/rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Ticket
+++ b/rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Ticket
@@ -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>
diff --git a/rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--User b/rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--User
index f77169a..d7627eb 100644
--- a/rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--User
+++ b/rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--User
@@ -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>
diff --git a/rt/share/html/Admin/Users/Modify.html b/rt/share/html/Admin/Users/Modify.html
index b14f936..05dde17 100755
--- a/rt/share/html/Admin/Users/Modify.html
+++ b/rt/share/html/Admin/Users/Modify.html
@@ -203,7 +203,7 @@
% 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);
diff --git a/rt/share/html/Admin/Users/index.html b/rt/share/html/Admin/Users/index.html
index a1e3fac..adcfeb5 100755
--- a/rt/share/html/Admin/Users/index.html
+++ b/rt/share/html/Admin/Users/index.html
@@ -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);
diff --git a/rt/share/html/Approvals/Elements/PendingMyApproval b/rt/share/html/Approvals/Elements/PendingMyApproval
index 75ad5e1..d2061da 100755
--- a/rt/share/html/Approvals/Elements/PendingMyApproval
+++ b/rt/share/html/Approvals/Elements/PendingMyApproval
@@ -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>
diff --git a/rt/share/html/Articles/Article/Edit.html b/rt/share/html/Articles/Article/Edit.html
index 756aa2c..d14c330 100644
--- a/rt/share/html/Articles/Article/Edit.html
+++ b/rt/share/html/Articles/Article/Edit.html
@@ -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 );
diff --git a/rt/share/html/Articles/Article/Elements/EditTopics b/rt/share/html/Articles/Article/Elements/EditTopics
index 807360b..82e9071 100644
--- a/rt/share/html/Articles/Article/Elements/EditTopics
+++ b/rt/share/html/Articles/Article/Elements/EditTopics
@@ -47,35 +47,32 @@
%# 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>
diff --git a/rt/share/html/Articles/Article/ExtractIntoClass.html b/rt/share/html/Articles/Article/ExtractIntoClass.html
index adf23fc..f3618fe 100644
--- a/rt/share/html/Articles/Article/ExtractIntoClass.html
+++ b/rt/share/html/Articles/Article/ExtractIntoClass.html
@@ -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">
diff --git a/rt/share/html/Articles/Topics.html b/rt/share/html/Articles/Topics.html
index 9a07c08..5187315 100644
--- a/rt/share/html/Articles/Topics.html
+++ b/rt/share/html/Articles/Topics.html
@@ -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>
@@ -59,71 +58,41 @@
% }
<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>
diff --git a/rt/share/html/Elements/CollectionAsTable/Header b/rt/share/html/Elements/CollectionAsTable/Header
index 81d8bbb..20586f9 100644
--- a/rt/share/html/Elements/CollectionAsTable/Header
+++ b/rt/share/html/Elements/CollectionAsTable/Header
@@ -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>'
);
}
diff --git a/rt/share/html/Elements/CollectionList b/rt/share/html/Elements/CollectionList
index 0c816fe..402a272 100644
--- a/rt/share/html/Elements/CollectionList
+++ b/rt/share/html/Elements/CollectionList
@@ -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(
diff --git a/rt/share/html/Elements/CollectionListPaging b/rt/share/html/Elements/CollectionListPaging
index b1faa21..26c0823 100644
--- a/rt/share/html/Elements/CollectionListPaging
+++ b/rt/share/html/Elements/CollectionListPaging
@@ -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> });
diff --git a/rt/share/html/Elements/ColumnMap b/rt/share/html/Elements/ColumnMap
index 745e57f..b9c3b4b 100644
--- a/rt/share/html/Elements/ColumnMap
+++ b/rt/share/html/Elements/ColumnMap
@@ -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 => {
diff --git a/rt/share/html/Elements/CreateTicket b/rt/share/html/Elements/CreateTicket
index 6e541db..6702abc 100755
--- a/rt/share/html/Elements/CreateTicket
+++ b/rt/share/html/Elements/CreateTicket
@@ -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>
diff --git a/rt/share/html/Elements/EditCustomField b/rt/share/html/Elements/EditCustomField
index c7c8bfa..b74c484 100644
--- a/rt/share/html/Elements/EditCustomField
+++ b/rt/share/html/Elements/EditCustomField
@@ -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 );
diff --git a/rt/share/html/Elements/EditCustomFieldAutocomplete b/rt/share/html/Elements/EditCustomFieldAutocomplete
index aaf5517..911e607 100644
--- a/rt/share/html/Elements/EditCustomFieldAutocomplete
+++ b/rt/share/html/Elements/EditCustomFieldAutocomplete
@@ -49,10 +49,10 @@
<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
diff --git a/rt/share/html/Elements/EditCustomFieldSelect b/rt/share/html/Elements/EditCustomFieldSelect
index b3fefbd..ed6bb14 100644
--- a/rt/share/html/Elements/EditCustomFieldSelect
+++ b/rt/share/html/Elements/EditCustomFieldSelect
@@ -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;
@@ -66,12 +66,12 @@
<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
);
diff --git a/rt/share/html/Elements/Error b/rt/share/html/Elements/Error
index 50f3b77..87dfd02 100755
--- a/rt/share/html/Elements/Error
+++ b/rt/share/html/Elements/Error
@@ -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;
diff --git a/rt/share/html/Elements/Header b/rt/share/html/Elements/Header
index 5d89fe6..1830c4b 100755
--- a/rt/share/html/Elements/Header
+++ b/rt/share/html/Elements/Header
@@ -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');
diff --git a/rt/share/html/Elements/HeaderJavascript b/rt/share/html/Elements/HeaderJavascript
index e392ac2..28788db 100644
--- a/rt/share/html/Elements/HeaderJavascript
+++ b/rt/share/html/Elements/HeaderJavascript
@@ -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>
diff --git a/rt/share/html/Elements/MessageBox b/rt/share/html/Elements/MessageBox
index 2943cab..61995e0 100755
--- a/rt/share/html/Elements/MessageBox
+++ b/rt/share/html/Elements/MessageBox
@@ -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>
diff --git a/rt/share/html/Elements/RT__CustomField/ColumnMap b/rt/share/html/Elements/RT__CustomField/ColumnMap
index 06e2674..ecb219d 100644
--- a/rt/share/html/Elements/RT__CustomField/ColumnMap
+++ b/rt/share/html/Elements/RT__CustomField/ColumnMap
@@ -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 => {
diff --git a/rt/share/html/Elements/RT__Dashboard/ColumnMap b/rt/share/html/Elements/RT__Dashboard/ColumnMap
index 8bc4383..6c366ec 100644
--- a/rt/share/html/Elements/RT__Dashboard/ColumnMap
+++ b/rt/share/html/Elements/RT__Dashboard/ColumnMap
@@ -111,7 +111,7 @@ my $COLUMN_MAP = {
}
}
- return \('<a href="'.$url.'">'.$frequency.'</a>');
+ return \'<a href="', $url, \'">', $frequency, \'</a>';
},
},
ShowURL => {
diff --git a/rt/share/html/Elements/RT__Queue/ColumnMap b/rt/share/html/Elements/RT__Queue/ColumnMap
index 00655c5..e08dd7c 100644
--- a/rt/share/html/Elements/RT__Queue/ColumnMap
+++ b/rt/share/html/Elements/RT__Queue/ColumnMap
@@ -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,
diff --git a/rt/share/html/Elements/SelectOwner b/rt/share/html/Elements/SelectOwner
index cc64e24..37a5971 100755
--- a/rt/share/html/Elements/SelectOwner
+++ b/rt/share/html/Elements/SelectOwner
@@ -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>
diff --git a/rt/share/html/Elements/SelectOwnerAutocomplete b/rt/share/html/Elements/SelectOwnerAutocomplete
index cf2010a..81b3838 100644
--- a/rt/share/html/Elements/SelectOwnerAutocomplete
+++ b/rt/share/html/Elements/SelectOwnerAutocomplete
@@ -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 ) {
diff --git a/rt/share/html/Elements/SelectStatus b/rt/share/html/Elements/SelectStatus
index e571baf..af1ff61 100755
--- a/rt/share/html/Elements/SelectStatus
+++ b/rt/share/html/Elements/SelectStatus
@@ -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
diff --git a/rt/share/html/Elements/ShowCustomFields b/rt/share/html/Elements/ShowCustomFields
index fcd530e..6059f4e 100644
--- a/rt/share/html/Elements/ShowCustomFields
+++ b/rt/share/html/Elements/ShowCustomFields
@@ -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} );
}
};
diff --git a/rt/share/html/Elements/ShowLink b/rt/share/html/Elements/ShowLink
index 8913a32..1727fa3 100644
--- a/rt/share/html/Elements/ShowLink
+++ b/rt/share/html/Elements/ShowLink
@@ -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'));
@@ -69,3 +69,12 @@
<%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>
diff --git a/rt/share/html/Elements/ShowSearch b/rt/share/html/Elements/ShowSearch
index 2b23181..4b96bbf 100644
--- a/rt/share/html/Elements/ShowSearch
+++ b/rt/share/html/Elements/ShowSearch
@@ -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;
}
}
diff --git a/rt/share/html/Elements/ShowUser b/rt/share/html/Elements/ShowUser
index 044ec4c..3654977 100644
--- a/rt/share/html/Elements/ShowUser
+++ b/rt/share/html/Elements/ShowUser
@@ -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
diff --git a/rt/share/html/Elements/Submit b/rt/share/html/Elements/Submit
index cbf3f58..b7840d3 100755
--- a/rt/share/html/Elements/Submit
+++ b/rt/share/html/Elements/Submit
@@ -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>
diff --git a/rt/share/html/Elements/Tabs b/rt/share/html/Elements/Tabs
index 75b8160..3193b48 100755
--- a/rt/share/html/Elements/Tabs
+++ b/rt/share/html/Elements/Tabs
@@ -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" );
diff --git a/rt/share/html/Helpers/Autocomplete/CustomFieldValues b/rt/share/html/Helpers/Autocomplete/CustomFieldValues
index b8b21e4..887302f 100644
--- a/rt/share/html/Helpers/Autocomplete/CustomFieldValues
+++ b/rt/share/html/Helpers/Autocomplete/CustomFieldValues
@@ -52,6 +52,17 @@
# 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(
diff --git a/rt/share/html/Helpers/Toggle/ShowRequestor b/rt/share/html/Helpers/Toggle/ShowRequestor
index bb90b98..68e8a05 100644
--- a/rt/share/html/Helpers/Toggle/ShowRequestor
+++ b/rt/share/html/Helpers/Toggle/ShowRequestor
@@ -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) {
diff --git a/rt/share/html/Install/DatabaseType.html b/rt/share/html/Install/DatabaseType.html
index 3312b57..68f8a67 100644
--- a/rt/share/html/Install/DatabaseType.html
+++ b/rt/share/html/Install/DatabaseType.html
@@ -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>
diff --git a/rt/share/html/Install/Finish.html b/rt/share/html/Install/Finish.html
index ee81e70..24ac0ff 100644
--- a/rt/share/html/Install/Finish.html
+++ b/rt/share/html/Install/Finish.html
@@ -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>
diff --git a/rt/share/html/Install/Initialize.html b/rt/share/html/Install/Initialize.html
index 47d7616..0cc39af 100644
--- a/rt/share/html/Install/Initialize.html
+++ b/rt/share/html/Install/Initialize.html
@@ -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);
diff --git a/rt/share/html/Install/index.html b/rt/share/html/Install/index.html
index 61fb89e..78069af 100644
--- a/rt/share/html/Install/index.html
+++ b/rt/share/html/Install/index.html
@@ -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
diff --git a/rt/share/html/NoAuth/Logout.html b/rt/share/html/NoAuth/Logout.html
index b8e119a..20024cc 100755
--- a/rt/share/html/NoAuth/Logout.html
+++ b/rt/share/html/NoAuth/Logout.html
@@ -81,5 +81,5 @@ if (keys %session) {
}
$m->callback( %ARGS, CallbackName => 'AfterSessionDelete' );
-$m->notes->{LogoutURL} = $URL;
+$m->notes->{RefreshURL} = $URL;
</%INIT>
diff --git a/rt/share/html/NoAuth/css/aileron/InHeader b/rt/share/html/NoAuth/css/aileron/InHeader
index aff24d8..e6d4cb3 100644
--- a/rt/share/html/NoAuth/css/aileron/InHeader
+++ b/rt/share/html/NoAuth/css/aileron/InHeader
@@ -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]-->
diff --git a/rt/share/html/NoAuth/css/aileron/boxes.css b/rt/share/html/NoAuth/css/aileron/boxes.css
index f90ac9f..ed6623c 100644
--- a/rt/share/html/NoAuth/css/aileron/boxes.css
+++ b/rt/share/html/NoAuth/css/aileron/boxes.css
@@ -91,6 +91,10 @@
.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
index baa9ebe..0000000
--- a/rt/share/html/NoAuth/css/aileron/msie-pie.css
+++ /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
index 6a40cef..0000000
--- a/rt/share/html/NoAuth/css/images/PIE.htc
+++ /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>
diff --git a/rt/share/html/NoAuth/css/web2/InHeader b/rt/share/html/NoAuth/css/web2/InHeader
index 408a541..a083eec 100644
--- a/rt/share/html/NoAuth/css/web2/InHeader
+++ b/rt/share/html/NoAuth/css/web2/InHeader
@@ -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
index 73d76d0..0000000
--- a/rt/share/html/NoAuth/css/web2/msie-pie.css
+++ /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);
-}
diff --git a/rt/share/html/NoAuth/js/titlebox-state.js b/rt/share/html/NoAuth/js/titlebox-state.js
index 5199637..a5e5dac 100644
--- a/rt/share/html/NoAuth/js/titlebox-state.js
+++ b/rt/share/html/NoAuth/js/titlebox-state.js
@@ -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();
diff --git a/rt/share/html/NoAuth/js/userautocomplete.js b/rt/share/html/NoAuth/js/userautocomplete.js
index db244d1..b4f678c 100644
--- a/rt/share/html/NoAuth/js/userautocomplete.js
+++ b/rt/share/html/NoAuth/js/userautocomplete.js
@@ -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 = [];
diff --git a/rt/share/html/NoAuth/js/util.js b/rt/share/html/NoAuth/js/util.js
index 62ee922..5bfce41 100644
--- a/rt/share/html/NoAuth/js/util.js
+++ b/rt/share/html/NoAuth/js/util.js
@@ -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");
}
diff --git a/rt/share/html/Prefs/Search.html b/rt/share/html/Prefs/Search.html
index fdd9c17..42aa16b 100644
--- a/rt/share/html/Prefs/Search.html
+++ b/rt/share/html/Prefs/Search.html
@@ -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'}) {
diff --git a/rt/share/html/REST/1.0/Forms/ticket/default b/rt/share/html/REST/1.0/Forms/ticket/default
index 9ae803d..9a2212b 100755
--- a/rt/share/html/REST/1.0/Forms/ticket/default
+++ b/rt/share/html/REST/1.0/Forms/ticket/default
@@ -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.";
}
diff --git a/rt/share/html/REST/1.0/Forms/ticket/links b/rt/share/html/REST/1.0/Forms/ticket/links
index e2e1830..bf4f257 100755
--- a/rt/share/html/REST/1.0/Forms/ticket/links
+++ b/rt/share/html/REST/1.0/Forms/ticket/links
@@ -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;
diff --git a/rt/share/html/REST/1.0/Forms/transaction/default b/rt/share/html/REST/1.0/Forms/transaction/default
index 1ffa2b2..2e45f67 100644
--- a/rt/share/html/REST/1.0/Forms/transaction/default
+++ b/rt/share/html/REST/1.0/Forms/transaction/default
@@ -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);
diff --git a/rt/share/html/REST/1.0/ticket/link b/rt/share/html/REST/1.0/ticket/link
index aa80b0d..8d3345f 100755
--- a/rt/share/html/REST/1.0/ticket/link
+++ b/rt/share/html/REST/1.0/ticket/link
@@ -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;
}
diff --git a/rt/share/html/Search/Build.html b/rt/share/html/Search/Build.html
index ae4c7ba..b200f90 100644
--- a/rt/share/html/Search/Build.html
+++ b/rt/share/html/Search/Build.html
@@ -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;
}
diff --git a/rt/share/html/Search/Chart.html b/rt/share/html/Search/Chart.html
index 884d183..070ce7c 100644
--- a/rt/share/html/Search/Chart.html
+++ b/rt/share/html/Search/Chart.html
@@ -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>
</&>
diff --git a/rt/share/html/Search/Elements/BuildFormatString b/rt/share/html/Search/Elements/BuildFormatString
index 3769972..a39287b 100644
--- a/rt/share/html/Search/Elements/BuildFormatString
+++ b/rt/share/html/Search/Elements/BuildFormatString
@@ -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;
diff --git a/rt/share/html/Search/Elements/Chart b/rt/share/html/Search/Elements/Chart
index 01b78c7..be05da3 100644
--- a/rt/share/html/Search/Elements/Chart
+++ b/rt/share/html/Search/Elements/Chart
@@ -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>
diff --git a/rt/share/html/Search/Elements/PickBasics b/rt/share/html/Search/Elements/PickBasics
index 7223b75..db7d9f5 100644
--- a/rt/share/html/Search/Elements/PickBasics
+++ b/rt/share/html/Search/Elements/PickBasics
@@ -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>
diff --git a/rt/share/html/Search/Elements/PickCFs b/rt/share/html/Search/Elements/PickCFs
index 4b9a88b..f2dc21f 100644
--- a/rt/share/html/Search/Elements/PickCFs
+++ b/rt/share/html/Search/Elements/PickCFs
@@ -50,21 +50,11 @@
% }
<%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>
diff --git a/rt/share/html/Search/Elements/PickCriteria b/rt/share/html/Search/Elements/PickCriteria
index 5d0b8af..74547c7 100644
--- a/rt/share/html/Search/Elements/PickCriteria
+++ b/rt/share/html/Search/Elements/PickCriteria
@@ -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>
diff --git a/rt/share/html/Search/Results.html b/rt/share/html/Search/Results.html
index 0040d2a..171b38d 100755
--- a/rt/share/html/Search/Results.html
+++ b/rt/share/html/Search/Results.html
@@ -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(
diff --git a/rt/share/html/Search/Simple.html b/rt/share/html/Search/Simple.html
index 07bd2f4..4d7b1e3 100644
--- a/rt/share/html/Search/Simple.html
+++ b/rt/share/html/Search/Simple.html
@@ -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;
@@ -82,13 +82,13 @@
% }
</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>
diff --git a/rt/share/html/SelfService/Elements/MyRequests b/rt/share/html/SelfService/Elements/MyRequests
index 549d458..76accfc 100755
--- a/rt/share/html/SelfService/Elements/MyRequests
+++ b/rt/share/html/SelfService/Elements/MyRequests
@@ -45,42 +45,34 @@
%# 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
diff --git a/rt/share/html/SelfService/index.html b/rt/share/html/SelfService/index.html
index a89296b..29accf5 100755
--- a/rt/share/html/SelfService/index.html
+++ b/rt/share/html/SelfService/index.html
@@ -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,
&>
diff --git a/rt/share/html/Ticket/Create.html b/rt/share/html/Ticket/Create.html
index 9fb2b2b..f29dfbb 100755
--- a/rt/share/html/Ticket/Create.html
+++ b/rt/share/html/Ticket/Create.html
@@ -110,7 +110,7 @@
<& /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 ) {
diff --git a/rt/share/html/Ticket/Display.html b/rt/share/html/Ticket/Display.html
index 6deb65c..0a29c97 100755
--- a/rt/share/html/Ticket/Display.html
+++ b/rt/share/html/Ticket/Display.html
@@ -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',
% );
diff --git a/rt/share/html/Ticket/Elements/Bookmark b/rt/share/html/Ticket/Elements/Bookmark
index 8393191..30c9a43 100644
--- a/rt/share/html/Ticket/Elements/Bookmark
+++ b/rt/share/html/Ticket/Elements/Bookmark
@@ -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 {
diff --git a/rt/share/html/Ticket/Elements/ClickToShowHistory b/rt/share/html/Ticket/Elements/ClickToShowHistory
index 4461b9a..5a9a477 100644
--- a/rt/share/html/Ticket/Elements/ClickToShowHistory
+++ b/rt/share/html/Ticket/Elements/ClickToShowHistory
@@ -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>
diff --git a/rt/share/html/Ticket/Elements/FoldStanzaJS b/rt/share/html/Ticket/Elements/FoldStanzaJS
index be6b264..4b0b4c4 100644
--- a/rt/share/html/Ticket/Elements/FoldStanzaJS
+++ b/rt/share/html/Ticket/Elements/FoldStanzaJS
@@ -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 />\
diff --git a/rt/share/html/Ticket/Elements/Reminders b/rt/share/html/Ticket/Elements/Reminders
index 563b0f0..36d0d8e 100644
--- a/rt/share/html/Ticket/Elements/Reminders
+++ b/rt/share/html/Ticket/Elements/Reminders
@@ -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>
diff --git a/rt/share/html/Ticket/Elements/ShowHistory b/rt/share/html/Ticket/Elements/ShowHistory
index b5e009c..909ea01 100755
--- a/rt/share/html/Ticket/Elements/ShowHistory
+++ b/rt/share/html/Ticket/Elements/ShowHistory
@@ -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=} .
diff --git a/rt/share/html/Ticket/Elements/ShowRequestor b/rt/share/html/Ticket/Elements/ShowRequestor
index 3e5f41f..8a8eef6 100755
--- a/rt/share/html/Ticket/Elements/ShowRequestor
+++ b/rt/share/html/Ticket/Elements/ShowRequestor
@@ -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
diff --git a/rt/share/html/Ticket/Elements/UpdateCc b/rt/share/html/Ticket/Elements/UpdateCc
index 392ee86..d062156 100644
--- a/rt/share/html/Ticket/Elements/UpdateCc
+++ b/rt/share/html/Ticket/Elements/UpdateCc
@@ -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}&>
%}
diff --git a/rt/share/html/Ticket/GnuPG.html b/rt/share/html/Ticket/GnuPG.html
index 6269907..d15ce72 100644
--- a/rt/share/html/Ticket/GnuPG.html
+++ b/rt/share/html/Ticket/GnuPG.html
@@ -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,
diff --git a/rt/share/html/Ticket/Graphs/Elements/EditGraphProperties b/rt/share/html/Ticket/Graphs/Elements/EditGraphProperties
index e68aaed..c5479e3 100644
--- a/rt/share/html/Ticket/Graphs/Elements/EditGraphProperties
+++ b/rt/share/html/Ticket/Graphs/Elements/EditGraphProperties
@@ -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>
diff --git a/rt/share/html/Ticket/Graphs/Elements/ShowGraph b/rt/share/html/Ticket/Graphs/Elements/ShowGraph
index 967a5e4..2163b81 100644
--- a/rt/share/html/Ticket/Graphs/Elements/ShowGraph
+++ b/rt/share/html/Ticket/Graphs/Elements/ShowGraph
@@ -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>
diff --git a/rt/share/html/Ticket/Graphs/dhandler b/rt/share/html/Ticket/Graphs/dhandler
index ba41445..89b1f37 100644
--- a/rt/share/html/Ticket/Graphs/dhandler
+++ b/rt/share/html/Ticket/Graphs/dhandler
@@ -65,6 +65,7 @@ unless ( $ticket->id ) {
require RT::Graph::Tickets;
my $graph = RT::Graph::Tickets->TicketLinks(
%ARGS,
+ Graph => undef,
Ticket => $ticket,
);
diff --git a/rt/share/html/Ticket/ModifyLinks.html b/rt/share/html/Ticket/ModifyLinks.html
index 28942e0..9dceb2a 100755
--- a/rt/share/html/Ticket/ModifyLinks.html
+++ b/rt/share/html/Ticket/ModifyLinks.html
@@ -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);
diff --git a/rt/share/html/Widgets/ComboBox b/rt/share/html/Widgets/ComboBox
index 238087f..69ac079 100644
--- a/rt/share/html/Widgets/ComboBox
+++ b/rt/share/html/Widgets/ComboBox
@@ -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>
diff --git a/rt/share/html/Widgets/TitleBoxStart b/rt/share/html/Widgets/TitleBoxStart
index a031477..cbcc5c3 100755
--- a/rt/share/html/Widgets/TitleBoxStart
+++ b/rt/share/html/Widgets/TitleBoxStart
@@ -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
diff --git a/rt/share/html/index.html b/rt/share/html/index.html
index c24b6cd..d6e0b79 100755
--- a/rt/share/html/index.html
+++ b/rt/share/html/index.html
@@ -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>
diff --git a/rt/share/html/l b/rt/share/html/l
index 6396bc6..9f1b343 100755
--- a/rt/share/html/l
+++ b/rt/share/html/l
@@ -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>
diff --git a/rt/share/html/m/_elements/footer b/rt/share/html/m/_elements/footer
index f3e0837..e0c023c 100644
--- a/rt/share/html/m/_elements/footer
+++ b/rt/share/html/m/_elements/footer
@@ -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>
-% }
diff --git a/rt/share/html/m/ticket/create b/rt/share/html/m/ticket/create
index 7c23194..052a1a5 100644
--- a/rt/share/html/m/ticket/create
+++ b/rt/share/html/m/ticket/create
@@ -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>
diff --git a/rt/share/html/m/ticket/show b/rt/share/html/m/ticket/show
index e979da3..4190bd3 100644
--- a/rt/share/html/m/ticket/show
+++ b/rt/share/html/m/ticket/show
@@ -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} );
}
};
diff --git a/rt/share/html/m/tickets/search b/rt/share/html/m/tickets/search
index 16864b4..e688ea8 100644
--- a/rt/share/html/m/tickets/search
+++ b/rt/share/html/m/tickets/search
@@ -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;
}
}
diff --git a/rt/t/api/date.t b/rt/t/api/date.t
index 9756e51..6fcaa49 100644
--- a/rt/t/api/date.t
+++ b/rt/t/api/date.t
@@ -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");
diff --git a/rt/t/api/tickets.t b/rt/t/api/tickets.t
index cabb00e..50d08f7 100644
--- a/rt/t/api/tickets.t
+++ b/rt/t/api/tickets.t
@@ -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";
+}
+
diff --git a/rt/t/lifecycles/basics.t b/rt/t/lifecycles/basics.t
index 40e2391..5825a10 100644
--- a/rt/t/lifecycles/basics.t
+++ b/rt/t/lifecycles/basics.t
@@ -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";
diff --git a/rt/t/mail/mime_decoding.t b/rt/t/mail/mime_decoding.t
index b02f979..7515e2c 100644
--- a/rt/t/mail/mime_decoding.t
+++ b/rt/t/mail/mime_decoding.t
@@ -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'
);
}
diff --git a/rt/t/web/case-sensitivity.t b/rt/t/web/case-sensitivity.t
index 276b761..f984bf3 100644
--- a/rt/t/web/case-sensitivity.t
+++ b/rt/t/web/case-sensitivity.t
@@ -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 ),
diff --git a/rt/t/web/query_builder.t b/rt/t/web/query_builder.t
index 0abbfac..5a64d46 100644
--- a/rt/t/web/query_builder.t
+++ b/rt/t/web/query_builder.t
@@ -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" );
+}
diff --git a/rt/t/web/redirect-after-login.t b/rt/t/web/redirect-after-login.t
index d429d30..835b24c 100644
--- a/rt/t/web/redirect-after-login.t
+++ b/rt/t/web/redirect-after-login.t
@@ -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");
diff --git a/rt/t/web/rest.t b/rt/t/web/rest.t
index 5e7194c..e38f201 100644
--- a/rt/t/web/rest.t
+++ b/rt/t/web/rest.t
@@ -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);
diff --git a/rt/t/web/scrub.t b/rt/t/web/scrub.t
index 6483a75..612c6e2 100644
--- a/rt/t/web/scrub.t
+++ b/rt/t/web/scrub.t
@@ -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");
}
diff --git a/rt/t/web/ticket_forward.t b/rt/t/web/ticket_forward.t
index be06ad9..1d74673 100644
--- a/rt/t/web/ticket_forward.t
+++ b/rt/t/web/ticket_forward.t
@@ -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;
diff --git a/rt/t/web/ticket_links.t b/rt/t/web/ticket_links.t
index cb30e92..efb6151 100644
--- a/rt/t/web/ticket_links.t
+++ b/rt/t/web/ticket_links.t
@@ -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');
+ }
+ }
}
}