diff options
Diffstat (limited to 'rt')
116 files changed, 2509 insertions, 2566 deletions
@@ -420,7 +420,7 @@ sub show { } elsif (my $spec = is_object_spec($_, $type)) { push @objects, $spec; - $rawprint = 1 if $_ =~ /\/content$/ or $_ !~ /^ticket/; + $rawprint = 1 if $_ =~ /\/content$/ or $_ =~ /\/links/ or $_ !~ /^ticket/; } else { my $datum = /^-/ ? "option" : "argument"; diff --git a/rt/bin/rt.in b/rt/bin/rt.in index e54a07add..2a9f643c8 100644 --- a/rt/bin/rt.in +++ b/rt/bin/rt.in @@ -420,7 +420,7 @@ sub show { } elsif (my $spec = is_object_spec($_, $type)) { push @objects, $spec; - $rawprint = 1 if $_ =~ /\/content$/ or $_ !~ /^ticket/; + $rawprint = 1 if $_ =~ /\/content$/ or $_ =~ /\/links/ or $_ !~ /^ticket/; } else { my $datum = /^-/ ? "option" : "argument"; diff --git a/rt/configure b/rt/configure index 1862c5fe6..76ef85b92 100755 --- a/rt/configure +++ b/rt/configure @@ -1,7 +1,7 @@ #! /bin/sh # From configure.ac Revision. # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.67 for RT rt-4.0.6. +# Generated by GNU Autoconf 2.68 for RT rt-4.0.7. # # Report bugs to <rt-bugs@bestpractical.com>. # @@ -92,6 +92,7 @@ fi IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. +as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -216,11 +217,18 @@ IFS=$as_save_IFS # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. + # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV export CONFIG_SHELL - exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"} + case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; + esac + exec "$CONFIG_SHELL" $as_opts "$as_myself" ${1+"$@"} fi if test x$as_have_required = xno; then : @@ -552,8 +560,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='RT' PACKAGE_TARNAME='rt' -PACKAGE_VERSION='rt-4.0.6' -PACKAGE_STRING='RT rt-4.0.6' +PACKAGE_VERSION='rt-4.0.7' +PACKAGE_STRING='RT rt-4.0.7' PACKAGE_BUGREPORT='rt-bugs@bestpractical.com' PACKAGE_URL='' @@ -1165,7 +1173,7 @@ Try \`$0 --help' for more information" $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 - : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option} + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac @@ -1303,7 +1311,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures RT rt-4.0.6 to adapt to many kinds of systems. +\`configure' configures RT rt-4.0.7 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1364,7 +1372,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of RT rt-4.0.6:";; + short | recursive ) echo "Configuration of RT rt-4.0.7:";; esac cat <<\_ACEOF @@ -1488,8 +1496,8 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -RT configure rt-4.0.6 -generated by GNU Autoconf 2.67 +RT configure rt-4.0.7 +generated by GNU Autoconf 2.68 Copyright (C) 2010 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation @@ -1535,7 +1543,7 @@ sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi - eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_compile @@ -1581,7 +1589,7 @@ fi # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo - eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_link @@ -1589,8 +1597,8 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by RT $as_me rt-4.0.6, which was -generated by GNU Autoconf 2.67. Invocation command line was +It was created by RT $as_me rt-4.0.7, which was +generated by GNU Autoconf 2.68. Invocation command line was $ $0 $@ @@ -1848,7 +1856,7 @@ $as_echo "$as_me: loading site script $ac_site_file" >&6;} || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file -See \`config.log' for more details" "$LINENO" 5 ; } +See \`config.log' for more details" "$LINENO" 5; } fi done @@ -1946,7 +1954,7 @@ rt_version_major=4 rt_version_minor=0 -rt_version_patch=6 +rt_version_patch=7 test "x$rt_version_major" = 'x' && rt_version_major=0 test "x$rt_version_minor" = 'x' && rt_version_minor=0 @@ -1998,7 +2006,7 @@ ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 $as_echo_n "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then -if test "${ac_cv_path_install+set}" = set; then : +if ${ac_cv_path_install+:} false; then : $as_echo_n "(cached) " >&6 else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -2079,7 +2087,7 @@ test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' set dummy perl; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_path_PERL+set}" = set; then : +if ${ac_cv_path_PERL+:} false; then : $as_echo_n "(cached) " >&6 else case $PERL in @@ -2800,7 +2808,7 @@ if test -n "$ac_tool_prefix"; then set dummy ${ac_tool_prefix}gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_CC+set}" = set; then : +if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then @@ -2840,7 +2848,7 @@ if test -z "$ac_cv_prog_CC"; then set dummy gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_ac_ct_CC+set}" = set; then : +if ${ac_cv_prog_ac_ct_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then @@ -2893,7 +2901,7 @@ if test -z "$CC"; then set dummy ${ac_tool_prefix}cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_CC+set}" = set; then : +if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then @@ -2933,7 +2941,7 @@ if test -z "$CC"; then set dummy cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_CC+set}" = set; then : +if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then @@ -2992,7 +3000,7 @@ if test -z "$CC"; then set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_CC+set}" = set; then : +if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then @@ -3036,7 +3044,7 @@ do set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_ac_ct_CC+set}" = set; then : +if ${ac_cv_prog_ac_ct_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then @@ -3091,7 +3099,7 @@ fi test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH -See \`config.log' for more details" "$LINENO" 5 ; } +See \`config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 @@ -3206,7 +3214,7 @@ sed 's/^/| /' conftest.$ac_ext >&5 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables -See \`config.log' for more details" "$LINENO" 5 ; } +See \`config.log' for more details" "$LINENO" 5; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } @@ -3249,7 +3257,7 @@ else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link -See \`config.log' for more details" "$LINENO" 5 ; } +See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest conftest$ac_cv_exeext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 @@ -3308,7 +3316,7 @@ $as_echo "$ac_try_echo"; } >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run C compiled programs. If you meant to cross compile, use \`--host'. -See \`config.log' for more details" "$LINENO" 5 ; } +See \`config.log' for more details" "$LINENO" 5; } fi fi fi @@ -3319,7 +3327,7 @@ rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out ac_clean_files=$ac_clean_files_save { $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 $as_echo_n "checking for suffix of object files... " >&6; } -if test "${ac_cv_objext+set}" = set; then : +if ${ac_cv_objext+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext @@ -3360,7 +3368,7 @@ sed 's/^/| /' conftest.$ac_ext >&5 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile -See \`config.log' for more details" "$LINENO" 5 ; } +See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest.$ac_cv_objext conftest.$ac_ext fi @@ -3370,7 +3378,7 @@ OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 $as_echo_n "checking whether we are using the GNU C compiler... " >&6; } -if test "${ac_cv_c_compiler_gnu+set}" = set; then : +if ${ac_cv_c_compiler_gnu+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext @@ -3407,7 +3415,7 @@ ac_test_CFLAGS=${CFLAGS+set} ac_save_CFLAGS=$CFLAGS { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 $as_echo_n "checking whether $CC accepts -g... " >&6; } -if test "${ac_cv_prog_cc_g+set}" = set; then : +if ${ac_cv_prog_cc_g+:} false; then : $as_echo_n "(cached) " >&6 else ac_save_c_werror_flag=$ac_c_werror_flag @@ -3485,7 +3493,7 @@ else fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 $as_echo_n "checking for $CC option to accept ISO C89... " >&6; } -if test "${ac_cv_prog_cc_c89+set}" = set; then : +if ${ac_cv_prog_cc_c89+:} false; then : $as_echo_n "(cached) " >&6 else ac_cv_prog_cc_c89=no @@ -3583,7 +3591,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu { $as_echo "$as_me:${as_lineno-$LINENO}: checking for aginitlib in -lgraph" >&5 $as_echo_n "checking for aginitlib in -lgraph... " >&6; } -if test "${ac_cv_lib_graph_aginitlib+set}" = set; then : +if ${ac_cv_lib_graph_aginitlib+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS @@ -3617,7 +3625,7 @@ LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_graph_aginitlib" >&5 $as_echo "$ac_cv_lib_graph_aginitlib" >&6; } -if test "x$ac_cv_lib_graph_aginitlib" = x""yes; then : +if test "x$ac_cv_lib_graph_aginitlib" = xyes; then : RT_GRAPHVIZ="1" fi @@ -3643,7 +3651,7 @@ fi set dummy gdlib-config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_RT_GD+set}" = set; then : +if ${ac_cv_prog_RT_GD+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$RT_GD"; then @@ -3699,7 +3707,7 @@ fi set dummy gpg; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_prog_RT_GPG+set}" = set; then : +if ${ac_cv_prog_RT_GPG+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$RT_GPG"; then @@ -3984,10 +3992,21 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then - test "x$cache_file" != "x/dev/null" && + if test "x$cache_file" != "x/dev/null"; then { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 $as_echo "$as_me: updating cache $cache_file" >&6;} - cat confcache >$cache_file + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi else { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} @@ -4055,7 +4074,7 @@ LTLIBOBJS=$ac_ltlibobjs -: ${CONFIG_STATUS=./config.status} +: "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" @@ -4156,6 +4175,7 @@ fi IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. +as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -4462,8 +4482,8 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by RT $as_me rt-4.0.6, which was -generated by GNU Autoconf 2.67. Invocation command line was +This file was extended by RT $as_me rt-4.0.7, which was +generated by GNU Autoconf 2.68. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS @@ -4515,8 +4535,8 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -RT config.status rt-4.0.6 -configured by $0, generated by GNU Autoconf 2.67, +RT config.status rt-4.0.7 +configured by $0, generated by GNU Autoconf 2.68, with options \\"\$ac_cs_config\\" Copyright (C) 2010 Free Software Foundation, Inc. @@ -4658,7 +4678,7 @@ do "t/data/configs/apache2.2+mod_perl.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.2+mod_perl.conf" ;; "t/data/configs/apache2.2+fastcgi.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.2+fastcgi.conf" ;; - *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5 ;; + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done @@ -4679,9 +4699,10 @@ fi # after its creation but before its name has been assigned to `$tmp'. $debug || { - tmp= + tmp= ac_tmp= trap 'exit_status=$? - { test -z "$tmp" || test ! -d "$tmp" || rm -fr "$tmp"; } && exit $exit_status + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } @@ -4689,12 +4710,13 @@ $debug || { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && - test -n "$tmp" && test -d "$tmp" + test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. @@ -4716,7 +4738,7 @@ else ac_cs_awk_cr=$ac_cr fi -echo 'BEGIN {' >"$tmp/subs1.awk" && +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF @@ -4744,7 +4766,7 @@ done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -cat >>"\$tmp/subs1.awk" <<\\_ACAWK && +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h @@ -4792,7 +4814,7 @@ t delim rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK -cat >>"\$tmp/subs1.awk" <<_ACAWK && +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" @@ -4824,7 +4846,7 @@ if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat -fi < "$tmp/subs1.awk" > "$tmp/subs.awk" \ +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF @@ -4864,7 +4886,7 @@ do esac case $ac_mode$ac_tag in :[FHL]*:*);; - :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5 ;; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac @@ -4883,7 +4905,7 @@ do for ac_f do case $ac_f in - -) ac_f="$tmp/stdin";; + -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. @@ -4892,7 +4914,7 @@ do [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || - as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5 ;; + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" @@ -4918,8 +4940,8 @@ $as_echo "$as_me: creating $ac_file" >&6;} esac case $ac_tag in - *:-:* | *:-) cat >"$tmp/stdin" \ - || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac @@ -5049,21 +5071,22 @@ s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t $ac_datarootdir_hack " -eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$tmp/subs.awk" >$tmp/out \ - || as_fn_error $? "could not create $ac_file" "$LINENO" 5 +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && - { ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } && - { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 $as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} - rm -f "$tmp/stdin" + rm -f "$ac_tmp/stdin" case $ac_file in - -) cat "$tmp/out" && rm -f "$tmp/out";; - *) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";; + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; diff --git a/rt/docs/web_deployment.pod b/rt/docs/web_deployment.pod index 4c3f73fb5..5d2cd4c00 100644 --- a/rt/docs/web_deployment.pod +++ b/rt/docs/web_deployment.pod @@ -23,7 +23,7 @@ to use L<Starman>, a high performance preforking server: /opt/rt4/sbin/rt-server --server Starman --port 8080 B<NOTICE>: After you run the standalone server as root, you will need to -remove your C<var/mason> directory, or the non-standalone servers +remove your C<var/mason_data> directory, or the non-standalone servers (Apache, etc), which run as a non-privileged user, will not be able to write to it and will not work. diff --git a/rt/etc/RT_Config.pm.in b/rt/etc/RT_Config.pm.in index bd48b6efd..169182033 100644 --- a/rt/etc/RT_Config.pm.in +++ b/rt/etc/RT_Config.pm.in @@ -640,6 +640,9 @@ Set($NotifyActor, 0); By default, RT records each message it sends out to its own internal database. To change this behavior, set C<$RecordOutgoingEmail> to 0 +If this is disabled, users' digest mail delivery preferences +(i.e. EmailFrequency) will also be ignored. + =cut Set($RecordOutgoingEmail, 1); @@ -897,8 +900,8 @@ Set(@JSFiles, qw/ jquery-1.4.2.min.js jquery_noconflict.js jquery-ui-1.8.4.custom.min.js + jquery-ui-timepicker-addon.js jquery-ui-patch-datepicker.js - ui.timepickr.js titlebox-state.js util.js userautocomplete.js @@ -1826,6 +1829,16 @@ If the "RT has detected a possible cross-site request forgery" error is triggere 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. +Simple wildcards, similar to SSL certificates, are allowed. For example: + + *.example.com:80 # matches foo.example.com + # but not example.com + # or foo.bar.example.com + + www*.example.com:80 # matches www3.example.com + # and www-test.example.com + # and www.example.com + =cut Set(@ReferrerWhitelist, qw()); @@ -2279,10 +2292,11 @@ all possible transitions in each lifecycle using the following format: =head3 Statuses available during ticket creation -By default users can create tickets with any status, except -deleted. If you want to restrict statuses available during creation -then describe transition from '' (empty string), like in the example -above. +By default users can create tickets with a status of new, +open, or resolved, but cannot create tickets with a status of +rejected, stalled, or deleted. If you want to change the statuses +available during creation, update the transition from '' (empty +string), like in the example above. =head3 Protecting status changes with rights diff --git a/rt/etc/RT_SiteConfig.pm b/rt/etc/RT_SiteConfig.pm index 29a7d0231..4a397fa16 100644 --- a/rt/etc/RT_SiteConfig.pm +++ b/rt/etc/RT_SiteConfig.pm @@ -49,7 +49,7 @@ Set($MessageBoxWidth, 80); Set($MessageBoxRichTextHeight, 368); #redirects to ticket display on quick create -#Set($QuickCreateRedirect, 1); +#Set($DisplayTicketAfterQuickCreate, 1); #Set(@Plugins,(qw(Extension::QuickDelete RT::FM))); diff --git a/rt/etc/initialdata b/rt/etc/initialdata index cc07cec59..7ab746db1 100644 --- a/rt/etc/initialdata +++ b/rt/etc/initialdata @@ -1,4 +1,4 @@ -# Initial data for a fresh RT3 Installation. +# Initial data for a fresh RT installation. @Users = ( { Name => 'root', diff --git a/rt/etc/schema.SQLite b/rt/etc/schema.SQLite index 138971cfb..6897be2d6 100644 --- a/rt/etc/schema.SQLite +++ b/rt/etc/schema.SQLite @@ -3,7 +3,7 @@ CREATE TABLE Attachments ( id INTEGER PRIMARY KEY , TransactionId INTEGER , - Parent integer NULL , + Parent integer NULL DEFAULT 0 , MessageId varchar(160) NULL , Subject varchar(255) NULL , Filename varchar(255) NULL , @@ -11,7 +11,7 @@ CREATE TABLE Attachments ( ContentEncoding varchar(80) NULL , Content LONGTEXT NULL , Headers LONGTEXT NULL , - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL ) ; @@ -30,12 +30,12 @@ CREATE TABLE Queues ( CommentAddress varchar(120) NULL , Lifecycle varchar(32) NULL , SubjectTag varchar(120) NULL , - InitialPriority integer NULL , - FinalPriority integer NULL , - DefaultDueIn integer NULL , - Creator integer NULL , + InitialPriority integer NULL DEFAULT 0 , + FinalPriority integer NULL DEFAULT 0 , + DefaultDueIn integer NULL DEFAULT 0 , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL , Disabled int2 NOT NULL DEFAULT 0 @@ -51,11 +51,11 @@ CREATE TABLE Links ( Base varchar(240) NULL , Target varchar(240) NULL , Type varchar(20) NOT NULL , - LocalTarget integer NULL , - LocalBase integer NULL , - LastUpdatedBy integer NULL , + LocalTarget integer NULL DEFAULT 0 , + LocalBase integer NULL DEFAULT 0 , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL , - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL ) ; @@ -106,9 +106,9 @@ CREATE TABLE ScripConditions ( Argument varchar(255) NULL , ApplicableTransTypes varchar(60) NULL , - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL ) ; @@ -119,8 +119,8 @@ CREATE TABLE ScripConditions ( CREATE TABLE Transactions ( id INTEGER PRIMARY KEY , ObjectType varchar(255) NULL , - ObjectId integer NULL , - TimeTaken integer NULL , + ObjectId integer NULL DEFAULT 0 , + TimeTaken integer NULL DEFAULT 0 , Type varchar(20) NULL , Field varchar(40) NULL , OldValue varchar(255) NULL , @@ -130,7 +130,7 @@ CREATE TABLE Transactions ( NewReference integer NULL , Data varchar(255) NULL , - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL ) ; @@ -143,19 +143,19 @@ CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId); CREATE TABLE Scrips ( id INTEGER PRIMARY KEY , Description varchar(255), - ScripCondition integer NULL , - ScripAction integer NULL , + ScripCondition integer NULL DEFAULT 0 , + ScripAction integer NULL DEFAULT 0 , ConditionRules text NULL , ActionRules text NULL , CustomIsApplicableCode text NULL , CustomPrepareCode text NULL , CustomCommitCode text NULL , Stage varchar(32) NULL , - Queue integer NULL , - Template integer NULL , - Creator integer NULL , + Queue integer NULL DEFAULT 0 , + Template integer NULL DEFAULT 0 , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL ) ; @@ -167,7 +167,7 @@ CREATE TABLE ACL ( id INTEGER PRIMARY KEY , PrincipalType varchar(25) NOT NULL, - PrincipalId INTEGER, + PrincipalId INTEGER DEFAULT 0, RightName varchar(25) NOT NULL , ObjectType varchar(25) NOT NULL , ObjectId INTEGER default 0, @@ -185,8 +185,8 @@ CREATE TABLE ACL ( CREATE TABLE GroupMembers ( id INTEGER PRIMARY KEY , - GroupId integer NULL, - MemberId integer NULL, + GroupId integer NULL DEFAULT 0, + MemberId integer NULL DEFAULT 0, Creator integer NOT NULL DEFAULT 0 , Created DATETIME NULL , LastUpdatedBy integer NOT NULL DEFAULT 0 , @@ -250,9 +250,9 @@ CREATE TABLE Users ( Timezone char(50) NULL , PGPKey text NULL, - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL ) ; @@ -270,20 +270,20 @@ CREATE INDEX Users4 ON Users (EmailAddress); CREATE TABLE Tickets ( id INTEGER PRIMARY KEY , - EffectiveId integer NULL , - Queue integer NULL , + EffectiveId integer NULL DEFAULT 0 , + Queue integer NULL DEFAULT 0 , Type varchar(16) NULL , - IssueStatement integer NULL , - Resolution integer NULL , - Owner integer NULL , + IssueStatement integer NULL DEFAULT 0 , + Resolution integer NULL DEFAULT 0 , + Owner integer NULL DEFAULT 0 , Subject varchar(200) NULL DEFAULT '[no subject]' , - InitialPriority integer NULL , - FinalPriority integer NULL , - Priority integer NULL , - TimeEstimated integer NULL , - TimeWorked integer NULL , + InitialPriority integer NULL DEFAULT 0 , + FinalPriority integer NULL DEFAULt 0 , + Priority integer NULL DEFAULT 0 , + TimeEstimated integer NULL DEFAULT 0 , + TimeWorked integer NULL DEFAULT 0 , Status varchar(64) NULL , - TimeLeft integer NULL , + TimeLeft integer NULL DEFAULT 0 , Told DATETIME NULL , Starts DATETIME NULL , Started DATETIME NULL , @@ -291,9 +291,9 @@ CREATE TABLE Tickets ( Resolved DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL , - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , Disabled int2 NOT NULL DEFAULT 0 @@ -315,9 +315,9 @@ CREATE TABLE ScripActions ( Description varchar(255) NULL , ExecModule varchar(60) NULL , Argument varchar(255) NULL , - Creator integer NULL , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL ) ; @@ -333,11 +333,11 @@ CREATE TABLE Templates ( Description varchar(255) NULL , Type varchar(16) NULL , Language varchar(16) NULL , - TranslationOf integer NULL , + TranslationOf integer NULL DEFAULT 0 , Content blob NULL , LastUpdated DATETIME NULL , - LastUpdatedBy integer NULL , - Creator integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL ) ; @@ -437,10 +437,10 @@ CREATE TABLE Attributes ( Content LONGTEXT NULL , ContentType varchar(16), ObjectType varchar(25) NOT NULL , - ObjectId INTEGER default 0, - Creator integer NULL , + ObjectId INTEGER , + Creator integer NULL DEFAULT 0 , Created DATETIME NULL , - LastUpdatedBy integer NULL , + LastUpdatedBy integer NULL DEFAULT 0 , LastUpdated DATETIME NULL ) ; @@ -483,22 +483,22 @@ Parent integer NOT NULL DEFAULT 0, Name varchar(255) NOT NULL DEFAULT '', Description varchar(255) NOT NULL DEFAULT '', ObjectType varchar(64) NOT NULL DEFAULT '', -ObjectId integer NOT NULL +ObjectId integer NOT NULL DEFAULT 0 ); CREATE TABLE ObjectTopics ( id INTEGER PRIMARY KEY, -Topic integer NOT NULL, +Topic integer NOT NULL DEFAULT 0, ObjectType varchar(64) NOT NULL DEFAULT '', -ObjectId integer NOT NULL +ObjectId integer NOT NULL DEFAULT 0 ); CREATE TABLE ObjectClasses ( id INTEGER PRIMARY KEY, -Class integer NOT NULL, +Class integer NOT NULL DEFAULT 0, ObjectType varchar(64) NOT NULL DEFAULT '', -ObjectId integer NOT NULL, +ObjectId integer NOT NULL DEFAULT 0, Creator integer NOT NULL DEFAULT 0, Created TIMESTAMP NULL, LastUpdatedBy integer NOT NULL DEFAULT 0, diff --git a/rt/etc/upgrade/3.3.0/schema.mysql b/rt/etc/upgrade/3.3.0/schema.mysql index f6998363e..d8b04991e 100644 --- a/rt/etc/upgrade/3.3.0/schema.mysql +++ b/rt/etc/upgrade/3.3.0/schema.mysql @@ -1,37 +1,32 @@ -alter Table Transactions ADD Column (ObjectType varchar(64) not null); -update Transactions set ObjectType = 'RT::Ticket'; -alter table Transactions drop column EffectiveTicket; -alter table Transactions add column ReferenceType varchar(255) NULL; -alter table Transactions add column OldReference integer NULL; -alter table Transactions add column NewReference integer NULL; -alter table Transactions drop index transactions1; -alter table Transactions change Ticket ObjectId integer NOT NULL DEFAULT 0 ; +drop index transactions1 ON Transactions; -CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId); - -alter table TicketCustomFieldValues rename ObjectCustomFieldValues; +alter Table Transactions + ADD COLUMN (ObjectType varchar(64) not null), + DROP COLUMN EffectiveTicket, + ADD COLUMN ReferenceType varchar(255) NULL, + ADD COLUMN OldReference integer NULL, + ADD COLUMN NewReference integer NULL, + CHANGE Ticket ObjectId integer NOT NULL DEFAULT 0; -alter table ObjectCustomFieldValues change Ticket ObjectId integer NOT NULL DEFAULT 0 ; +UPDATE Transactions set ObjectType = 'RT::Ticket'; +CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId); -alter table ObjectCustomFieldValues add column ObjectType varchar(255) not null; +alter table TicketCustomFieldValues rename ObjectCustomFieldValues, + change Ticket ObjectId integer NOT NULL DEFAULT 0 , + add column ObjectType varchar(255) not null, + add column Current bool default 1, + add column LargeContent LONGTEXT NULL, + add column ContentType varchar(80) NULL, + add column ContentEncoding varchar(80) NULL; update ObjectCustomFieldValues set ObjectType = 'RT::Ticket'; -alter table ObjectCustomFieldValues add column Current bool default 1; - -alter table ObjectCustomFieldValues add column LargeContent LONGTEXT NULL; - -alter table ObjectCustomFieldValues add column ContentType varchar(80) NULL; - -alter table ObjectCustomFieldValues add column ContentEncoding varchar(80) NULL; - # These could fail if there's no such index and there's no "drop index if exists" syntax #alter table ObjectCustomFieldValues drop index ticketcustomfieldvalues1; #alter table ObjectCustomFieldValues drop index ticketcustomfieldvalues2; -alter table ObjectCustomFieldValues add index ObjectCustomFieldValues1 (Content); - -alter table ObjectCustomFieldValues add index ObjectCustomFieldValues2 (CustomField,ObjectType,ObjectId); +alter table ObjectCustomFieldValues add index ObjectCustomFieldValues1 (Content), + add index ObjectCustomFieldValues2 (CustomField,ObjectType,ObjectId); CREATE TABLE ObjectCustomFields ( @@ -50,10 +45,10 @@ CREATE TABLE ObjectCustomFields ( INSERT into ObjectCustomFields (id, CustomField, ObjectId, SortOrder, Creator, LastUpdatedBy) SELECT null, id, Queue, SortOrder, Creator, LastUpdatedBy from CustomFields; -alter table CustomFields add column LookupType varchar(255) NOT NULL; -alter table CustomFields add column Repeated int2 NOT NULL DEFAULT 0 ; -alter table CustomFields add column Pattern varchar(255) NULL; -alter table CustomFields add column MaxValues integer; +alter table CustomFields add column LookupType varchar(255) NOT NULL, + add column Repeated int2 NOT NULL DEFAULT 0 , + add column Pattern varchar(255) NULL, + add column MaxValues integer; # See above # alter table CustomFields drop index CustomFields1; @@ -62,4 +57,4 @@ UPDATE CustomFields SET MaxValues = 1 WHERE Type LIKE '%Single'; UPDATE CustomFields SET Type = 'Select' WHERE Type LIKE 'Select%'; UPDATE CustomFields SET Type = 'Freeform' WHERE Type LIKE 'Freeform%'; UPDATE CustomFields Set LookupType = 'RT::Queue-RT::Ticket'; -alter table CustomFields drop column Queue; +alter table CustomFields drop column Queue; diff --git a/rt/etc/upgrade/3.3.11/schema.mysql b/rt/etc/upgrade/3.3.11/schema.mysql index cc35d40f2..eff84783f 100644 --- a/rt/etc/upgrade/3.3.11/schema.mysql +++ b/rt/etc/upgrade/3.3.11/schema.mysql @@ -1,5 +1,5 @@ -ALTER TABLE ObjectCustomFieldValues ADD COLUMN SortOrder INTEGER NOT NULL DEFAULT 0; -ALTER TABLE ObjectCustomFieldValues ADD COLUMN Disabled int2 NOT NULL DEFAULT 0; +ALTER TABLE ObjectCustomFieldValues ADD COLUMN SortOrder INTEGER NOT NULL DEFAULT 0, + ADD COLUMN Disabled int2 NOT NULL DEFAULT 0; UPDATE ObjectCustomFieldValues SET Disabled = 1 WHERE Current = 0; ALTER TABLE ObjectCustomFieldValues DROP COLUMN Current; diff --git a/rt/etc/upgrade/3.9.5/schema.mysql b/rt/etc/upgrade/3.9.5/schema.mysql index 4bd0907c0..fe5018c78 100644 --- a/rt/etc/upgrade/3.9.5/schema.mysql +++ b/rt/etc/upgrade/3.9.5/schema.mysql @@ -6,15 +6,15 @@ AND CustomFieldValues.id = Attributes.ObjectId); DELETE FROM Attributes WHERE Name = 'Category' AND ObjectType = 'RT::CustomFieldValue'; -ALTER TABLE Groups ADD COLUMN Creator integer NOT NULL DEFAULT 0; -ALTER TABLE Groups ADD COLUMN Created DATETIME NULL; -ALTER TABLE Groups ADD COLUMN LastUpdatedBy integer NOT NULL DEFAULT 0; -ALTER TABLE Groups ADD COLUMN LastUpdated DATETIME NULL; -ALTER TABLE GroupMembers ADD COLUMN Creator integer NOT NULL DEFAULT 0; -ALTER TABLE GroupMembers ADD COLUMN Created DATETIME NULL; -ALTER TABLE GroupMembers ADD COLUMN LastUpdatedBy integer NOT NULL DEFAULT 0; -ALTER TABLE GroupMembers ADD COLUMN LastUpdated DATETIME NULL; -ALTER TABLE ACL ADD COLUMN Creator integer NOT NULL DEFAULT 0; -ALTER TABLE ACL ADD COLUMN Created DATETIME NULL; -ALTER TABLE ACL ADD COLUMN LastUpdatedBy integer NOT NULL DEFAULT 0; -ALTER TABLE ACL ADD COLUMN LastUpdated DATETIME NULL; +ALTER TABLE Groups ADD COLUMN Creator integer NOT NULL DEFAULT 0, + ADD COLUMN Created DATETIME NULL, + ADD COLUMN LastUpdatedBy integer NOT NULL DEFAULT 0, + ADD COLUMN LastUpdated DATETIME NULL; +ALTER TABLE GroupMembers ADD COLUMN Creator integer NOT NULL DEFAULT 0, + ADD COLUMN Created DATETIME NULL, + ADD COLUMN LastUpdatedBy integer NOT NULL DEFAULT 0, + ADD COLUMN LastUpdated DATETIME NULL; +ALTER TABLE ACL ADD COLUMN Creator integer NOT NULL DEFAULT 0, + ADD COLUMN Created DATETIME NULL, + ADD COLUMN LastUpdatedBy integer NOT NULL DEFAULT 0, + ADD COLUMN LastUpdated DATETIME NULL; diff --git a/rt/etc/upgrade/3.9.7/schema.mysql b/rt/etc/upgrade/3.9.7/schema.mysql index 1be165647..4cbed6cc7 100644 --- a/rt/etc/upgrade/3.9.7/schema.mysql +++ b/rt/etc/upgrade/3.9.7/schema.mysql @@ -1,6 +1,6 @@ ALTER TABLE Users ADD COLUMN AuthToken VARCHAR(16) CHARACTER SET ascii NULL; -ALTER TABLE CustomFields ADD COLUMN BasedOn INTEGER NULL; -ALTER TABLE CustomFields ADD COLUMN RenderType VARCHAR(64) NULL; -ALTER TABLE CustomFields ADD COLUMN ValuesClass VARCHAR(64) CHARACTER SET ascii NULL; -ALTER TABLE Queues ADD COLUMN SubjectTag VARCHAR(120) NULL; -ALTER TABLE Queues ADD COLUMN Lifecycle VARCHAR(32) NULL; +ALTER TABLE CustomFields ADD COLUMN BasedOn INTEGER NULL, + ADD COLUMN RenderType VARCHAR(64) NULL, + ADD COLUMN ValuesClass VARCHAR(64) CHARACTER SET ascii NULL; +ALTER TABLE Queues ADD COLUMN SubjectTag VARCHAR(120) NULL, + ADD COLUMN Lifecycle VARCHAR(32) NULL; diff --git a/rt/lib/RT.pm b/rt/lib/RT.pm index 063f7f719..4372a564d 100644 --- a/rt/lib/RT.pm +++ b/rt/lib/RT.pm @@ -138,6 +138,8 @@ up logging|/InitLogging>, and L<loads plugins|/InitPlugins>. sub Init { + my @arg = @_; + CheckPerlRequirements(); InitPluginPaths(); @@ -146,7 +148,7 @@ sub Init { ConnectToDatabase(); InitSystemObjects(); InitClasses(); - InitLogging(); + InitLogging(@arg); InitPlugins(); RT::I18N->Init; RT->Config->PostLoadCheck; @@ -174,6 +176,8 @@ Create the Logger object and set up signal handlers. sub InitLogging { + my %arg = @_; + # We have to set the record separator ($, man perlvar) # or Log::Dispatch starts getting # really pissy, as some other module we use unsets it. @@ -309,11 +313,14 @@ sub InitLogging { )); } } - InitSignalHandlers(); + InitSignalHandlers(%arg); } sub InitSignalHandlers { + my %arg = @_; + return if $arg{'NoSignalHandlers'}; + # Signal handlers ## This is the default handling of warnings and die'ings in the code ## (including other used modules - maybe except for errors catched by diff --git a/rt/lib/RT.pm.in b/rt/lib/RT.pm.in deleted file mode 100644 index fafd2b778..000000000 --- a/rt/lib/RT.pm.in +++ /dev/null @@ -1,719 +0,0 @@ -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 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 }}} - -use strict; -use warnings; - -package RT; - - -use File::Spec (); -use Cwd (); - -use vars qw($Config $System $SystemUser $Nobody $Handle $Logger $_INSTALL_MODE); - -our $VERSION = '@RT_VERSION_MAJOR@.@RT_VERSION_MINOR@.@RT_VERSION_PATCH@'; - -@DATABASE_ENV_PREF@ - -our $BasePath = '@RT_PATH@'; -our $EtcPath = '@RT_ETC_PATH@'; -our $BinPath = '@RT_BIN_PATH@'; -our $SbinPath = '@RT_SBIN_PATH@'; -our $VarPath = '@RT_VAR_PATH@'; -our $PluginPath = '@RT_PLUGIN_PATH@'; -our $LocalPath = '@RT_LOCAL_PATH@'; -our $LocalEtcPath = '@LOCAL_ETC_PATH@'; -our $LocalLibPath = '@LOCAL_LIB_PATH@'; -our $LocalLexiconPath = '@LOCAL_LEXICON_PATH@'; -our $LocalPluginPath = $LocalPath."/plugins"; - - -# $MasonComponentRoot is where your rt instance keeps its mason html files - -our $MasonComponentRoot = '@MASON_HTML_PATH@'; - -# $MasonLocalComponentRoot is where your rt instance keeps its site-local -# mason html files. - -our $MasonLocalComponentRoot = '@MASON_LOCAL_HTML_PATH@'; - -# $MasonDataDir Where mason keeps its datafiles - -our $MasonDataDir = '@MASON_DATA_PATH@'; - -# RT needs to put session data (for preserving state between connections -# via the web interface) -our $MasonSessionDir = '@MASON_SESSION_PATH@'; - -unless ( File::Spec->file_name_is_absolute($EtcPath) ) { - -# if BasePath exists and is absolute, we won't infer it from $INC{'RT.pm'}. -# otherwise RT.pm will make src dir(where we configure RT) be the BasePath -# instead of the --prefix one - unless ( -d $BasePath && File::Spec->file_name_is_absolute($BasePath) ) { - my $pm_path = ( File::Spec->splitpath( $INC{'RT.pm'} ) )[1]; - - # need rel2abs here is to make sure path is absolute, since $INC{'RT.pm'} - # is not always absolute - $BasePath = - File::Spec->rel2abs( - File::Spec->catdir( $pm_path, File::Spec->updir ) ); - } - - $BasePath = Cwd::realpath( $BasePath ); - - for my $path ( qw/EtcPath BinPath SbinPath VarPath LocalPath LocalEtcPath - LocalLibPath LocalLexiconPath PluginPath LocalPluginPath - MasonComponentRoot MasonLocalComponentRoot MasonDataDir - MasonSessionDir/ ) { - no strict 'refs'; - # just change relative ones - $$path = File::Spec->catfile( $BasePath, $$path ) - unless File::Spec->file_name_is_absolute( $$path ); - } -} - - -=head1 NAME - -RT - Request Tracker - -=head1 SYNOPSIS - -A fully featured request tracker package - -=head1 DESCRIPTION - -=head2 INITIALIZATION - -=head2 LoadConfig - -Load RT's config file. First, the site configuration file -(F<RT_SiteConfig.pm>) is loaded, in order to establish overall site -settings like hostname and name of RT instance. Then, the core -configuration file (F<RT_Config.pm>) is loaded to set fallback values -for all settings; it bases some values on settings from the site -configuration file. - -In order for the core configuration to not override the site's -settings, the function C<Set> is used; it only sets values if they -have not been set already. - -=cut - -sub LoadConfig { - require RT::Config; - $Config = new RT::Config; - $Config->LoadConfigs; - require RT::I18N; - - # RT::Essentials mistakenly recommends that WebPath be set to '/'. - # If the user does that, do what they mean. - $RT::WebPath = '' if ($RT::WebPath eq '/'); - - # fix relative LogDir and GnuPG homedir - unless ( File::Spec->file_name_is_absolute( $Config->Get('LogDir') ) ) { - $Config->Set( LogDir => - File::Spec->catfile( $BasePath, $Config->Get('LogDir') ) ); - } - - my $gpgopts = $Config->Get('GnuPGOptions'); - unless ( File::Spec->file_name_is_absolute( $gpgopts->{homedir} ) ) { - $gpgopts->{homedir} = File::Spec->catfile( $BasePath, $gpgopts->{homedir} ); - } - - RT::I18N->Init; -} - -=head2 Init - -L<Connect to the database /ConnectToDatabase>, L<initilizes system objects /InitSystemObjects>, -L<preloads classes /InitClasses> and L<set up logging /InitLogging>. - -=cut - -sub Init { - - my @arg = @_; - - CheckPerlRequirements(); - - InitPluginPaths(); - - #Get a database connection - ConnectToDatabase(); - InitSystemObjects(); - InitClasses(); - InitLogging(@arg); - InitPlugins(); - RT->Config->PostLoadCheck; - -} - -=head2 ConnectToDatabase - -Get a database connection. See also </Handle>. - -=cut - -sub ConnectToDatabase { - require RT::Handle; - $Handle = new RT::Handle unless $Handle; - $Handle->Connect; - return $Handle; -} - -=head2 InitLogging - -Create the Logger object and set up signal handlers. - -=cut - -sub InitLogging { - - my %arg = @_; - - # We have to set the record separator ($, man perlvar) - # or Log::Dispatch starts getting - # really pissy, as some other module we use unsets it. - $, = ''; - use Log::Dispatch 1.6; - - my %level_to_num = ( - map( { $_ => } 0..7 ), - debug => 0, - info => 1, - notice => 2, - warning => 3, - error => 4, 'err' => 4, - critical => 5, crit => 5, - alert => 6, - emergency => 7, emerg => 7, - ); - - unless ( $RT::Logger ) { - - $RT::Logger = Log::Dispatch->new; - - my $stack_from_level; - if ( $stack_from_level = RT->Config->Get('LogStackTraces') ) { - # if option has old style '\d'(true) value - $stack_from_level = 0 if $stack_from_level =~ /^\d+$/; - $stack_from_level = $level_to_num{ $stack_from_level } || 0; - } else { - $stack_from_level = 99; # don't log - } - - my $simple_cb = sub { - # if this code throw any warning we can get segfault - no warnings; - my %p = @_; - - # skip Log::* stack frames - my $frame = 0; - $frame++ while caller($frame) && caller($frame) =~ /^Log::/; - my ($package, $filename, $line) = caller($frame); - - $p{'message'} =~ s/(?:\r*\n)+$//; - return "[". gmtime(time) ."] [". $p{'level'} ."]: " - . $p{'message'} ." ($filename:$line)\n"; - }; - - my $syslog_cb = sub { - # if this code throw any warning we can get segfault - no warnings; - my %p = @_; - - my $frame = 0; # stack frame index - # skip Log::* stack frames - $frame++ while caller($frame) && caller($frame) =~ /^Log::/; - my ($package, $filename, $line) = caller($frame); - - # syswrite() cannot take utf8; turn it off here. - Encode::_utf8_off($p{message}); - - $p{message} =~ s/(?:\r*\n)+$//; - if ($p{level} eq 'debug') { - return "$p{message}\n"; - } else { - return "$p{message} ($filename:$line)\n"; - } - }; - - my $stack_cb = sub { - no warnings; - my %p = @_; - return $p{'message'} unless $level_to_num{ $p{'level'} } >= $stack_from_level; - - require Devel::StackTrace; - my $trace = Devel::StackTrace->new( ignore_class => [ 'Log::Dispatch', 'Log::Dispatch::Base' ] ); - return $p{'message'} . $trace->as_string; - - # skip calling of the Log::* subroutins - my $frame = 0; - $frame++ while caller($frame) && caller($frame) =~ /^Log::/; - $frame++ while caller($frame) && (caller($frame))[3] =~ /^Log::/; - - $p{'message'} .= "\nStack trace:\n"; - while( my ($package, $filename, $line, $sub) = caller($frame++) ) { - $p{'message'} .= "\t$sub(...) called at $filename:$line\n"; - } - return $p{'message'}; - }; - - if ( $Config->Get('LogToFile') ) { - my ($filename, $logdir) = ( - $Config->Get('LogToFileNamed') || 'rt.log', - $Config->Get('LogDir') || File::Spec->catdir( $VarPath, 'log' ), - ); - if ( $filename =~ m![/\\]! ) { # looks like an absolute path. - ($logdir) = $filename =~ m{^(.*[/\\])}; - } - else { - $filename = File::Spec->catfile( $logdir, $filename ); - } - - unless ( -d $logdir && ( ( -f $filename && -w $filename ) || -w $logdir ) ) { - # localizing here would be hard when we don't have a current user yet - die "Log file '$filename' couldn't be written or created.\n RT can't run."; - } - - require Log::Dispatch::File; - $RT::Logger->add( Log::Dispatch::File->new - ( name=>'file', - min_level=> $Config->Get('LogToFile'), - filename=> $filename, - mode=>'append', - callbacks => [ $simple_cb, $stack_cb ], - )); - } - if ( $Config->Get('LogToScreen') ) { - require Log::Dispatch::Screen; - $RT::Logger->add( Log::Dispatch::Screen->new - ( name => 'screen', - min_level => $Config->Get('LogToScreen'), - callbacks => [ $simple_cb, $stack_cb ], - stderr => 1, - )); - } - if ( $Config->Get('LogToSyslog') ) { - require Log::Dispatch::Syslog; - $RT::Logger->add(Log::Dispatch::Syslog->new - ( name => 'syslog', - ident => 'RT', - min_level => $Config->Get('LogToSyslog'), - callbacks => [ $syslog_cb, $stack_cb ], - stderr => 1, - $Config->Get('LogToSyslogConf'), - )); - } - } - InitSignalHandlers(%arg); -} - -sub InitSignalHandlers { - - my %arg = @_; - -# Signal handlers -## This is the default handling of warnings and die'ings in the code -## (including other used modules - maybe except for errors catched by -## Mason). It will log all problems through the standard logging -## mechanism (see above). - - unless ( $arg{'NoSignalHandlers'} ) { - - $SIG{__WARN__} = sub { - # The 'wide character' warnings has to be silenced for now, at least - # until HTML::Mason offers a sane way to process both raw output and - # unicode strings. - # use 'goto &foo' syntax to hide ANON sub from stack - if( index($_[0], 'Wide character in ') != 0 ) { - unshift @_, $RT::Logger, qw(level warning message); - goto &Log::Dispatch::log; - } - }; - - #When we call die, trap it and log->crit with the value of the die. - - $SIG{__DIE__} = sub { - # if we are not in eval and perl is not parsing code - # then rollback transactions and log RT error - unless ($^S || !defined $^S ) { - $RT::Handle->Rollback(1) if $RT::Handle; - $RT::Logger->crit("$_[0]") if $RT::Logger; - } - die $_[0]; - }; - - } -} - - -sub CheckPerlRequirements { - if ($^V < 5.008003) { - die sprintf "RT requires Perl v5.8.3 or newer. Your current Perl is v%vd\n", $^V; - } - - # use $error here so the following "die" can still affect the global $@ - my $error; - { - local $@; - eval { - my $x = ''; - my $y = \$x; - require Scalar::Util; - Scalar::Util::weaken($y); - }; - $error = $@; - } - - if ($error) { - die <<"EOF"; - -RT requires the Scalar::Util module be built with support for the 'weaken' -function. - -It is sometimes the case that operating system upgrades will replace -a working Scalar::Util with a non-working one. If your system was working -correctly up until now, this is likely the cause of the problem. - -Please reinstall Scalar::Util, being careful to let it build with your C -compiler. Ususally this is as simple as running the following command as -root. - - perl -MCPAN -e'install Scalar::Util' - -EOF - - } -} - -=head2 InitClasses - -Load all modules that define base classes. - -=cut - -sub InitClasses { - shift if @_%2; # so we can call it as a function or method - my %args = (@_); - require RT::Tickets; - require RT::Transactions; - require RT::Attachments; - require RT::Users; - require RT::Principals; - require RT::CurrentUser; - require RT::Templates; - require RT::Queues; - require RT::ScripActions; - require RT::ScripConditions; - require RT::Scrips; - require RT::Groups; - require RT::GroupMembers; - require RT::CustomFields; - require RT::CustomFieldValues; - require RT::ObjectCustomFields; - require RT::ObjectCustomFieldValues; - require RT::Attributes; - require RT::Dashboard; - require RT::Approval; - - # on a cold server (just after restart) people could have an object - # in the session, as we deserialize it so we never call constructor - # of the class, so the list of accessible fields is empty and we die - # with "Method xxx is not implemented in RT::SomeClass" - $_->_BuildTableAttributes foreach qw( - RT::Ticket - RT::Transaction - RT::Attachment - RT::User - RT::Principal - RT::Template - RT::Queue - RT::ScripAction - RT::ScripCondition - RT::Scrip - RT::Group - RT::GroupMember - RT::CustomField - RT::CustomFieldValue - RT::ObjectCustomField - RT::ObjectCustomFieldValue - RT::Attribute - ); - - if ( $args{'Heavy'} ) { - # load scrips' modules - my $scrips = RT::Scrips->new($RT::SystemUser); - $scrips->Limit( FIELD => 'Stage', OPERATOR => '!=', VALUE => 'Disabled' ); - while ( my $scrip = $scrips->Next ) { - local $@; - eval { $scrip->LoadModules } or - $RT::Logger->error("Invalid Scrip ".$scrip->Id.". Unable to load the Action or Condition. ". - "You should delete or repair this Scrip in the admin UI.\n$@\n"); - } - - foreach my $class ( grep $_, RT->Config->Get('CustomFieldValuesSources') ) { - local $@; - eval "require $class; 1" or $RT::Logger->error( - "Class '$class' is listed in CustomFieldValuesSources option" - ." in the config, but we failed to load it:\n$@\n" - ); - } - - RT::I18N->LoadLexicons; - } -} - -=head2 InitSystemObjects - -Initializes system objects: C<$RT::System>, C<$RT::SystemUser> -and C<$RT::Nobody>. - -=cut - -sub InitSystemObjects { - - #RT's system user is a genuine database user. its id lives here - require RT::CurrentUser; - $SystemUser = new RT::CurrentUser; - $SystemUser->LoadByName('RT_System'); - - #RT's "nobody user" is a genuine database user. its ID lives here. - $Nobody = new RT::CurrentUser; - $Nobody->LoadByName('Nobody'); - - require RT::System; - $System = RT::System->new( $SystemUser ); -} - -=head1 CLASS METHODS - -=head2 Config - -Returns the current L<config object RT::Config>, but note that -you must L<load config /LoadConfig> first otherwise this method -returns undef. - -Method can be called as class method. - -=cut - -sub Config { return $Config } - -=head2 DatabaseHandle - -Returns the current L<database handle object RT::Handle>. - -See also L</ConnectToDatabase>. - -=cut - -sub DatabaseHandle { return $Handle } - -=head2 Logger - -Returns the logger. See also L</InitLogging>. - -=cut - -sub Logger { return $Logger } - -=head2 System - -Returns the current L<system object RT::System>. See also -L</InitSystemObjects>. - -=cut - -sub System { return $System } - -=head2 SystemUser - -Returns the system user's object, it's object of -L<RT::CurrentUser> class that represents the system. See also -L</InitSystemObjects>. - -=cut - -sub SystemUser { return $SystemUser } - -=head2 Nobody - -Returns object of Nobody. It's object of L<RT::CurrentUser> class -that represents a user who can own ticket and nothing else. See -also L</InitSystemObjects>. - -=cut - -sub Nobody { return $Nobody } - -=head2 Plugins - -Returns a listref of all Plugins currently configured for this RT instance. -You can define plugins by adding them to the @Plugins list in your RT_SiteConfig - -=cut - -our @PLUGINS = (); -sub Plugins { - my $self = shift; - unless (@PLUGINS) { - $self->InitPluginPaths; - @PLUGINS = $self->InitPlugins; - } - return \@PLUGINS; -} - -=head2 PluginDirs - -Takes optional subdir (e.g. po, lib, etc.) and return plugins' dirs that exist. - -This code chacke plugins names or anything else and required when main config -is loaded to load plugins' configs. - -=cut - -sub PluginDirs { - my $self = shift; - my $subdir = shift; - - require RT::Plugin; - - my @res; - foreach my $plugin (grep $_, RT->Config->Get('Plugins')) { - my $path = RT::Plugin->new( name => $plugin )->Path( $subdir ); - next unless -d $path; - push @res, $path; - } - return @res; -} - -=head2 InitPluginPaths - -Push plugins' lib paths into @INC right after F<local/lib>. -In case F<local/lib> isn't in @INC, append them to @INC - -=cut - -sub InitPluginPaths { - my $self = shift || __PACKAGE__; - - my @lib_dirs = $self->PluginDirs('lib'); - - my @tmp_inc; - my $added; - for (@INC) { - if ( Cwd::realpath($_) eq $RT::LocalLibPath) { - push @tmp_inc, $_, @lib_dirs; - $added = 1; - } else { - push @tmp_inc, $_; - } - } - - # append @lib_dirs in case $RT::LocalLibPath isn't in @INC - push @tmp_inc, @lib_dirs unless $added; - - my %seen; - @INC = grep !$seen{$_}++, @tmp_inc; -} - -=head2 InitPlugins - -Initialze all Plugins found in the RT configuration file, setting up their lib and HTML::Mason component roots. - -=cut - -sub InitPlugins { - my $self = shift; - my @plugins; - require RT::Plugin; - foreach my $plugin (grep $_, RT->Config->Get('Plugins')) { - $plugin->require; - die $UNIVERSAL::require::ERROR if ($UNIVERSAL::require::ERROR); - push @plugins, RT::Plugin->new(name =>$plugin); - } - return @plugins; -} - - -sub InstallMode { - my $self = shift; - if (@_) { - $_INSTALL_MODE = shift; - if($_INSTALL_MODE) { - require RT::CurrentUser; - $SystemUser = RT::CurrentUser->new(); - } - } - return $_INSTALL_MODE; -} - - -=head1 BUGS - -Please report them to rt-bugs@bestpractical.com, if you know what's -broken and have at least some idea of what needs to be fixed. - -If you're not sure what's going on, report them rt-devel@lists.bestpractical.com. - -=head1 SEE ALSO - -L<RT::StyleGuide> -L<DBIx::SearchBuilder> - - -=cut - -require RT::Base; -RT::Base->_ImportOverlays(); - -1; diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm index 31489c8ff..efd2bdaf6 100644 --- a/rt/lib/RT/Action/CreateTickets.pm +++ b/rt/lib/RT/Action/CreateTickets.pm @@ -567,7 +567,8 @@ sub Parse { $self->_ParseMultilineTemplate(%args); } elsif ( $args{'Content'} =~ /(?:\t|,)/i ) { $self->_ParseXSVTemplate(%args); - + } else { + RT->Logger->error("Invalid Template Content (Couldn't find ===, and is not a csv/tsv template) - unable to parse: $args{Content}"); } } diff --git a/rt/lib/RT/Articles.pm b/rt/lib/RT/Articles.pm index 8dd661d2e..47d0ebea2 100644 --- a/rt/lib/RT/Articles.pm +++ b/rt/lib/RT/Articles.pm @@ -360,6 +360,7 @@ sub LimitCustomField { QUOTEVALUE => $args{'QUOTEVALUE'}, ENTRYAGGREGATOR => 'AND', #$args{'ENTRYAGGREGATOR'}, SUBCLAUSE => $clause, + CASESENSITIVE => 0, ); $self->SUPER::Limit( ALIAS => $ObjectValuesAlias, @@ -380,6 +381,7 @@ sub LimitCustomField { QUOTEVALUE => $args{'QUOTEVALUE'}, ENTRYAGGREGATOR => $args{'ENTRYAGGREGATOR'}, SUBCLAUSE => $clause, + CASESENSITIVE => 0, ); $self->SUPER::Limit( ALIAS => $ObjectValuesAlias, @@ -389,6 +391,7 @@ sub LimitCustomField { QUOTEVALUE => $args{'QUOTEVALUE'}, ENTRYAGGREGATOR => $args{'ENTRYAGGREGATOR'}, SUBCLAUSE => $clause, + CASESENSITIVE => 0, ); } } diff --git a/rt/lib/RT/Config.pm b/rt/lib/RT/Config.pm index f87ef84c9..014c76468 100644 --- a/rt/lib/RT/Config.pm +++ b/rt/lib/RT/Config.pm @@ -411,8 +411,8 @@ our %META = ( Description => q|What tickets to display in the 'More about requestor' box|, #loc Values => [qw(Active Inactive All None)], ValuesLabel => { - Active => "Show the Requestor's 10 highest priority open tickets", #loc - Inactive => "Show the Requestor's 10 highest priority closed tickets", #loc + Active => "Show the Requestor's 10 highest priority active tickets", #loc + Inactive => "Show the Requestor's 10 highest priority inactive tickets", #loc All => "Show the Requestor's 10 highest priority tickets", #loc None => "Show no tickets for the Requestor", #loc }, @@ -749,7 +749,7 @@ our %META = ( my %seen; foreach my $encoding ( grep defined && length, splice @$value ) { - next if $seen{ $encoding }++; + next if $seen{ $encoding }; if ( $encoding eq '*' ) { unshift @$value, '*'; next; diff --git a/rt/lib/RT/Crypt/GnuPG.pm b/rt/lib/RT/Crypt/GnuPG.pm index ab444d068..c5fb12bef 100644 --- a/rt/lib/RT/Crypt/GnuPG.pm +++ b/rt/lib/RT/Crypt/GnuPG.pm @@ -1683,6 +1683,7 @@ my %ignore_keyword = map { $_ => 1 } qw( BEGIN_ENCRYPTION SIG_ID VALIDSIG ENC_TO BEGIN_DECRYPTION END_DECRYPTION GOODMDC TRUST_UNDEFINED TRUST_NEVER TRUST_MARGINAL TRUST_FULLY TRUST_ULTIMATE + DECRYPTION_INFO ); sub ParseStatus { diff --git a/rt/lib/RT/Dashboard.pm b/rt/lib/RT/Dashboard.pm index 14ffa6ad3..2e2bbc489 100644 --- a/rt/lib/RT/Dashboard.pm +++ b/rt/lib/RT/Dashboard.pm @@ -454,6 +454,36 @@ sub CurrentUserCanCreateAny { return 0; } +=head2 Delete + +Deletes the dashboard and related subscriptions. +Returns a tuple of status and message, where status is true upon success. + +=cut + +sub Delete { + my $self = shift; + my $id = $self->id; + my ( $status, $msg ) = $self->SUPER::Delete(@_); + if ( $status ) { + # delete all the subscriptions + my $subscriptions = RT::Attributes->new( RT->SystemUser ); + $subscriptions->Limit( + FIELD => 'Name', + VALUE => 'Subscription', + ); + $subscriptions->Limit( + FIELD => 'Description', + VALUE => "Subscription to dashboard $id", + ); + while ( my $subscription = $subscriptions->Next ) { + $subscription->Delete(); + } + } + + return ( $status, $msg ); +} + RT::Base->_ImportOverlays(); 1; diff --git a/rt/lib/RT/Generated.pm b/rt/lib/RT/Generated.pm index 2abcf3b6e..9fd946f5b 100644 --- a/rt/lib/RT/Generated.pm +++ b/rt/lib/RT/Generated.pm @@ -50,7 +50,7 @@ package RT; use warnings; use strict; -our $VERSION = '4.0.6'; +our $VERSION = '4.0.7'; diff --git a/rt/lib/RT/I18N.pm b/rt/lib/RT/I18N.pm index cadf7cc7c..e453cfa04 100644 --- a/rt/lib/RT/I18N.pm +++ b/rt/lib/RT/I18N.pm @@ -227,7 +227,7 @@ sub SetMIMEEntityToEncoding { my $body = $entity->bodyhandle; - if ( $enc ne $charset && $body ) { + if ( $body && ($enc ne $charset || $enc =~ /^utf-?8(?:-strict)?$/i) ) { my $string = $body->as_string or return; $RT::Logger->debug( "Converting '$charset' to '$enc' for " @@ -335,7 +335,7 @@ sub DecodeMIMEWordsToEncoding { } # now we have got a decoded subject, try to convert into the encoding - unless ( $charset eq $to_charset ) { + if ( $charset ne $to_charset || $charset =~ /^utf-?8(?:-strict)?$/i ) { Encode::from_to( $enc_str, $charset, $to_charset ); } @@ -537,7 +537,7 @@ sub SetMIMEHeadToEncoding { my @values = $head->get_all($tag); $head->delete($tag); foreach my $value (@values) { - if ( $charset ne $enc ) { + if ( $charset ne $enc || $enc =~ /^utf-?8(?:-strict)?$/i ) { Encode::_utf8_off($value); Encode::from_to( $value, $charset => $enc ); } diff --git a/rt/lib/RT/Interface/Email.pm b/rt/lib/RT/Interface/Email.pm index 02a1ec0c0..4c3ee9986 100755 --- a/rt/lib/RT/Interface/Email.pm +++ b/rt/lib/RT/Interface/Email.pm @@ -787,7 +787,7 @@ sub GetForwardFrom { my $ticket = $args{Ticket} || $txn->Object; if ( RT->Config->Get('ForwardFromUser') ) { - return ( $txn || $ticket )->CurrentUser->UserObj->EmailAddress; + return ( $txn || $ticket )->CurrentUser->EmailAddress; } else { return $ticket->QueueObj->CorrespondAddress @@ -1221,8 +1221,16 @@ sub SetInReplyTo { if @references > 10; my $mail = $args{'Message'}; - $mail->head->set( 'In-Reply-To' => join ' ', @rtid? (@rtid) : (@id) ) if @id || @rtid; - $mail->head->set( 'References' => join ' ', @references ); + $mail->head->set( 'In-Reply-To' => Encode::encode_utf8(join ' ', @rtid? (@rtid) : (@id)) ) if @id || @rtid; + $mail->head->set( 'References' => Encode::encode_utf8(join ' ', @references) ); +} + +sub ExtractTicketId { + my $entity = shift; + + my $subject = $entity->head->get('Subject') || ''; + chomp $subject; + return ParseTicketId( $subject ); } sub ParseTicketId { @@ -1448,7 +1456,7 @@ sub Gateway { } # }}} - $args{'ticket'} ||= ParseTicketId( $Subject ); + $args{'ticket'} ||= ExtractTicketId( $Message ); $SystemTicket = RT::Ticket->new( RT->SystemUser ); $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ; @@ -1704,17 +1712,20 @@ sub _RunUnsafeAction { return ( 0, "Ticket not taken" ); } } elsif ( $args{'Action'} =~ /^resolve$/i ) { - my ( $status, $msg ) = $args{'Ticket'}->SetStatus('resolved'); - unless ($status) { + my $new_status = $args{'Ticket'}->FirstInactiveStatus; + if ($new_status) { + my ( $status, $msg ) = $args{'Ticket'}->SetStatus($new_status); + unless ($status) { - #Warn the sender that we couldn't actually submit the comment. - MailError( - To => $args{'ErrorsTo'}, - Subject => "Ticket not resolved", - Explanation => $msg, - MIMEObj => $args{'Message'} - ); - return ( 0, "Ticket not resolved" ); + #Warn the sender that we couldn't actually submit the comment. + MailError( + To => $args{'ErrorsTo'}, + Subject => "Ticket not resolved", + Explanation => $msg, + MIMEObj => $args{'Message'} + ); + return ( 0, "Ticket not resolved" ); + } } } else { return ( 0, "Not supported unsafe action $args{'Action'}", $args{'Ticket'} ); diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm index 94da3072d..1aae7581e 100644 --- a/rt/lib/RT/Interface/Web.pm +++ b/rt/lib/RT/Interface/Web.pm @@ -261,7 +261,15 @@ sub HandleRequest { $HTML::Mason::Commands::m->comp( '/Elements/SetupSessionCookie', %$ARGS ); SendSessionCookie(); - $HTML::Mason::Commands::session{'CurrentUser'} = RT::CurrentUser->new() unless _UserLoggedIn(); + + if ( _UserLoggedIn() ) { + # make user info up to date + $HTML::Mason::Commands::session{'CurrentUser'} + ->Load( $HTML::Mason::Commands::session{'CurrentUser'}->id ); + } + else { + $HTML::Mason::Commands::session{'CurrentUser'} = RT::CurrentUser->new(); + } # Process session-related callbacks before any auth attempts $HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Session', CallbackPage => '/autohandler' ); @@ -287,7 +295,7 @@ sub HandleRequest { my $m = $HTML::Mason::Commands::m; # REST urls get a special 401 response - if ($m->request_comp->path =~ '^/REST/\d+\.\d+/') { + if ($m->request_comp->path =~ m{^/REST/\d+\.\d+/}) { $HTML::Mason::Commands::r->content_type("text/plain"); $m->error_format("text"); $m->out("RT/$RT::VERSION 401 Credentials required\n"); @@ -457,7 +465,7 @@ sub MaybeShowInstallModePage { my $m = $HTML::Mason::Commands::m; if ( $m->base_comp->path =~ RT->Config->Get('WebNoAuthRegex') ) { $m->call_next(); - } elsif ( $m->request_comp->path !~ '^(/+)Install/' ) { + } elsif ( $m->request_comp->path !~ m{^(/+)Install/} ) { RT::Interface::Web::Redirect( RT->Config->Get('WebURL') . "Install/index.html" ); } else { $m->call_next(); @@ -557,7 +565,7 @@ sub ShowRequestedPage { unless ( $HTML::Mason::Commands::session{'CurrentUser'}->Privileged ) { # if the user is trying to access a ticket, redirect them - if ( $m->request_comp->path =~ '^(/+)Ticket/Display.html' && $ARGS->{'id'} ) { + if ( $m->request_comp->path =~ m{^(/+)Ticket/Display.html} && $ARGS->{'id'} ) { RT::Interface::Web::Redirect( RT->Config->Get('WebURL') . "SelfService/Display.html?id=" . $ARGS->{'id'} ); } @@ -659,7 +667,7 @@ sub AttemptExternalAuth { delete $HTML::Mason::Commands::session{'CurrentUser'}; $user = $orig_user; - if ( RT->Config->Get('WebExternalOnly') ) { + unless ( RT->Config->Get('WebFallbackToInternalAuth') ) { TangentForLoginWithError('You are not an authorized user'); } } @@ -970,7 +978,7 @@ sub MobileClient { my $self = shift; -if (($ENV{'HTTP_USER_AGENT'} || '') =~ /(?:hiptop|Blazer|Novarra|Vagabond|SonyEricsson|Symbian|NetFront|UP.Browser|UP.Link|Windows CE|MIDP|J2ME|DoCoMo|J-PHONE|PalmOS|PalmSource|iPhone|iPod|AvantGo|Nokia|Android|WebOS|S60)/io && !$HTML::Mason::Commands::session{'NotMobile'}) { +if (($ENV{'HTTP_USER_AGENT'} || '') =~ /(?:hiptop|Blazer|Novarra|Vagabond|SonyEricsson|Symbian|NetFront|UP.Browser|UP.Link|Windows CE|MIDP|J2ME|DoCoMo|J-PHONE|PalmOS|PalmSource|iPhone|iPod|AvantGo|Nokia|Android|WebOS|S60|Mobile)/io && !$HTML::Mason::Commands::session{'NotMobile'}) { return 1; } else { return undef; @@ -1183,6 +1191,14 @@ our %is_whitelisted_component = ( # information for the search. Because it's a straight-up read, in # addition to embedding its own auth, it's fine. '/NoAuth/rss/dhandler' => 1, + + # While these can be used for denial-of-service against RT + # (construct a very inefficient query and trick lots of users into + # running them against RT) it's incredibly useful to be able to link + # to a search result or bookmark a result page. + '/Search/Results.html' => 1, + '/Search/Simple.html' => 1, + '/m/tickets/search' => 1, ); sub IsCompCSRFWhitelisted { @@ -1237,7 +1253,19 @@ sub IsRefererCSRFWhitelisted { my $configs; for my $config ( $base_url, RT->Config->Get('ReferrerWhitelist') ) { push @$configs,$config; - return 1 if $referer->host_port eq $config; + + my $host_port = $referer->host_port; + if ($config =~ /\*/) { + # Turn a literal * into a domain component or partial component match. + # Refer to http://tools.ietf.org/html/rfc2818#page-5 + my $regex = join "[a-zA-Z0-9\-]*", + map { quotemeta($_) } + split /\*/, $config; + + return 1 if $host_port =~ /^$regex$/i; + } else { + return 1 if $host_port eq $config; + } } return (0,$referer,$configs); @@ -1962,7 +1990,7 @@ sub MakeMIMEEntity { ); my $Message = MIME::Entity->build( Type => 'multipart/mixed', - "Message-Id" => RT::Interface::Email::GenMessageId, + "Message-Id" => Encode::encode_utf8( RT::Interface::Email::GenMessageId ), map { $_ => Encode::encode_utf8( $args{ $_} ) } grep defined $args{$_}, qw(Subject From Cc) ); diff --git a/rt/lib/RT/Record.pm b/rt/lib/RT/Record.pm index e134178be..fd238de16 100755 --- a/rt/lib/RT/Record.pm +++ b/rt/lib/RT/Record.pm @@ -639,6 +639,8 @@ sub __Value { my $value = $self->SUPER::__Value($field); + return undef if (!defined $value); + if ( $args{'decode_utf8'} ) { if ( !utf8::is_utf8($value) ) { utf8::decode($value); @@ -1675,7 +1677,7 @@ sub _AddCustomFieldValue { 0, $self->loc( "Custom field [_1] does not apply to this object", - $args{'Field'} + ref $args{'Field'} ? $args{'Field'}->id : $args{'Field'} ) ); } diff --git a/rt/lib/RT/Scrip.pm b/rt/lib/RT/Scrip.pm index 950661624..8f97e747f 100755 --- a/rt/lib/RT/Scrip.pm +++ b/rt/lib/RT/Scrip.pm @@ -545,7 +545,7 @@ sub _Set { } } - return $self->__Set(@_); + return $self->SUPER::_Set(@_); } diff --git a/rt/lib/RT/Scrips.pm b/rt/lib/RT/Scrips.pm index 13a4b7d7d..fa33f7ec7 100755 --- a/rt/lib/RT/Scrips.pm +++ b/rt/lib/RT/Scrips.pm @@ -178,16 +178,6 @@ Commit all of this object's prepared scrips sub Commit { my $self = shift; - # RT::Scrips->_SetupSourceObjects will clobber - # the CurrentUser, but we need to keep this ticket - # so that the _TransactionBatch cache is maintained - # and doesn't run twice. sigh. - $self->_StashCurrentUser( TicketObj => $self->{TicketObj} ) if $self->{TicketObj}; - - #We're really going to need a non-acled ticket for the scrips to work - $self->_SetupSourceObjects( TicketObj => $self->{'TicketObj'}, - TransactionObj => $self->{'TransactionObj'} ); - foreach my $scrip (@{$self->Prepared}) { $RT::Logger->debug( "Committing scrip #". $scrip->id @@ -199,8 +189,6 @@ sub Commit { TransactionObj => $self->{'TransactionObj'} ); } - # Apply the bandaid. - $self->_RestoreCurrentUser( TicketObj => $self->{TicketObj} ) if $self->{TicketObj}; } @@ -221,12 +209,6 @@ sub Prepare { Type => undef, @_ ); - # RT::Scrips->_SetupSourceObjects will clobber - # the CurrentUser, but we need to keep this ticket - # so that the _TransactionBatch cache is maintained - # and doesn't run twice. sigh. - $self->_StashCurrentUser( TicketObj => $args{TicketObj} ) if $args{TicketObj}; - #We're really going to need a non-acled ticket for the scrips to work $self->_SetupSourceObjects( TicketObj => $args{'TicketObj'}, Ticket => $args{'Ticket'}, @@ -259,10 +241,6 @@ sub Prepare { } - # Apply the bandaid. - $self->_RestoreCurrentUser( TicketObj => $args{TicketObj} ) if $args{TicketObj}; - - return (@{$self->Prepared}); }; @@ -279,40 +257,6 @@ sub Prepared { return ($self->{'prepared_scrips'} || []); } -=head2 _StashCurrentUser TicketObj => RT::Ticket - -Saves aside the current user of the original ticket that was passed to these scrips. -This is used to make sure that we don't accidentally leak the RT_System current user -back to the calling code. - -=cut - -sub _StashCurrentUser { - my $self = shift; - my %args = @_; - - $self->{_TicketCurrentUser} = $args{TicketObj}->CurrentUser; -} - -=head2 _RestoreCurrentUser TicketObj => RT::Ticket - -Uses the current user saved by _StashCurrentUser to reset a Ticket object -back to the caller's current user and avoid leaking an RT_System ticket to -calling code. - -=cut - -sub _RestoreCurrentUser { - my $self = shift; - my %args = @_; - unless ( $self->{_TicketCurrentUser} ) { - RT->Logger->debug("Called _RestoreCurrentUser without a stashed current user object"); - return; - } - $args{TicketObj}->CurrentUser($self->{_TicketCurrentUser}); - -} - =head2 _SetupSourceObjects { TicketObj , Ticket, Transaction, TransactionObj } Setup a ticket and transaction for this Scrip collection to work with as it runs through the @@ -334,14 +278,22 @@ sub _SetupSourceObjects { @_ ); - if ( $self->{'TicketObj'} = $args{'TicketObj'} ) { - # This clobbers the passed in TicketObj by turning it into one - # whose current user is RT_System. Anywhere in the Web UI - # currently calling into this is thus susceptable to a privilege - # leak; the only current call site is ->Apply, which bandaids - # over the top of this by re-asserting the CurrentUser - # afterwards. - $self->{'TicketObj'}->CurrentUser( $self->CurrentUser ); + if ( $args{'TicketObj'} ) { + # This loads a clean copy of the Ticket object to ensure that we + # don't accidentally escalate the privileges of the passed in + # ticket (this function can be invoked from the UI). + # We copy the TransactionBatch transactions so that Scrips + # running against the new Ticket will have access to them. We + # use RanTransactionBatch to guard against running + # TransactionBatch Scrips more than once. + $self->{'TicketObj'} = RT::Ticket->new( $self->CurrentUser ); + $self->{'TicketObj'}->Load( $args{'TicketObj'}->Id ); + if ( $args{'TicketObj'}->TransactionBatch ) { + # try to ensure that we won't infinite loop if something dies, triggering DESTROY while + # we have the _TransactionBatch objects; + $self->{'TicketObj'}->RanTransactionBatch(1); + $self->{'TicketObj'}->{'_TransactionBatch'} = $args{'TicketObj'}->{'_TransactionBatch'}; + } } else { $self->{'TicketObj'} = RT::Ticket->new( $self->CurrentUser ); diff --git a/rt/lib/RT/Search/Googleish.pm b/rt/lib/RT/Search/Googleish.pm index a1254836a..1b4071f4d 100644 --- a/rt/lib/RT/Search/Googleish.pm +++ b/rt/lib/RT/Search/Googleish.pm @@ -110,7 +110,7 @@ sub QueryToSQL { (\w+) # A straight word (?:\. # With an optional .foo ($RE{delimited}{-delim=>q['"]} - |\w+ + |[\w-]+ # Allow \w + dashes ) # Which could be ."foo bar", too )? ) @@ -225,6 +225,11 @@ sub GuessType { return "default"; } +# $_[0] is $self +# $_[1] is escaped value without surrounding single quotes +# $_[2] is a boolean of "was quoted by the user?" +# ensure this is false before you do smart matching like $_[1] eq "me" +# $_[3] is escaped subkey, if any (see HandleCf) sub HandleDefault { return subject => "Subject LIKE '$_[1]'"; } sub HandleSubject { return subject => "Subject LIKE '$_[1]'"; } sub HandleFulltext { return content => "Content LIKE '$_[1]'"; } @@ -242,7 +247,14 @@ sub HandleStatus { } } sub HandleOwner { - return owner => (!$_[2] and $_[1] eq "me") ? "Owner.id = '__CurrentUser__'" : "Owner = '$_[1]'"; + if (!$_[2] and $_[1] eq "me") { + return owner => "Owner.id = '__CurrentUser__'"; + } + elsif (!$_[2] and $_[1] =~ /\w+@\w+/) { + return owner => "Owner.EmailAddress = '$_[1]'"; + } else { + return owner => "Owner = '$_[1]'"; + } } sub HandleWatcher { return watcher => (!$_[2] and $_[1] eq "me") ? "Watcher.id = '__CurrentUser__'" : "Watcher = '$_[1]'"; diff --git a/rt/lib/RT/SearchBuilder.pm b/rt/lib/RT/SearchBuilder.pm index 3e9855110..4278f7587 100644 --- a/rt/lib/RT/SearchBuilder.pm +++ b/rt/lib/RT/SearchBuilder.pm @@ -211,29 +211,35 @@ sub LimitCustomField { @_ ); my $alias = $self->Join( - TYPE => 'left', - ALIAS1 => 'main', - FIELD1 => 'id', - TABLE2 => 'ObjectCustomFieldValues', - FIELD2 => 'ObjectId' + TYPE => 'left', + ALIAS1 => 'main', + FIELD1 => 'id', + TABLE2 => 'ObjectCustomFieldValues', + FIELD2 => 'ObjectId' ); $self->Limit( - ALIAS => $alias, - FIELD => 'CustomField', - OPERATOR => '=', - VALUE => $args{'CUSTOMFIELD'}, + ALIAS => $alias, + FIELD => 'CustomField', + OPERATOR => '=', + VALUE => $args{'CUSTOMFIELD'}, ) if ($args{'CUSTOMFIELD'}); $self->Limit( - ALIAS => $alias, - FIELD => 'ObjectType', - OPERATOR => '=', - VALUE => $self->_SingularClass, + ALIAS => $alias, + FIELD => 'ObjectType', + OPERATOR => '=', + VALUE => $self->_SingularClass, ); $self->Limit( - ALIAS => $alias, - FIELD => 'Content', - OPERATOR => $args{'OPERATOR'}, - VALUE => $args{'VALUE'}, + ALIAS => $alias, + FIELD => 'Content', + OPERATOR => $args{'OPERATOR'}, + VALUE => $args{'VALUE'}, + ); + $self->Limit( + ALIAS => $alias, + FIELD => 'Disabled', + OPERATOR => '=', + VALUE => 0, ); } diff --git a/rt/lib/RT/Shredder.pm b/rt/lib/RT/Shredder.pm index 40c73b36d..4f96e162d 100644 --- a/rt/lib/RT/Shredder.pm +++ b/rt/lib/RT/Shredder.pm @@ -539,9 +539,9 @@ sub WipeoutAll { my $self = $_[0]; - while ( my ($k, $v) = each %{ $self->{'cache'} } ) { - next if $v->{'State'} & (WIPED | IN_WIPING); - $self->Wipeout( Object => $v->{'Object'} ); + foreach my $cache_val ( values %{ $self->{'cache'} } ) { + next if $cache_val->{'State'} & (WIPED | IN_WIPING); + $self->Wipeout( Object => $cache_val->{'Object'} ); } } diff --git a/rt/lib/RT/Test.pm b/rt/lib/RT/Test.pm index 7d69dd60d..3e7c910ec 100644 --- a/rt/lib/RT/Test.pm +++ b/rt/lib/RT/Test.pm @@ -131,14 +131,14 @@ sub import { if (RT->Config->Get('DevelMode')) { require Module::Refresh; } - $class->bootstrap_db( %args ); - RT::InitPluginPaths(); + RT::InitClasses(); + + $class->bootstrap_db( %args ); __reconnect_rt() unless $args{nodb}; - RT::InitClasses(); RT::InitLogging(); RT->Plugins; diff --git a/rt/lib/RT/Ticket.pm b/rt/lib/RT/Ticket.pm index 00f88b657..577c44429 100755 --- a/rt/lib/RT/Ticket.pm +++ b/rt/lib/RT/Ticket.pm @@ -1124,7 +1124,7 @@ sub AddWatcher { return (0, $self->loc("Couldn't parse address from '[_1]' string", $args{'Email'} )) unless $addr; - if ( lc $self->CurrentUser->UserObj->EmailAddress + if ( lc $self->CurrentUser->EmailAddress eq lc RT::User->CanonicalizeEmailAddress( $addr->address ) ) { $args{'PrincipalId'} = $self->CurrentUser->id; @@ -1305,7 +1305,7 @@ sub DeleteWatcher { } } else { - $RT::Logger->warn("$self -> DeleteWatcher got passed a bogus type"); + $RT::Logger->warning("$self -> DeleteWatcher got passed a bogus type"); return ( 0, $self->loc('Error in parameters to Ticket->DeleteWatcher') ); } @@ -1989,6 +1989,31 @@ sub FirstActiveStatus { return $next; } +=head2 FirstInactiveStatus + +Returns the first inactive status that the ticket could transition to, +according to its current Queue's lifecycle. May return undef if there +is no such possible status to transition to, or we are already in it. +This is used in resolve action in UnsafeEmailCommands, for instance. + +=cut + +sub FirstInactiveStatus { + my $self = shift; + + my $lifecycle = $self->QueueObj->Lifecycle; + my $status = $self->Status; + my @inactive = $lifecycle->Inactive; + # no change if no inactive statuses in the lifecycle + return undef unless @inactive; + + # no change if the ticket is already has first status from the list of inactive + return undef if lc $status eq lc $inactive[0]; + + my ($next) = grep $lifecycle->IsInactive($_), $lifecycle->Transitions($status); + return $next; +} + =head2 SetStarted Takes a date in ISO format or undef @@ -2315,7 +2340,9 @@ sub _RecordNote { my $msgid = $args{'MIMEObj'}->head->get('Message-ID'); unless (defined $msgid && $msgid =~ /<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@\Q$org\E>/) { $args{'MIMEObj'}->head->set( - 'RT-Message-ID' => RT::Interface::Email::GenMessageId( Ticket => $self ) + 'RT-Message-ID' => Encode::encode_utf8( + RT::Interface::Email::GenMessageId( Ticket => $self ) + ) ); } @@ -3340,6 +3367,28 @@ sub SeenUpTo { return $txns->First; } +=head2 RanTransactionBatch + +Acts as a guard around running TransactionBatch scrips. + +Should be false until you enter the code that runs TransactionBatch scrips + +Accepts an optional argument to indicate that TransactionBatch Scrips should no longer be run on this object. + +=cut + +sub RanTransactionBatch { + my $self = shift; + my $val = shift; + + if ( defined $val ) { + return $self->{_RanTransactionBatch} = $val; + } else { + return $self->{_RanTransactionBatch}; + } + +} + =head2 TransactionBatch @@ -3376,6 +3425,22 @@ sub ApplyTransactionBatch { sub _ApplyTransactionBatch { my $self = shift; + + return if $self->RanTransactionBatch; + $self->RanTransactionBatch(1); + + my $still_exists = RT::Ticket->new( RT->SystemUser ); + $still_exists->Load( $self->Id ); + if (not $still_exists->Id) { + # The ticket has been removed from the database, but we still + # have pending TransactionBatch txns for it. Unfortunately, + # because it isn't in the DB anymore, attempting to run scrips + # on it may produce unpredictable results; simply drop the + # batched transactions. + $RT::Logger->warning("TransactionBatch was fired on a ticket that no longer exists; unable to run scrips! Call ->ApplyTransactionBatch before shredding the ticket, for consistent results."); + return; + } + my $batch = $self->TransactionBatch; my %seen; @@ -3423,10 +3488,7 @@ sub DESTROY { return; } - my $batch = $self->TransactionBatch; - return unless $batch && @$batch; - - return $self->_ApplyTransactionBatch; + return $self->ApplyTransactionBatch; } diff --git a/rt/lib/RT/Tickets.pm b/rt/lib/RT/Tickets.pm index 485d7df53..c9986f41e 100755 --- a/rt/lib/RT/Tickets.pm +++ b/rt/lib/RT/Tickets.pm @@ -436,6 +436,10 @@ sub _LinkLimit { my $is_null = 0; $is_null = 1 if !$value || $value =~ /^null$/io; + unless ($is_null) { + $value = RT::URI->new( $sb->CurrentUser )->CanonicalizeURI( $value ); + } + my $direction = $meta->[1] || ''; my ($matchfield, $linkfield) = ('', ''); if ( $direction eq 'To' ) { @@ -1651,6 +1655,7 @@ sub _CustomFieldLimit { FIELD => $column, OPERATOR => $op, VALUE => $value, + CASESENSITIVE => 0, %rest ) ); $self->_CloseParen; @@ -1713,6 +1718,7 @@ sub _CustomFieldLimit { FIELD => 'Content', OPERATOR => $op, VALUE => $value, + CASESENSITIVE => 0, %rest ); } @@ -1739,6 +1745,7 @@ sub _CustomFieldLimit { OPERATOR => $op, VALUE => $value, ENTRYAGGREGATOR => 'AND', + CASESENSITIVE => 0, ) ); } } @@ -1748,6 +1755,7 @@ sub _CustomFieldLimit { FIELD => 'Content', OPERATOR => $op, VALUE => $value, + CASESENSITIVE => 0, %rest ); @@ -1774,6 +1782,7 @@ sub _CustomFieldLimit { OPERATOR => $op, VALUE => $value, ENTRYAGGREGATOR => 'AND', + CASESENSITIVE => 0, ) ); $self->_CloseParen; } @@ -1830,6 +1839,7 @@ sub _CustomFieldLimit { FIELD => $column, OPERATOR => $op, VALUE => $value, + CASESENSITIVE => 0, ) ); } else { @@ -1839,6 +1849,7 @@ sub _CustomFieldLimit { FIELD => 'Content', OPERATOR => $op, VALUE => $value, + CASESENSITIVE => 0, ); } $self->_SQLLimit( diff --git a/rt/lib/RT/Transaction.pm b/rt/lib/RT/Transaction.pm index bd4d83546..3344687da 100755 --- a/rt/lib/RT/Transaction.pm +++ b/rt/lib/RT/Transaction.pm @@ -133,12 +133,6 @@ sub Create { return ( 0, $self->loc( "Transaction->Create couldn't, as you didn't specify an object type and id")); } - - # Set up any custom fields passed at creation. Has to happen - # before scrips. - - $self->UpdateCustomFields(%{ $args{'CustomFields'} }); - #lets create our transaction my %params = ( Type => $args{'Type'}, @@ -169,6 +163,11 @@ sub Create { } } + # Set up any custom fields passed at creation. Has to happen + # before scrips. + + $self->UpdateCustomFields(%{ $args{'CustomFields'} }); + $self->AddAttribute( Name => 'SquelchMailTo', Content => RT::User->CanonicalizeEmailAddress($_) diff --git a/rt/lib/RT/URI.pm b/rt/lib/RT/URI.pm index fce04598a..284a75ee0 100644 --- a/rt/lib/RT/URI.pm +++ b/rt/lib/RT/URI.pm @@ -91,7 +91,26 @@ sub new { return ($self); } +=head2 CanonicalizeURI <URI> +Returns the canonical form of the given URI by calling L</FromURI> and then L</URI>. + +If the URI is unparseable by FromURI the passed in URI is simply returned untouched. + +=cut + +sub CanonicalizeURI { + my $self = shift; + my $uri = shift; + if ($self->FromURI($uri)) { + my $canonical = $self->URI; + if ($canonical and $uri ne $canonical) { + RT->Logger->debug("Canonicalizing URI '$uri' to '$canonical'"); + $uri = $canonical; + } + } + return $uri; +} =head2 FromObject <Object> diff --git a/rt/lib/RT/User.pm b/rt/lib/RT/User.pm index 9b4a82683..e7f7c2ad6 100755 --- a/rt/lib/RT/User.pm +++ b/rt/lib/RT/User.pm @@ -932,7 +932,7 @@ sub IsPassword { # crypt() output return 0 unless crypt(encode_utf8($value), $stored) eq $stored; } else { - $RT::Logger->warn("Unknown password form"); + $RT::Logger->warning("Unknown password form"); return 0; } diff --git a/rt/sbin/rt-server.fcgi.in b/rt/sbin/rt-server.fcgi.in index 45c377088..f84f6c103 100644 --- a/rt/sbin/rt-server.fcgi.in +++ b/rt/sbin/rt-server.fcgi.in @@ -172,7 +172,7 @@ if (caller) { require Plack::Runner; my $is_fastcgi = $0 =~ m/fcgi$/; -my $r = Plack::Runner->new( $0 =~ 'standalone' ? ( server => 'Standalone' ) : +my $r = Plack::Runner->new( $0 =~ /standalone/ ? ( server => 'Standalone' ) : $is_fastcgi ? ( server => 'FCGI' ) : (), env => 'deployment' ); diff --git a/rt/sbin/rt-server.in b/rt/sbin/rt-server.in index 45c377088..f84f6c103 100644 --- a/rt/sbin/rt-server.in +++ b/rt/sbin/rt-server.in @@ -172,7 +172,7 @@ if (caller) { require Plack::Runner; my $is_fastcgi = $0 =~ m/fcgi$/; -my $r = Plack::Runner->new( $0 =~ 'standalone' ? ( server => 'Standalone' ) : +my $r = Plack::Runner->new( $0 =~ /standalone/ ? ( server => 'Standalone' ) : $is_fastcgi ? ( server => 'FCGI' ) : (), env => 'deployment' ); diff --git a/rt/sbin/rt-test-dependencies.in b/rt/sbin/rt-test-dependencies.in index 37ef32f64..960d640c3 100644 --- a/rt/sbin/rt-test-dependencies.in +++ b/rt/sbin/rt-test-dependencies.in @@ -56,9 +56,10 @@ no warnings qw(numeric redefine); use Getopt::Long; my %args; my %deps; +my @orig_argv = @ARGV; GetOptions( \%args, 'v|verbose', - 'install', 'with-MYSQL', + 'install!', 'with-MYSQL', 'with-POSTGRESQL|with-pg|with-pgsql', 'with-SQLITE', 'with-ORACLE', 'with-FASTCGI', 'with-MODPERL1', 'with-MODPERL2', @@ -293,7 +294,7 @@ Test::LongString . $deps{'FASTCGI'} = [ text_to_hash( << '.') ]; -FCGI +FCGI 0.74 FCGI::ProcManager . @@ -344,7 +345,7 @@ URI 1.59 $deps{'GRAPHVIZ'} = [ text_to_hash( << '.') ]; GraphViz -IPC::Run +IPC::Run 0.90 . $deps{'GD'} = [ text_to_hash( << '.') ]; @@ -359,6 +360,7 @@ Convert::Color my %AVOID = ( 'DBD::Oracle' => [qw(1.23)], + 'Email::Address' => [qw(1.893 1.894)], ); if ($args{'download'}) { @@ -403,7 +405,12 @@ foreach my $type (sort grep $args{$_}, keys %args) { $Missing_By_Type{$type} = \%missing if keys %missing; } -conclude(%Missing_By_Type); +if ( $args{'install'} && keys %Missing_By_Type ) { + exec($0, @orig_argv, '--no-install'); +} +else { + conclude(%Missing_By_Type); +} sub test_deps { my @deps = @_; diff --git a/rt/sbin/standalone_httpd b/rt/sbin/standalone_httpd index 3386cd1fe..cef0f3102 100755 --- a/rt/sbin/standalone_httpd +++ b/rt/sbin/standalone_httpd @@ -172,7 +172,7 @@ if (caller) { require Plack::Runner; my $is_fastcgi = $0 =~ m/fcgi$/; -my $r = Plack::Runner->new( $0 =~ 'standalone' ? ( server => 'Standalone' ) : +my $r = Plack::Runner->new( $0 =~ /standalone/ ? ( server => 'Standalone' ) : $is_fastcgi ? ( server => 'FCGI' ) : (), env => 'deployment' ); diff --git a/rt/sbin/standalone_httpd.in b/rt/sbin/standalone_httpd.in index 45c377088..f84f6c103 100644 --- a/rt/sbin/standalone_httpd.in +++ b/rt/sbin/standalone_httpd.in @@ -172,7 +172,7 @@ if (caller) { require Plack::Runner; my $is_fastcgi = $0 =~ m/fcgi$/; -my $r = Plack::Runner->new( $0 =~ 'standalone' ? ( server => 'Standalone' ) : +my $r = Plack::Runner->new( $0 =~ /standalone/ ? ( server => 'Standalone' ) : $is_fastcgi ? ( server => 'FCGI' ) : (), env => 'deployment' ); diff --git a/rt/share/html/Admin/Queues/Modify.html b/rt/share/html/Admin/Queues/Modify.html index 5682eee28..85cd62f16 100755 --- a/rt/share/html/Admin/Queues/Modify.html +++ b/rt/share/html/Admin/Queues/Modify.html @@ -51,7 +51,7 @@ -<form action="<%RT->Config->Get('WebPath')%>/Admin/Queues/Modify.html" name="ModifyQueue" method="post"> +<form action="<%RT->Config->Get('WebPath')%>/Admin/Queues/Modify.html" name="ModifyQueue" method="post" enctype="multipart/form-data"> <input type="hidden" class="hidden" name="SetEnabled" value="1" /> <input type="hidden" class="hidden" name="id" value="<% $Create? 'new': $QueueObj->Id %>" /> diff --git a/rt/share/html/Approvals/Elements/PendingMyApproval b/rt/share/html/Approvals/Elements/PendingMyApproval index d2061da84..169c25cb6 100755 --- a/rt/share/html/Approvals/Elements/PendingMyApproval +++ b/rt/share/html/Approvals/Elements/PendingMyApproval @@ -74,7 +74,7 @@ $tickets->LimitOwner( VALUE => $session{'CurrentUser'}->Id ); # also consider AdminCcs as potential approvers. my $group_tickets = RT::Tickets->new( $session{'CurrentUser'} ); -$group_tickets->LimitWatcher( VALUE => $session{'CurrentUser'}->UserObj->EmailAddress, TYPE => 'AdminCc' ); +$group_tickets->LimitWatcher( VALUE => $session{'CurrentUser'}->EmailAddress, TYPE => 'AdminCc' ); my $created_before = RT::Date->new( $session{'CurrentUser'} ); my $created_after = RT::Date->new( $session{'CurrentUser'} ); diff --git a/rt/share/html/Approvals/autohandler b/rt/share/html/Approvals/autohandler index a05770654..3e0f2c6db 100644 --- a/rt/share/html/Approvals/autohandler +++ b/rt/share/html/Approvals/autohandler @@ -46,8 +46,13 @@ %# %# END BPS TAGGED BLOCK }}} <%init> -$m->call_next(%ARGS) if $session{'CurrentUser'}->UserObj->HasRight( +if ( $session{'CurrentUser'}->UserObj->HasRight( Right => 'ShowApprovalsTab', Object => $RT::System, -); +) ) { + $m->call_next(%ARGS); +} +else { + Abort("No permission to view approval"); +} </%init> diff --git a/rt/share/html/Dashboards/Subscription.html b/rt/share/html/Dashboards/Subscription.html index 3669e4687..3a57102c7 100644 --- a/rt/share/html/Dashboards/Subscription.html +++ b/rt/share/html/Dashboards/Subscription.html @@ -171,7 +171,7 @@ <&|/l&>Recipient</&>: </td><td class="value"> <input name="Recipient" id="Recipient" size="30" value="<%$fields{Recipient} ? $fields{Recipient} : ''%>" /> -<div class="hints"><% loc("Leave blank to send to your current email address ([_1])", $session{'CurrentUser'}->UserObj->EmailAddress) %></div> +<div class="hints"><% loc("Leave blank to send to your current email address ([_1])", $session{'CurrentUser'}->EmailAddress) %></div> </td></tr> </table> </&> diff --git a/rt/share/html/Elements/AddCustomers b/rt/share/html/Elements/AddCustomers index 9828d7d53..6517db42c 100644 --- a/rt/share/html/Elements/AddCustomers +++ b/rt/share/html/Elements/AddCustomers @@ -41,7 +41,7 @@ my @Customers = (); if ( $CustomerString ) { @Customers = &RT::URI::freeside::smart_search( 'search' => $CustomerString, - 'no_fuzzy_on_exact' => 1, #pref? + 'no_fuzzy_on_exact' => ! $FS::CurrentUser::CurrentUser->option('enable_fuzzy_on_exact'), ); } diff --git a/rt/share/html/Elements/ColumnMap b/rt/share/html/Elements/ColumnMap index b9c3b4bc8..f268a5d1c 100644 --- a/rt/share/html/Elements/ColumnMap +++ b/rt/share/html/Elements/ColumnMap @@ -118,7 +118,7 @@ my $COLUMN_MAP = { CheckBox => { title => sub { my $name = $_[1] || 'SelectedTickets'; - my $checked = $m->request_args->{ $name .'All' }? 'checked="checked"': ''; + my $checked = $DECODED_ARGS->{ $name .'All' }? 'checked="checked"': ''; return \qq{<input type="checkbox" name="}, $name, \qq{All" value="1" $checked onclick="setCheckbox(this.form, }, @@ -130,9 +130,9 @@ my $COLUMN_MAP = { my $name = $_[2] || 'SelectedTickets'; return \qq{<input type="checkbox" name="}, $name, \qq{" value="$id" checked="checked" />} - if $m->request_args->{ $name . 'All'}; + if $DECODED_ARGS->{ $name . 'All'}; - my $arg = $m->request_args->{ $name }; + my $arg = $DECODED_ARGS->{ $name }; my $checked = ''; if ( $arg && ref $arg ) { $checked = 'checked="checked"' if grep $_ == $id, @$arg; @@ -149,7 +149,7 @@ my $COLUMN_MAP = { my $id = $_[0]->id; my $name = $_[2] || 'SelectedTicket'; - my $arg = $m->request_args->{ $name }; + my $arg = $DECODED_ARGS->{ $name }; my $checked = ''; $checked = 'checked="checked"' if $arg && $arg == $id; return \qq{<input type="radio" name="}, $name, \qq{" value="$id" $checked />}; diff --git a/rt/share/html/Elements/EditCustomField b/rt/share/html/Elements/EditCustomField index b74c4844e..8b87fd425 100644 --- a/rt/share/html/Elements/EditCustomField +++ b/rt/share/html/Elements/EditCustomField @@ -71,7 +71,7 @@ if ( $Object && $Object->id ) { # Always fill $Default with submited values if it's empty if ( ( !defined $Default || !length $Default ) && $DefaultsFromTopArguments ) { - my %TOP = $m->request_args; + my %TOP = %$DECODED_ARGS; $Default = $TOP{ $NamePrefix .$CustomField->Id . '-Values' } || $TOP{ $NamePrefix .$CustomField->Id . '-Value' }; } diff --git a/rt/share/html/Elements/Header b/rt/share/html/Elements/Header index 1830c4bf2..65d06f879 100755 --- a/rt/share/html/Elements/Header +++ b/rt/share/html/Elements/Header @@ -130,7 +130,8 @@ if ($m->comp_exists($stylesheet_plugin) ) { # $m->callback( %ARGS, CallbackName => 'Head' ); $head .= $m->scomp( '/Elements/Callback', _CallbackName => 'Head', %ARGS ); -my $etc = qq[ class="\L$style" ]; +my $sbs = RT->Config->Get("UseSideBySideLayout", $session{'CurrentUser'}) ? ' sidebyside' : ''; +my $etc = qq[ class="\L$style$sbs" ]; $etc .= qq[ id="comp-$id"] if $id; </%INIT> diff --git a/rt/share/html/Elements/HeaderJavascript b/rt/share/html/Elements/HeaderJavascript index 28788db57..d5741f4e6 100644 --- a/rt/share/html/Elements/HeaderJavascript +++ b/rt/share/html/Elements/HeaderJavascript @@ -67,7 +67,7 @@ $onload => undef % } % if ( $RichText and RT->Config->Get('MessageBoxRichText', $session{'CurrentUser'})) { - jQuery().ready(function () { ReplaceAllTextareas(<%$m->request_args->{'CKeditorEncoded'} || 0 |n,j%>) }); + jQuery().ready(function () { ReplaceAllTextareas(<%$DECODED_ARGS->{'CKeditorEncoded'} || 0 |n,j%>) }); % } --></script> <%ARGS> diff --git a/rt/share/html/Elements/ListActions b/rt/share/html/Elements/ListActions index 999d3fe5b..8929ff731 100755 --- a/rt/share/html/Elements/ListActions +++ b/rt/share/html/Elements/ListActions @@ -65,7 +65,7 @@ if ( ref( $session{'Actions'}{''} ) eq 'ARRAY' ) { unshift @actions, @{ delete $session{'Actions'}{''} }; } -my $actions_pointer = $m->request_args->{'results'}; +my $actions_pointer = $DECODED_ARGS->{'results'}; if ($actions_pointer && ref( $session{'Actions'}->{$actions_pointer} ) eq 'ARRAY' ) { unshift @actions, @{ delete $session{'Actions'}->{$actions_pointer} }; diff --git a/rt/share/html/Elements/MessageBox b/rt/share/html/Elements/MessageBox index 61995e057..69227bfa9 100755 --- a/rt/share/html/Elements/MessageBox +++ b/rt/share/html/Elements/MessageBox @@ -46,7 +46,7 @@ %# %# END BPS TAGGED BLOCK }}} <textarea autocomplete="off" class="messagebox" <% $width_attr %>="<% $Width %>" rows="<% $Height %>" <% $wrap_type |n %> name="<% $Name %>" id="<% $Name %>">\ -% $m->comp('/Articles/Elements/IncludeArticle', %ARGS); +% $m->comp('/Articles/Elements/IncludeArticle', %ARGS) if $IncludeArticle; % $m->callback( %ARGS, SignatureRef => \$signature ); <% $Default || '' %><% $message %><% $signature %></textarea> % $m->callback( %ARGS, CallbackName => 'AfterTextArea' ); @@ -89,4 +89,5 @@ $Width => RT->Config->Get('MessageBoxWidth', $session{'CurrentUser'} $Height => RT->Config->Get('MessageBoxHeight', $session{'CurrentUser'} ) || 15 $Wrap => RT->Config->Get('MessageBoxWrap', $session{'CurrentUser'} ) || 'SOFT' $IncludeSignature => RT->Config->Get('MessageBoxIncludeSignature'); +$IncludeArticle => 1; </%ARGS> diff --git a/rt/share/html/Elements/QueueSummaryByStatus b/rt/share/html/Elements/QueueSummaryByStatus index 09f274f74..f649d2850 100644 --- a/rt/share/html/Elements/QueueSummaryByStatus +++ b/rt/share/html/Elements/QueueSummaryByStatus @@ -122,9 +122,13 @@ my $statuses = {}; use RT::Report::Tickets; my $report = RT::Report::Tickets->new( RT->SystemUser ); -my $query = @queues - ? join(' OR ', map "Queue = ".$_->{id}, @queues) - : 'id < 0'; +my $query = + "(". + join(" OR ", map {s{(['\\])}{\\$1}g; "Status = '$_'"} @statuses) #' + .") AND (". + join(' OR ', map "Queue = ".$_->{id}, @queues) + .")"; +$query = 'id < 0' unless @queues; $report->SetupGroupings( Query => $query, GroupBy => [qw(Status Queue)] ); while ( my $entry = $report->Next ) { diff --git a/rt/share/html/Elements/RT__CustomField/ColumnMap b/rt/share/html/Elements/RT__CustomField/ColumnMap index ecb219d9e..b04398434 100644 --- a/rt/share/html/Elements/RT__CustomField/ColumnMap +++ b/rt/share/html/Elements/RT__CustomField/ColumnMap @@ -118,7 +118,7 @@ my $COLUMN_MAP = { RemoveCheckBox => { title => sub { my $name = 'RemoveCustomField'; - my $checked = $m->request_args->{ $name .'All' }? 'checked="checked"': ''; + my $checked = $DECODED_ARGS->{ $name .'All' }? 'checked="checked"': ''; return \qq{<input type="checkbox" name="}, $name, \qq{All" value="1" $checked onclick="setCheckbox(this.form, }, @@ -130,7 +130,7 @@ my $COLUMN_MAP = { return '' if $_[0]->IsApplied; my $name = 'RemoveCustomField'; - my $arg = $m->request_args->{ $name }; + my $arg = $DECODED_ARGS->{ $name }; my $checked = ''; if ( $arg && ref $arg ) { diff --git a/rt/share/html/Elements/SelectWatcherType b/rt/share/html/Elements/SelectWatcherType index 44beee00d..4f1df60b2 100755 --- a/rt/share/html/Elements/SelectWatcherType +++ b/rt/share/html/Elements/SelectWatcherType @@ -56,7 +56,7 @@ <%INIT> my @types; -if ($Scope =~ 'queue') { +if ($Scope =~ /queue/) { @types = RT::Queue->ManageableRoleGroupTypes; } else { diff --git a/rt/share/html/Elements/Tabs b/rt/share/html/Elements/Tabs index 3193b488d..3aac9d803 100755 --- a/rt/share/html/Elements/Tabs +++ b/rt/share/html/Elements/Tabs @@ -845,7 +845,7 @@ my $build_selfservice_nav = sub { } elsif ( $queue_id ) { Menu->child( new => title => loc('New ticket'), path => '/SelfService/Create.html?Queue=' . $queue_id ); } - my $tickets = Menu->child( tickets => title => loc('Tickets')); + my $tickets = Menu->child( tickets => title => loc('Tickets'), path => '/SelfService/' ); $tickets->child( open => title => loc('Open tickets'), path => '/SelfService/' ); $tickets->child( closed => title => loc('Closed tickets'), path => '/SelfService/Closed.html' ); diff --git a/rt/share/html/Helpers/Autocomplete/Users b/rt/share/html/Helpers/Autocomplete/Users index dbc2d888f..c2b92c1bf 100644 --- a/rt/share/html/Helpers/Autocomplete/Users +++ b/rt/share/html/Helpers/Autocomplete/Users @@ -116,6 +116,9 @@ foreach (split /\s*,\s*/, $exclude) { my @suggestions; +$users->Limit( FIELD => $return, OPERATOR => '!=', VALUE => '' ); +$users->Limit( FIELD => $return, OPERATOR => 'IS NOT', VALUE => 'NULL', ENTRYAGGREGATOR => 'AND' ); + while ( my $user = $users->Next ) { next if $user->id == RT->SystemUser->id or $user->id == RT->Nobody->id; diff --git a/rt/share/html/NoAuth/css/aileron/boxes.css b/rt/share/html/NoAuth/css/aileron/boxes.css index ed6623cba..f90ac9f77 100644 --- a/rt/share/html/NoAuth/css/aileron/boxes.css +++ b/rt/share/html/NoAuth/css/aileron/boxes.css @@ -91,10 +91,6 @@ .titlebox .titlebox-title { position: relative; - /* This is for [rt3 #19044]. Move it to an IE-specific file if it causes - * problems. If we remove CSS3PIE, it can also probably go away, although it - * probably won't hurt. */ - z-index: 1; } .titlebox .titlebox-title a { diff --git a/rt/share/html/NoAuth/css/aileron/ticket.css b/rt/share/html/NoAuth/css/aileron/ticket.css index 4d069d9f9..7b573f72c 100644 --- a/rt/share/html/NoAuth/css/aileron/ticket.css +++ b/rt/share/html/NoAuth/css/aileron/ticket.css @@ -87,8 +87,7 @@ div#ticket-history { float: left; margin: 0.25em 0.70em 0.25em 0.25em; width: 1em; - height: 1.25em; - padding: 0.75em 0 0 0; + padding: 0; border-right: 1px solid #999; border-bottom: 1px solid #999; -moz-border-radius-bottomright: 0.25em; @@ -100,6 +99,16 @@ div#ticket-history { div#ticket-history span.type a { color: #fff; + padding-top: 0.75em; + display: block; +} + +#ticket-history a#lasttrans { + display: inline; + height: 0; + width: 0; + padding: 0; + margin: 0; } diff --git a/rt/share/html/NoAuth/css/ballard/boxes.css b/rt/share/html/NoAuth/css/ballard/boxes.css index 912ac55f4..9610cd5e7 100644 --- a/rt/share/html/NoAuth/css/ballard/boxes.css +++ b/rt/share/html/NoAuth/css/ballard/boxes.css @@ -54,6 +54,7 @@ margin-left: 1em; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; + border-radius: 0.5em; margin-bottom: 2em; border-bottom: 2px solid #aaa; border-right: 2px solid #aaa; @@ -71,6 +72,7 @@ margin-top: 1em; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; + border-radius: 0.5em; margin-right: 0.25em; } @@ -114,6 +116,7 @@ padding-right: 0.75em; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; + border-radius: 0.5em; border-bottom: 2px solid #aaa; border-right: 2px solid #aaa; @@ -138,10 +141,12 @@ padding-top: 0.5em; -moz-border-radius-bottomleft: 0.25em; -webkit-border-bottom-left-radius: 0.25em; + border-bottom-left-radius: 0.25em; -moz-border-radius-topright: 0.25em; -webkit-border-top-right-radius: 0.25em; + border-top-right-radius: 0.25em; } diff --git a/rt/share/html/NoAuth/css/ballard/layout.css b/rt/share/html/NoAuth/css/ballard/layout.css index 8dc0cc162..8b600b828 100644 --- a/rt/share/html/NoAuth/css/ballard/layout.css +++ b/rt/share/html/NoAuth/css/ballard/layout.css @@ -60,8 +60,10 @@ div#body { padding: 1.8em 1em 1em 1em; -moz-border-radius-topleft: 0.5em; -webkit-border-top-left-radius: 0.5em; + border-top-left-radius: 0.5em; -moz-border-radius-bottomleft: 0.5em; -webkit-border-bottom-left-radius: 0.5em; + border-bottom-left-radius: 0.5em; margin-left: 10em; margin-top: 3em; margin-right: 0; @@ -89,8 +91,10 @@ div#footer { border-left: 2px solid #aaa; -moz-border-radius-topleft: 0.5em; -webkit-border-top-left-radius: 0.5em; + border-top-left-radius: 0.5em; -moz-border-radius-bottomleft: 0.5em; -webkit-border-bottom-left-radius: 0.5em; + border-bottom-left-radius: 0.5em; } div#footer #time { diff --git a/rt/share/html/NoAuth/css/ballard/nav.css b/rt/share/html/NoAuth/css/ballard/nav.css index 196f0e6c0..dc29818fe 100644 --- a/rt/share/html/NoAuth/css/ballard/nav.css +++ b/rt/share/html/NoAuth/css/ballard/nav.css @@ -49,8 +49,10 @@ background-color: #fff; -moz-border-radius-bottomright: 0.5em; -webkit-border-bottom-right-radius: 0.5em; + border-bottom-right-radius: 0.5em; -moz-border-radius-topright: 0.5em; -webkit-border-top-right-radius: 0.5em; + border-top-right-radius: 0.5em; width: 10em; font-size: 0.85em; position: absolute; @@ -130,6 +132,7 @@ border: 1px solid #ccc; -moz-border-radius-bottomleft: 0.5em; -webkit-border-bottom-left-radius: 0.5em; + border-bottom-left-radius: 0.5em; padding: 0; padding-top: 0.5em; padding-right: 0.5em; diff --git a/rt/share/html/NoAuth/css/ballard/ticket-search.css b/rt/share/html/NoAuth/css/ballard/ticket-search.css index 19ee847ff..fb252b5e3 100644 --- a/rt/share/html/NoAuth/css/ballard/ticket-search.css +++ b/rt/share/html/NoAuth/css/ballard/ticket-search.css @@ -163,6 +163,7 @@ border-bottom: 1px solid #999; -moz-border-radius-bottomleft: 0.5em; -webkit-border-bottom-left-radius: 0.5em; + border-bottom-left-radius: 0.5em; } diff --git a/rt/share/html/NoAuth/css/ballard/ticket.css b/rt/share/html/NoAuth/css/ballard/ticket.css index 06b6678c9..4d416e175 100644 --- a/rt/share/html/NoAuth/css/ballard/ticket.css +++ b/rt/share/html/NoAuth/css/ballard/ticket.css @@ -77,6 +77,7 @@ div#ticket-history { color: #ccc; -moz-border-radius-bottomleft: 0.5em; -webkit-border-bottom-left-radius: 0.5em; + border-bottom-left-radius: 0.5em; white-space: nowrap; } @@ -91,6 +92,7 @@ div#ticket-history { border-bottom: 1px solid #999; -moz-border-radius: 0.25em; -webkit-border-bottom-right-radius: 0.25em; + border-bottom-right-radius: 0.25em; } div#ticket-history span.type a { @@ -150,6 +152,7 @@ border-bottom: 2px solid #aaa; margin-top: 0.5em; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; +border-radius: 0.5em; } diff --git a/rt/share/html/NoAuth/css/base/forms.css b/rt/share/html/NoAuth/css/base/forms.css index eab97b19b..19af1b2a3 100644 --- a/rt/share/html/NoAuth/css/base/forms.css +++ b/rt/share/html/NoAuth/css/base/forms.css @@ -87,6 +87,7 @@ input[type=reset], input[type=submit], input[class=button], button { padding-right: 0.5em; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; + border-radius: 0.5em; } input.button:hover, button:hover, input[type=reset]:hover, input[type=submit]:hover, input[class=button]:hover { diff --git a/rt/share/html/NoAuth/css/base/jquery-ui-timepicker-addon.css b/rt/share/html/NoAuth/css/base/jquery-ui-timepicker-addon.css new file mode 100644 index 000000000..7eb871568 --- /dev/null +++ b/rt/share/html/NoAuth/css/base/jquery-ui-timepicker-addon.css @@ -0,0 +1,7 @@ +.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } +.ui-timepicker-div dl { text-align: left; } +.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } +.ui-timepicker-div dl dd { margin: 0 10px 10px 65px; } +.ui-timepicker-div td { font-size: 90%; } +.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } +.ui-datepicker-buttonpane button.ui-datepicker-current { opacity: 1.0; } diff --git a/rt/share/html/NoAuth/css/base/jquery-ui.css b/rt/share/html/NoAuth/css/base/jquery-ui.css index 820996ea8..8fe4f1545 100644 --- a/rt/share/html/NoAuth/css/base/jquery-ui.css +++ b/rt/share/html/NoAuth/css/base/jquery-ui.css @@ -46,5 +46,3 @@ %# %# END BPS TAGGED BLOCK }}} @import "jquery-ui.custom.modified.css"; -@import "ui.timepickr.css"; -@import "ui.timepickr.custom.css"; diff --git a/rt/share/html/NoAuth/css/base/jquery-ui.custom.modified.css b/rt/share/html/NoAuth/css/base/jquery-ui.custom.modified.css index 7a323229a..3b1e1a00e 100644 --- a/rt/share/html/NoAuth/css/base/jquery-ui.custom.modified.css +++ b/rt/share/html/NoAuth/css/base/jquery-ui.custom.modified.css @@ -452,3 +452,27 @@ width: 200px; /*must have*/ height: 200px; /*must have*/ } +/* + * jQuery UI Slider 1.8.4 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider#theming + */ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; } diff --git a/rt/share/html/NoAuth/css/base/main.css b/rt/share/html/NoAuth/css/base/main.css index 9f77c8aee..dac733d87 100644 --- a/rt/share/html/NoAuth/css/base/main.css +++ b/rt/share/html/NoAuth/css/base/main.css @@ -49,6 +49,7 @@ @import "yui-fonts.css"; @import "jquery-ui.css"; +@import "jquery-ui-timepicker-addon.css"; @import "superfish.css"; @import "superfish-navbar.css"; @import "superfish-vertical.css"; diff --git a/rt/share/html/NoAuth/css/base/superfish-navbar.css b/rt/share/html/NoAuth/css/base/superfish-navbar.css index 9a3f24cd9..459156ec7 100644 --- a/rt/share/html/NoAuth/css/base/superfish-navbar.css +++ b/rt/share/html/NoAuth/css/base/superfish-navbar.css @@ -90,4 +90,6 @@ ul.sf-navbar .current ul ul { -moz-border-radius-topright: 0; -webkit-border-top-right-radius: 0; -webkit-border-bottom-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; } diff --git a/rt/share/html/NoAuth/css/base/superfish.css b/rt/share/html/NoAuth/css/base/superfish.css index 31198e423..7cb3b567c 100644 --- a/rt/share/html/NoAuth/css/base/superfish.css +++ b/rt/share/html/NoAuth/css/base/superfish.css @@ -130,6 +130,8 @@ li.sfHover > a > .sf-sub-indicator { -moz-border-radius-topright: 17px; -webkit-border-top-right-radius: 17px; -webkit-border-bottom-left-radius: 17px; + border-top-right-radius: 17px; + border-bottom-left-radius: 17px; } .sf-shadow ul.sf-shadow-off { background: transparent; diff --git a/rt/share/html/NoAuth/css/base/ticket-form.css b/rt/share/html/NoAuth/css/base/ticket-form.css index daab263b1..869eba774 100644 --- a/rt/share/html/NoAuth/css/base/ticket-form.css +++ b/rt/share/html/NoAuth/css/base/ticket-form.css @@ -82,21 +82,17 @@ iframe.richtext-editor { .messagebox-container.action-response iframe { background-color: #fcc !important; -} - -/* -% if ( RT->Config->Get("UseSideBySideLayout", $session{'CurrentUser'}) ) { -*/ +} -#ticket-create-metadata, -#ticket-update-metadata { +.sidebyside #ticket-create-metadata, +.sidebyside #ticket-update-metadata { float: right; width: 40%; clear: right; } -#ticket-create-message, -#ticket-update-message { +.sidebyside #ticket-create-message, +.sidebyside #ticket-update-message { float: left; width: 58%; clear: left; @@ -104,10 +100,10 @@ iframe.richtext-editor { @media (max-width: 950px) { /* Revert to a single column when we're less than 1000px wide */ - #ticket-create-metadata, - #ticket-update-metadata, - #ticket-create-message, - #ticket-update-message + .sidebyside #ticket-create-metadata, + .sidebyside #ticket-update-metadata, + .sidebyside #ticket-create-message, + .sidebyside #ticket-update-message { float: none; width: auto; @@ -115,15 +111,12 @@ iframe.richtext-editor { } } -#comp-Ticket-Update #body { +.sidebyside #comp-Ticket-Update #body { padding-top: 3em; } -#ticket-create-message .button[name="AddMoreAttach"], -#ticket-update-message .button[name="AddMoreAttach"] { +.sidebyside #ticket-create-message .button[name="AddMoreAttach"], +.sidebyside #ticket-update-message .button[name="AddMoreAttach"] { float: right; } -/* -% } -*/ diff --git a/rt/share/html/NoAuth/css/base/ui.timepickr.css b/rt/share/html/NoAuth/css/base/ui.timepickr.css deleted file mode 100644 index e2dacf7a9..000000000 --- a/rt/share/html/NoAuth/css/base/ui.timepickr.css +++ /dev/null @@ -1,56 +0,0 @@ -/* - jQuery ui.timepickr - http://code.google.com/p/jquery-utils/ - - copyright Maxime Haineault <haineault@gmail.com> - http://haineault.com - - MIT License (http://www.opensource.org/licenses/mit-license.php -*/ -.ui-timepickr { - position:absolute; - width:480px; -} - -.ui-timepickr-row { - margin:0; - padding:0; - margin-top:2px; - display:none; - position:relative; -} - -.ui-timepickr-button { - float:left; - margin:0; - padding:0; - list-style:none; - list-style-type:none; -} - -.ui-timepickr-button span { - font-size:.7em; - padding:4px 6px 4px 6px; - margin-left:2px; - text-align:center; - cursor:pointer; - display:block; - text-align:center; - - - /* system theme (default) */ - border-width:1px; - border-style:solid; - /*border-color:ThreeDLightShadow ThreeDShadow ThreeDShadow ThreeDLightShadow; - color:ButtonText; - background:ButtonFace;*/ -} - -.ui-timepickr-button span.ui-state-hover { - /*color:HighlightText; - background:Highlight;*/ -} - -.ui-state-hover span { - /*background:#c30;*/ -} diff --git a/rt/share/html/NoAuth/css/base/ui.timepickr.custom.css b/rt/share/html/NoAuth/css/base/ui.timepickr.custom.css deleted file mode 100644 index ad2aa66ce..000000000 --- a/rt/share/html/NoAuth/css/base/ui.timepickr.custom.css +++ /dev/null @@ -1,54 +0,0 @@ -%# BEGIN BPS TAGGED BLOCK {{{ -%# -%# COPYRIGHT: -%# -%# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC -%# <sales@bestpractical.com> -%# -%# (Except where explicitly superseded by other copyright notices) -%# -%# -%# LICENSE: -%# -%# This work is made available to you under the terms of Version 2 of -%# the GNU General Public License. A copy of that license should have -%# been provided with this software, but in any event can be snarfed -%# from www.gnu.org. -%# -%# This work is distributed in the hope that it will be useful, but -%# WITHOUT ANY WARRANTY; without even the implied warranty of -%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%# General Public License for more details. -%# -%# You should have received a copy of the GNU General Public License -%# along with this program; if not, write to the Free Software -%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -%# 02110-1301 or visit their web page on the internet at -%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -%# -%# -%# CONTRIBUTION SUBMISSION POLICY: -%# -%# (The following paragraph is not intended to limit the rights granted -%# to you to modify and distribute this software under the terms of -%# the GNU General Public License and is only of importance to you if -%# you choose to contribute your changes and enhancements to the -%# community by submitting them to Best Practical Solutions, LLC.) -%# -%# By intentionally submitting any modifications, corrections or -%# derivatives to this work, or any other work intended for use with -%# Request Tracker, to Best Practical Solutions, LLC, you confirm that -%# you are the copyright holder for those contributions and you grant -%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -%# royalty-free, perpetual, license to use, copy, create derivative -%# works based on those contributions, and sublicense and distribute -%# those contributions and any derivatives thereof. -%# -%# END BPS TAGGED BLOCK }}} -.ui-timepickr { - font-size: 1.1em; -} - -.ui-timepickr-button span { - background: white; -} diff --git a/rt/share/html/NoAuth/css/web2/nav.css b/rt/share/html/NoAuth/css/web2/nav.css index be63c5984..e404b61c8 100644 --- a/rt/share/html/NoAuth/css/web2/nav.css +++ b/rt/share/html/NoAuth/css/web2/nav.css @@ -239,6 +239,7 @@ border: 1px solid #ccc; -moz-border-radius-bottomleft: 0.5em; -webkit-border-bottom-left-radius: 0.5em; + border-bottom-left-radius: 0.5em; border-right: none; border-top: none; list-style-type: none; diff --git a/rt/share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js b/rt/share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js index e90b4fe4b..0466005dc 100644 --- a/rt/share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js +++ b/rt/share/html/NoAuth/js/jquery-ui-1.8.4.custom.min.js @@ -222,3 +222,53 @@ c=this._daylightSavingAdjust(new Date(c,e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(t function(a){if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b)); return this.each(function(){typeof a=="string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new L;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.4";window["DP_jQuery_"+y]=d})(jQuery); ; +/*! + * jQuery UI Mouse 1.8.4 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Mouse + * + * Depends: + * jquery.ui.widget.js + */ +(function(c){c.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(b){return a._mouseDown(b)}).bind("click."+this.widgetName,function(b){if(a._preventClickEvent){a._preventClickEvent=false;b.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName)},_mouseDown:function(a){a.originalEvent=a.originalEvent||{};if(!a.originalEvent.mouseHandled){this._mouseStarted&& +this._mouseUp(a);this._mouseDownEvent=a;var b=this,e=a.which==1,f=typeof this.options.cancel=="string"?c(a.target).parents().add(a.target).filter(this.options.cancel).length:false;if(!e||f||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){b.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted=this._mouseStart(a)!==false;if(!this._mouseStarted){a.preventDefault(); +return true}}this._mouseMoveDelegate=function(d){return b._mouseMove(d)};this._mouseUpDelegate=function(d){return b._mouseUp(d)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);c.browser.safari||a.preventDefault();return a.originalEvent.mouseHandled=true}},_mouseMove:function(a){if(c.browser.msie&&!(document.documentMode>=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&& +this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=a.target==this._mouseDownEvent.target;this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX- +a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); +/* + * jQuery UI Slider 1.8.4 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function(d){d.widget("ui.slider",d.ui.mouse,{widgetEventPrefix:"slide",options:{animate:false,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null},_create:function(){var a=this,b=this.options;this._mouseSliding=this._keySliding=false;this._animateOff=true;this._handleIndex=null;this._detectOrientation();this._mouseInit();this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget ui-widget-content ui-corner-all");b.disabled&&this.element.addClass("ui-slider-disabled ui-disabled"); +this.range=d([]);if(b.range){if(b.range===true){this.range=d("<div></div>");if(!b.values)b.values=[this._valueMin(),this._valueMin()];if(b.values.length&&b.values.length!==2)b.values=[b.values[0],b.values[0]]}else this.range=d("<div></div>");this.range.appendTo(this.element).addClass("ui-slider-range");if(b.range==="min"||b.range==="max")this.range.addClass("ui-slider-range-"+b.range);this.range.addClass("ui-widget-header")}d(".ui-slider-handle",this.element).length===0&&d("<a href='#'></a>").appendTo(this.element).addClass("ui-slider-handle"); +if(b.values&&b.values.length)for(;d(".ui-slider-handle",this.element).length<b.values.length;)d("<a href='#'></a>").appendTo(this.element).addClass("ui-slider-handle");this.handles=d(".ui-slider-handle",this.element).addClass("ui-state-default ui-corner-all");this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(c){c.preventDefault()}).hover(function(){b.disabled||d(this).addClass("ui-state-hover")},function(){d(this).removeClass("ui-state-hover")}).focus(function(){if(b.disabled)d(this).blur(); +else{d(".ui-slider .ui-state-focus").removeClass("ui-state-focus");d(this).addClass("ui-state-focus")}}).blur(function(){d(this).removeClass("ui-state-focus")});this.handles.each(function(c){d(this).data("index.ui-slider-handle",c)});this.handles.keydown(function(c){var e=true,f=d(this).data("index.ui-slider-handle"),h,g,i;if(!a.options.disabled){switch(c.keyCode){case d.ui.keyCode.HOME:case d.ui.keyCode.END:case d.ui.keyCode.PAGE_UP:case d.ui.keyCode.PAGE_DOWN:case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:e= +false;if(!a._keySliding){a._keySliding=true;d(this).addClass("ui-state-active");h=a._start(c,f);if(h===false)return}break}i=a.options.step;h=a.options.values&&a.options.values.length?(g=a.values(f)):(g=a.value());switch(c.keyCode){case d.ui.keyCode.HOME:g=a._valueMin();break;case d.ui.keyCode.END:g=a._valueMax();break;case d.ui.keyCode.PAGE_UP:g=a._trimAlignValue(h+(a._valueMax()-a._valueMin())/5);break;case d.ui.keyCode.PAGE_DOWN:g=a._trimAlignValue(h-(a._valueMax()-a._valueMin())/5);break;case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:if(h=== +a._valueMax())return;g=a._trimAlignValue(h+i);break;case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:if(h===a._valueMin())return;g=a._trimAlignValue(h-i);break}a._slide(c,f,g);return e}}).keyup(function(c){var e=d(this).data("index.ui-slider-handle");if(a._keySliding){a._keySliding=false;a._stop(c,e);a._change(c,e);d(this).removeClass("ui-state-active")}});this._refreshValue();this._animateOff=false},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider"); +this._mouseDestroy();return this},_mouseCapture:function(a){var b=this.options,c,e,f,h,g;if(b.disabled)return false;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();c=this._normValueFromMouse({x:a.pageX,y:a.pageY});e=this._valueMax()-this._valueMin()+1;h=this;this.handles.each(function(i){var j=Math.abs(c-h.values(i));if(e>j){e=j;f=d(this);g=i}});if(b.range===true&&this.values(1)===b.min){g+=1;f=d(this.handles[g])}if(this._start(a, +g)===false)return false;this._mouseSliding=true;h._handleIndex=g;f.addClass("ui-state-active").focus();b=f.offset();this._clickOffset=!d(a.target).parents().andSelf().is(".ui-slider-handle")?{left:0,top:0}:{left:a.pageX-b.left-f.width()/2,top:a.pageY-b.top-f.height()/2-(parseInt(f.css("borderTopWidth"),10)||0)-(parseInt(f.css("borderBottomWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0)};this._slide(a,g,c);return this._animateOff=true},_mouseStart:function(){return true},_mouseDrag:function(a){var b= +this._normValueFromMouse({x:a.pageX,y:a.pageY});this._slide(a,this._handleIndex,b);return false},_mouseStop:function(a){this.handles.removeClass("ui-state-active");this._mouseSliding=false;this._stop(a,this._handleIndex);this._change(a,this._handleIndex);this._clickOffset=this._handleIndex=null;return this._animateOff=false},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(a){var b;if(this.orientation==="horizontal"){b= +this.elementSize.width;a=a.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{b=this.elementSize.height;a=a.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}b=a/b;if(b>1)b=1;if(b<0)b=0;if(this.orientation==="vertical")b=1-b;a=this._valueMax()-this._valueMin();return this._trimAlignValue(this._valueMin()+b*a)},_start:function(a,b){var c={handle:this.handles[b],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(b); +c.values=this.values()}return this._trigger("start",a,c)},_slide:function(a,b,c){var e;if(this.options.values&&this.options.values.length){e=this.values(b?0:1);if(this.options.values.length===2&&this.options.range===true&&(b===0&&c>e||b===1&&c<e))c=e;if(c!==this.values(b)){e=this.values();e[b]=c;a=this._trigger("slide",a,{handle:this.handles[b],value:c,values:e});this.values(b?0:1);a!==false&&this.values(b,c,true)}}else if(c!==this.value()){a=this._trigger("slide",a,{handle:this.handles[b],value:c}); +a!==false&&this.value(c)}},_stop:function(a,b){var c={handle:this.handles[b],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(b);c.values=this.values()}this._trigger("stop",a,c)},_change:function(a,b){if(!this._keySliding&&!this._mouseSliding){var c={handle:this.handles[b],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(b);c.values=this.values()}this._trigger("change",a,c)}},value:function(a){if(arguments.length){this.options.value= +this._trimAlignValue(a);this._refreshValue();this._change(null,0)}return this._value()},values:function(a,b){var c,e,f;if(arguments.length>1){this.options.values[a]=this._trimAlignValue(b);this._refreshValue();this._change(null,a)}if(arguments.length)if(d.isArray(arguments[0])){c=this.options.values;e=arguments[0];for(f=0;f<c.length;f+=1){c[f]=this._trimAlignValue(e[f]);this._change(null,f)}this._refreshValue()}else return this.options.values&&this.options.values.length?this._values(a):this.value(); +else return this._values()},_setOption:function(a,b){var c,e=0;if(d.isArray(this.options.values))e=this.options.values.length;d.Widget.prototype._setOption.apply(this,arguments);switch(a){case "disabled":if(b){this.handles.filter(".ui-state-focus").blur();this.handles.removeClass("ui-state-hover");this.handles.attr("disabled","disabled");this.element.addClass("ui-disabled")}else{this.handles.removeAttr("disabled");this.element.removeClass("ui-disabled")}break;case "orientation":this._detectOrientation(); +this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation);this._refreshValue();break;case "value":this._animateOff=true;this._refreshValue();this._change(null,0);this._animateOff=false;break;case "values":this._animateOff=true;this._refreshValue();for(c=0;c<e;c+=1)this._change(null,c);this._animateOff=false;break}},_value:function(){var a=this.options.value;return a=this._trimAlignValue(a)},_values:function(a){var b,c;if(arguments.length){b=this.options.values[a]; +return b=this._trimAlignValue(b)}else{b=this.options.values.slice();for(c=0;c<b.length;c+=1)b[c]=this._trimAlignValue(b[c]);return b}},_trimAlignValue:function(a){if(a<this._valueMin())return this._valueMin();if(a>this._valueMax())return this._valueMax();var b=this.options.step>0?this.options.step:1,c=a%b;a=a-c;if(Math.abs(c)*2>=b)a+=c>0?b:-b;return parseFloat(a.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var a= +this.options.range,b=this.options,c=this,e=!this._animateOff?b.animate:false,f,h={},g,i,j,l;if(this.options.values&&this.options.values.length)this.handles.each(function(k){f=(c.values(k)-c._valueMin())/(c._valueMax()-c._valueMin())*100;h[c.orientation==="horizontal"?"left":"bottom"]=f+"%";d(this).stop(1,1)[e?"animate":"css"](h,b.animate);if(c.options.range===true)if(c.orientation==="horizontal"){if(k===0)c.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},b.animate);if(k===1)c.range[e?"animate":"css"]({width:f- +g+"%"},{queue:false,duration:b.animate})}else{if(k===0)c.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},b.animate);if(k===1)c.range[e?"animate":"css"]({height:f-g+"%"},{queue:false,duration:b.animate})}g=f});else{i=this.value();j=this._valueMin();l=this._valueMax();f=l!==j?(i-j)/(l-j)*100:0;h[c.orientation==="horizontal"?"left":"bottom"]=f+"%";this.handle.stop(1,1)[e?"animate":"css"](h,b.animate);if(a==="min"&&this.orientation==="horizontal")this.range.stop(1,1)[e?"animate":"css"]({width:f+"%"}, +b.animate);if(a==="max"&&this.orientation==="horizontal")this.range[e?"animate":"css"]({width:100-f+"%"},{queue:false,duration:b.animate});if(a==="min"&&this.orientation==="vertical")this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},b.animate);if(a==="max"&&this.orientation==="vertical")this.range[e?"animate":"css"]({height:100-f+"%"},{queue:false,duration:b.animate})}}});d.extend(d.ui.slider,{version:"1.8.4"})})(jQuery); diff --git a/rt/share/html/NoAuth/js/jquery-ui-patch-datepicker.js b/rt/share/html/NoAuth/js/jquery-ui-patch-datepicker.js index 40cc0db99..2ac101f93 100644 --- a/rt/share/html/NoAuth/js/jquery-ui-patch-datepicker.js +++ b/rt/share/html/NoAuth/js/jquery-ui-patch-datepicker.js @@ -58,4 +58,35 @@ return data; }; + + $.datepicker._checkOffset_orig = $.datepicker._checkOffset; + $.datepicker._checkOffset = function(inst, offset, isFixed) { + // copied from the original + var dpHeight = inst.dpDiv.outerHeight(); + var inputHeight = inst.input ? inst.input.outerHeight() : 0; + var viewHeight = document.documentElement.clientHeight + $(document).scrollTop(); + + // save the original offset rather than the new offset because the + // original function modifies the passed arg as a side-effect + var old_offset = { top: offset.top, left: offset.left }; + offset = $.datepicker._checkOffset_orig(inst, offset, isFixed); + + // Negate any up or down positioning by adding instead of subtracting + offset.top += Math.min(old_offset.top, (old_offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? + Math.abs(dpHeight + inputHeight) : 0); + + return offset; + }; + + + $.timepicker._newInst_orig = $.timepicker._newInst; + $.timepicker._newInst = function($input, o) { + var tp_inst = $.timepicker._newInst_orig($input, o); + tp_inst._defaults.onClose = function(dateText, dp_inst) { + if ($.isFunction(o.onClose)) + o.onClose.call($input[0], dateText, dp_inst, tp_inst); + }; + return tp_inst; + }; + })(jQuery); diff --git a/rt/share/html/NoAuth/js/jquery-ui-timepicker-addon.js b/rt/share/html/NoAuth/js/jquery-ui-timepicker-addon.js new file mode 100644 index 000000000..0a4ff026e --- /dev/null +++ b/rt/share/html/NoAuth/js/jquery-ui-timepicker-addon.js @@ -0,0 +1,1326 @@ +/* +* jQuery timepicker addon +* By: Trent Richardson [http://trentrichardson.com] +* Version 1.0.0 +* Last Modified: 02/05/2012 +* +* Copyright 2012 Trent Richardson +* Dual licensed under the MIT and GPL licenses. +* http://trentrichardson.com/Impromptu/GPL-LICENSE.txt +* http://trentrichardson.com/Impromptu/MIT-LICENSE.txt +* +* HERES THE CSS: +* .ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } +* .ui-timepicker-div dl { text-align: left; } +* .ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } +* .ui-timepicker-div dl dd { margin: 0 10px 10px 65px; } +* .ui-timepicker-div td { font-size: 90%; } +* .ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } +*/ + +(function($) { + +// Prevent "Uncaught RangeError: Maximum call stack size exceeded" +$.ui.timepicker = $.ui.timepicker || {}; +if ($.ui.timepicker.version) { + return; +} + +$.extend($.ui, { timepicker: { version: "1.0.0" } }); + +/* Time picker manager. + Use the singleton instance of this class, $.timepicker, to interact with the time picker. + Settings for (groups of) time pickers are maintained in an instance object, + allowing multiple different settings on the same page. */ + +function Timepicker() { + this.regional = []; // Available regional settings, indexed by language code + this.regional[''] = { // Default regional settings + currentText: 'Now', + closeText: 'Done', + ampm: false, + amNames: ['AM', 'A'], + pmNames: ['PM', 'P'], + timeFormat: 'hh:mm tt', + timeSuffix: '', + timeOnlyTitle: 'Choose Time', + timeText: 'Time', + hourText: 'Hour', + minuteText: 'Minute', + secondText: 'Second', + millisecText: 'Millisecond', + timezoneText: 'Time Zone' + }; + this._defaults = { // Global defaults for all the datetime picker instances + showButtonPanel: true, + timeOnly: false, + showHour: true, + showMinute: true, + showSecond: false, + showMillisec: false, + showTimezone: false, + showTime: true, + stepHour: 1, + stepMinute: 1, + stepSecond: 1, + stepMillisec: 1, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + timezone: '+0000', + hourMin: 0, + minuteMin: 0, + secondMin: 0, + millisecMin: 0, + hourMax: 23, + minuteMax: 59, + secondMax: 59, + millisecMax: 999, + minDateTime: null, + maxDateTime: null, + onSelect: null, + hourGrid: 0, + minuteGrid: 0, + secondGrid: 0, + millisecGrid: 0, + alwaysSetTime: true, + separator: ' ', + altFieldTimeOnly: true, + showTimepicker: true, + timezoneIso8609: false, + timezoneList: null, + addSliderAccess: false, + sliderAccessArgs: null + }; + $.extend(this._defaults, this.regional['']); +}; + +$.extend(Timepicker.prototype, { + $input: null, + $altInput: null, + $timeObj: null, + inst: null, + hour_slider: null, + minute_slider: null, + second_slider: null, + millisec_slider: null, + timezone_select: null, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + timezone: '+0000', + hourMinOriginal: null, + minuteMinOriginal: null, + secondMinOriginal: null, + millisecMinOriginal: null, + hourMaxOriginal: null, + minuteMaxOriginal: null, + secondMaxOriginal: null, + millisecMaxOriginal: null, + ampm: '', + formattedDate: '', + formattedTime: '', + formattedDateTime: '', + timezoneList: null, + + /* Override the default settings for all instances of the time picker. + @param settings object - the new settings to use as defaults (anonymous object) + @return the manager object */ + setDefaults: function(settings) { + extendRemove(this._defaults, settings || {}); + return this; + }, + + //######################################################################## + // Create a new Timepicker instance + //######################################################################## + _newInst: function($input, o) { + var tp_inst = new Timepicker(), + inlineSettings = {}; + + for (var attrName in this._defaults) { + var attrValue = $input.attr('time:' + attrName); + if (attrValue) { + try { + inlineSettings[attrName] = eval(attrValue); + } catch (err) { + inlineSettings[attrName] = attrValue; + } + } + } + tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, o, { + beforeShow: function(input, dp_inst) { + if ($.isFunction(o.beforeShow)) + return o.beforeShow(input, dp_inst, tp_inst); + }, + onChangeMonthYear: function(year, month, dp_inst) { + // Update the time as well : this prevents the time from disappearing from the $input field. + tp_inst._updateDateTime(dp_inst); + if ($.isFunction(o.onChangeMonthYear)) + o.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst); + }, + onClose: function(dateText, dp_inst) { + if (tp_inst.timeDefined === true && $input.val() != '') + tp_inst._updateDateTime(dp_inst); + if ($.isFunction(o.onClose)) + o.onClose.call($input[0], dateText, dp_inst, tp_inst); + }, + timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker'); + }); + tp_inst.amNames = $.map(tp_inst._defaults.amNames, function(val) { return val.toUpperCase(); }); + tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function(val) { return val.toUpperCase(); }); + + if (tp_inst._defaults.timezoneList === null) { + var timezoneList = []; + for (var i = -11; i <= 12; i++) + timezoneList.push((i >= 0 ? '+' : '-') + ('0' + Math.abs(i).toString()).slice(-2) + '00'); + if (tp_inst._defaults.timezoneIso8609) + timezoneList = $.map(timezoneList, function(val) { + return val == '+0000' ? 'Z' : (val.substring(0, 3) + ':' + val.substring(3)); + }); + tp_inst._defaults.timezoneList = timezoneList; + } + + tp_inst.hour = tp_inst._defaults.hour; + tp_inst.minute = tp_inst._defaults.minute; + tp_inst.second = tp_inst._defaults.second; + tp_inst.millisec = tp_inst._defaults.millisec; + tp_inst.ampm = ''; + tp_inst.$input = $input; + + if (o.altField) + tp_inst.$altInput = $(o.altField) + .css({ cursor: 'pointer' }) + .focus(function(){ $input.trigger("focus"); }); + + if(tp_inst._defaults.minDate==0 || tp_inst._defaults.minDateTime==0) + { + tp_inst._defaults.minDate=new Date(); + } + if(tp_inst._defaults.maxDate==0 || tp_inst._defaults.maxDateTime==0) + { + tp_inst._defaults.maxDate=new Date(); + } + + // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime.. + if(tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) + tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime()); + if(tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) + tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime()); + if(tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) + tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime()); + if(tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) + tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime()); + return tp_inst; + }, + + //######################################################################## + // add our sliders to the calendar + //######################################################################## + _addTimePicker: function(dp_inst) { + var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ? + this.$input.val() + ' ' + this.$altInput.val() : + this.$input.val(); + + this.timeDefined = this._parseTime(currDT); + this._limitMinMaxDateTime(dp_inst, false); + this._injectTimePicker(); + }, + + //######################################################################## + // parse the time string from input value or _setTime + //######################################################################## + _parseTime: function(timeString, withDate) { + var regstr = this._defaults.timeFormat.toString() + .replace(/h{1,2}/ig, '(\\d?\\d)') + .replace(/m{1,2}/ig, '(\\d?\\d)') + .replace(/s{1,2}/ig, '(\\d?\\d)') + .replace(/l{1}/ig, '(\\d?\\d?\\d)') + .replace(/t{1,2}/ig, this._getPatternAmpm()) + .replace(/z{1}/ig, '(z|[-+]\\d\\d:?\\d\\d)?') + .replace(/\s/g, '\\s?') + this._defaults.timeSuffix + '$', + order = this._getFormatPositions(), + ampm = '', + treg; + + if (!this.inst) this.inst = $.datepicker._getInst(this.$input[0]); + + if (withDate || !this._defaults.timeOnly) { + // the time should come after x number of characters and a space. + // x = at least the length of text specified by the date format + var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat'); + // escape special regex characters in the seperator + var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g"); + regstr = '^.{' + dp_dateFormat.length + ',}?' + this._defaults.separator.replace(specials, "\\$&") + regstr; + } + + treg = timeString.match(new RegExp(regstr, 'i')); + + if (treg) { + if (order.t !== -1) { + if (treg[order.t] === undefined || treg[order.t].length === 0) { + ampm = ''; + this.ampm = ''; + } else { + ampm = $.inArray(treg[order.t].toUpperCase(), this.amNames) !== -1 ? 'AM' : 'PM'; + this.ampm = this._defaults[ampm == 'AM' ? 'amNames' : 'pmNames'][0]; + } + } + + if (order.h !== -1) { + if (ampm == 'AM' && treg[order.h] == '12') + this.hour = 0; // 12am = 0 hour + else if (ampm == 'PM' && treg[order.h] != '12') + this.hour = (parseFloat(treg[order.h]) + 12).toFixed(0); // 12pm = 12 hour, any other pm = hour + 12 + else this.hour = Number(treg[order.h]); + } + + if (order.m !== -1) this.minute = Number(treg[order.m]); + if (order.s !== -1) this.second = Number(treg[order.s]); + if (order.l !== -1) this.millisec = Number(treg[order.l]); + if (order.z !== -1 && treg[order.z] !== undefined) { + var tz = treg[order.z].toUpperCase(); + switch (tz.length) { + case 1: // Z + tz = this._defaults.timezoneIso8609 ? 'Z' : '+0000'; + break; + case 5: // +hhmm + if (this._defaults.timezoneIso8609) + tz = tz.substring(1) == '0000' + ? 'Z' + : tz.substring(0, 3) + ':' + tz.substring(3); + break; + case 6: // +hh:mm + if (!this._defaults.timezoneIso8609) + tz = tz == 'Z' || tz.substring(1) == '00:00' + ? '+0000' + : tz.replace(/:/, ''); + else if (tz.substring(1) == '00:00') + tz = 'Z'; + break; + } + this.timezone = tz; + } + + return true; + + } + return false; + }, + + //######################################################################## + // pattern for standard and localized AM/PM markers + //######################################################################## + _getPatternAmpm: function() { + var markers = [], + o = this._defaults; + if (o.amNames) + $.merge(markers, o.amNames); + if (o.pmNames) + $.merge(markers, o.pmNames); + markers = $.map(markers, function(val) { return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&'); }); + return '(' + markers.join('|') + ')?'; + }, + + //######################################################################## + // figure out position of time elements.. cause js cant do named captures + //######################################################################## + _getFormatPositions: function() { + var finds = this._defaults.timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|t{1,2}|z)/g), + orders = { h: -1, m: -1, s: -1, l: -1, t: -1, z: -1 }; + + if (finds) + for (var i = 0; i < finds.length; i++) + if (orders[finds[i].toString().charAt(0)] == -1) + orders[finds[i].toString().charAt(0)] = i + 1; + + return orders; + }, + + //######################################################################## + // generate and inject html for timepicker into ui datepicker + //######################################################################## + _injectTimePicker: function() { + var $dp = this.inst.dpDiv, + o = this._defaults, + tp_inst = this, + // Added by Peter Medeiros: + // - Figure out what the hour/minute/second max should be based on the step values. + // - Example: if stepMinute is 15, then minMax is 45. + hourMax = parseInt((o.hourMax - ((o.hourMax - o.hourMin) % o.stepHour)) ,10), + minMax = parseInt((o.minuteMax - ((o.minuteMax - o.minuteMin) % o.stepMinute)) ,10), + secMax = parseInt((o.secondMax - ((o.secondMax - o.secondMin) % o.stepSecond)) ,10), + millisecMax = parseInt((o.millisecMax - ((o.millisecMax - o.millisecMin) % o.stepMillisec)) ,10), + dp_id = this.inst.id.toString().replace(/([^A-Za-z0-9_])/g, ''); + + // Prevent displaying twice + //if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0) { + if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0 && o.showTimepicker) { + var noDisplay = ' style="display:none;"', + html = '<div class="ui-timepicker-div" id="ui-timepicker-div-' + dp_id + '"><dl>' + + '<dt class="ui_tpicker_time_label" id="ui_tpicker_time_label_' + dp_id + '"' + + ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' + + '<dd class="ui_tpicker_time" id="ui_tpicker_time_' + dp_id + '"' + + ((o.showTime) ? '' : noDisplay) + '></dd>' + + '<dt class="ui_tpicker_hour_label" id="ui_tpicker_hour_label_' + dp_id + '"' + + ((o.showHour) ? '' : noDisplay) + '>' + o.hourText + '</dt>', + hourGridSize = 0, + minuteGridSize = 0, + secondGridSize = 0, + millisecGridSize = 0, + size = null; + + // Hours + html += '<dd class="ui_tpicker_hour"><div id="ui_tpicker_hour_' + dp_id + '"' + + ((o.showHour) ? '' : noDisplay) + '></div>'; + if (o.showHour && o.hourGrid > 0) { + html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>'; + + for (var h = o.hourMin; h <= hourMax; h += parseInt(o.hourGrid,10)) { + hourGridSize++; + var tmph = (o.ampm && h > 12) ? h-12 : h; + if (tmph < 10) tmph = '0' + tmph; + if (o.ampm) { + if (h == 0) tmph = 12 +'a'; + else if (h < 12) tmph += 'a'; + else tmph += 'p'; + } + html += '<td>' + tmph + '</td>'; + } + + html += '</tr></table></div>'; + } + html += '</dd>'; + + // Minutes + html += '<dt class="ui_tpicker_minute_label" id="ui_tpicker_minute_label_' + dp_id + '"' + + ((o.showMinute) ? '' : noDisplay) + '>' + o.minuteText + '</dt>'+ + '<dd class="ui_tpicker_minute"><div id="ui_tpicker_minute_' + dp_id + '"' + + ((o.showMinute) ? '' : noDisplay) + '></div>'; + + if (o.showMinute && o.minuteGrid > 0) { + html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>'; + + for (var m = o.minuteMin; m <= minMax; m += parseInt(o.minuteGrid,10)) { + minuteGridSize++; + html += '<td>' + ((m < 10) ? '0' : '') + m + '</td>'; + } + + html += '</tr></table></div>'; + } + html += '</dd>'; + + // Seconds + html += '<dt class="ui_tpicker_second_label" id="ui_tpicker_second_label_' + dp_id + '"' + + ((o.showSecond) ? '' : noDisplay) + '>' + o.secondText + '</dt>'+ + '<dd class="ui_tpicker_second"><div id="ui_tpicker_second_' + dp_id + '"'+ + ((o.showSecond) ? '' : noDisplay) + '></div>'; + + if (o.showSecond && o.secondGrid > 0) { + html += '<div style="padding-left: 1px"><table><tr>'; + + for (var s = o.secondMin; s <= secMax; s += parseInt(o.secondGrid,10)) { + secondGridSize++; + html += '<td>' + ((s < 10) ? '0' : '') + s + '</td>'; + } + + html += '</tr></table></div>'; + } + html += '</dd>'; + + // Milliseconds + html += '<dt class="ui_tpicker_millisec_label" id="ui_tpicker_millisec_label_' + dp_id + '"' + + ((o.showMillisec) ? '' : noDisplay) + '>' + o.millisecText + '</dt>'+ + '<dd class="ui_tpicker_millisec"><div id="ui_tpicker_millisec_' + dp_id + '"'+ + ((o.showMillisec) ? '' : noDisplay) + '></div>'; + + if (o.showMillisec && o.millisecGrid > 0) { + html += '<div style="padding-left: 1px"><table><tr>'; + + for (var l = o.millisecMin; l <= millisecMax; l += parseInt(o.millisecGrid,10)) { + millisecGridSize++; + html += '<td>' + ((l < 10) ? '0' : '') + l + '</td>'; + } + + html += '</tr></table></div>'; + } + html += '</dd>'; + + // Timezone + html += '<dt class="ui_tpicker_timezone_label" id="ui_tpicker_timezone_label_' + dp_id + '"' + + ((o.showTimezone) ? '' : noDisplay) + '>' + o.timezoneText + '</dt>'; + html += '<dd class="ui_tpicker_timezone" id="ui_tpicker_timezone_' + dp_id + '"' + + ((o.showTimezone) ? '' : noDisplay) + '></dd>'; + + html += '</dl></div>'; + $tp = $(html); + + // if we only want time picker... + if (o.timeOnly === true) { + $tp.prepend( + '<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' + + '<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' + + '</div>'); + $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide(); + } + + this.hour_slider = $tp.find('#ui_tpicker_hour_'+ dp_id).slider({ + orientation: "horizontal", + value: this.hour, + min: o.hourMin, + max: hourMax, + step: o.stepHour, + slide: function(event, ui) { + tp_inst.hour_slider.slider( "option", "value", ui.value); + tp_inst._onTimeChange(); + } + }); + + + // Updated by Peter Medeiros: + // - Pass in Event and UI instance into slide function + this.minute_slider = $tp.find('#ui_tpicker_minute_'+ dp_id).slider({ + orientation: "horizontal", + value: this.minute, + min: o.minuteMin, + max: minMax, + step: o.stepMinute, + slide: function(event, ui) { + tp_inst.minute_slider.slider( "option", "value", ui.value); + tp_inst._onTimeChange(); + } + }); + + this.second_slider = $tp.find('#ui_tpicker_second_'+ dp_id).slider({ + orientation: "horizontal", + value: this.second, + min: o.secondMin, + max: secMax, + step: o.stepSecond, + slide: function(event, ui) { + tp_inst.second_slider.slider( "option", "value", ui.value); + tp_inst._onTimeChange(); + } + }); + + this.millisec_slider = $tp.find('#ui_tpicker_millisec_'+ dp_id).slider({ + orientation: "horizontal", + value: this.millisec, + min: o.millisecMin, + max: millisecMax, + step: o.stepMillisec, + slide: function(event, ui) { + tp_inst.millisec_slider.slider( "option", "value", ui.value); + tp_inst._onTimeChange(); + } + }); + + this.timezone_select = $tp.find('#ui_tpicker_timezone_'+ dp_id).append('<select></select>').find("select"); + $.fn.append.apply(this.timezone_select, + $.map(o.timezoneList, function(val, idx) { + return $("<option />") + .val(typeof val == "object" ? val.value : val) + .text(typeof val == "object" ? val.label : val); + }) + ); + this.timezone_select.val((typeof this.timezone != "undefined" && this.timezone != null && this.timezone != "") ? this.timezone : o.timezone); + this.timezone_select.change(function() { + tp_inst._onTimeChange(); + }); + + // Add grid functionality + if (o.showHour && o.hourGrid > 0) { + size = 100 * hourGridSize * o.hourGrid / (hourMax - o.hourMin); + + $tp.find(".ui_tpicker_hour table").css({ + width: size + "%", + marginLeft: (size / (-2 * hourGridSize)) + "%", + borderCollapse: 'collapse' + }).find("td").each( function(index) { + $(this).click(function() { + var h = $(this).html(); + if(o.ampm) { + var ap = h.substring(2).toLowerCase(), + aph = parseInt(h.substring(0,2), 10); + if (ap == 'a') { + if (aph == 12) h = 0; + else h = aph; + } else if (aph == 12) h = 12; + else h = aph + 12; + } + tp_inst.hour_slider.slider("option", "value", h); + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }).css({ + cursor: 'pointer', + width: (100 / hourGridSize) + '%', + textAlign: 'center', + overflow: 'hidden' + }); + }); + } + + if (o.showMinute && o.minuteGrid > 0) { + size = 100 * minuteGridSize * o.minuteGrid / (minMax - o.minuteMin); + $tp.find(".ui_tpicker_minute table").css({ + width: size + "%", + marginLeft: (size / (-2 * minuteGridSize)) + "%", + borderCollapse: 'collapse' + }).find("td").each(function(index) { + $(this).click(function() { + tp_inst.minute_slider.slider("option", "value", $(this).html()); + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }).css({ + cursor: 'pointer', + width: (100 / minuteGridSize) + '%', + textAlign: 'center', + overflow: 'hidden' + }); + }); + } + + if (o.showSecond && o.secondGrid > 0) { + $tp.find(".ui_tpicker_second table").css({ + width: size + "%", + marginLeft: (size / (-2 * secondGridSize)) + "%", + borderCollapse: 'collapse' + }).find("td").each(function(index) { + $(this).click(function() { + tp_inst.second_slider.slider("option", "value", $(this).html()); + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }).css({ + cursor: 'pointer', + width: (100 / secondGridSize) + '%', + textAlign: 'center', + overflow: 'hidden' + }); + }); + } + + if (o.showMillisec && o.millisecGrid > 0) { + $tp.find(".ui_tpicker_millisec table").css({ + width: size + "%", + marginLeft: (size / (-2 * millisecGridSize)) + "%", + borderCollapse: 'collapse' + }).find("td").each(function(index) { + $(this).click(function() { + tp_inst.millisec_slider.slider("option", "value", $(this).html()); + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }).css({ + cursor: 'pointer', + width: (100 / millisecGridSize) + '%', + textAlign: 'center', + overflow: 'hidden' + }); + }); + } + + var $buttonPanel = $dp.find('.ui-datepicker-buttonpane'); + if ($buttonPanel.length) $buttonPanel.before($tp); + else $dp.append($tp); + + this.$timeObj = $tp.find('#ui_tpicker_time_'+ dp_id); + + if (this.inst !== null) { + var timeDefined = this.timeDefined; + this._onTimeChange(); + this.timeDefined = timeDefined; + } + + //Emulate datepicker onSelect behavior. Call on slidestop. + var onSelectDelegate = function() { + tp_inst._onSelectHandler(); + }; + this.hour_slider.bind('slidestop',onSelectDelegate); + this.minute_slider.bind('slidestop',onSelectDelegate); + this.second_slider.bind('slidestop',onSelectDelegate); + this.millisec_slider.bind('slidestop',onSelectDelegate); + + // slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/ + if (this._defaults.addSliderAccess){ + var sliderAccessArgs = this._defaults.sliderAccessArgs; + setTimeout(function(){ // fix for inline mode + if($tp.find('.ui-slider-access').length == 0){ + $tp.find('.ui-slider:visible').sliderAccess(sliderAccessArgs); + + // fix any grids since sliders are shorter + var sliderAccessWidth = $tp.find('.ui-slider-access:eq(0)').outerWidth(true); + if(sliderAccessWidth){ + $tp.find('table:visible').each(function(){ + var $g = $(this), + oldWidth = $g.outerWidth(), + oldMarginLeft = $g.css('marginLeft').toString().replace('%',''), + newWidth = oldWidth - sliderAccessWidth, + newMarginLeft = ((oldMarginLeft * newWidth)/oldWidth) + '%'; + + $g.css({ width: newWidth, marginLeft: newMarginLeft }); + }); + } + } + },0); + } + // end slideAccess integration + + } + }, + + //######################################################################## + // This function tries to limit the ability to go outside the + // min/max date range + //######################################################################## + _limitMinMaxDateTime: function(dp_inst, adjustSliders){ + var o = this._defaults, + dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay); + + if(!this._defaults.showTimepicker) return; // No time so nothing to check here + + if($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date){ + var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'), + minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0); + + if(this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null){ + this.hourMinOriginal = o.hourMin; + this.minuteMinOriginal = o.minuteMin; + this.secondMinOriginal = o.secondMin; + this.millisecMinOriginal = o.millisecMin; + } + + if(dp_inst.settings.timeOnly || minDateTimeDate.getTime() == dp_date.getTime()) { + this._defaults.hourMin = minDateTime.getHours(); + if (this.hour <= this._defaults.hourMin) { + this.hour = this._defaults.hourMin; + this._defaults.minuteMin = minDateTime.getMinutes(); + if (this.minute <= this._defaults.minuteMin) { + this.minute = this._defaults.minuteMin; + this._defaults.secondMin = minDateTime.getSeconds(); + } else if (this.second <= this._defaults.secondMin){ + this.second = this._defaults.secondMin; + this._defaults.millisecMin = minDateTime.getMilliseconds(); + } else { + if(this.millisec < this._defaults.millisecMin) + this.millisec = this._defaults.millisecMin; + this._defaults.millisecMin = this.millisecMinOriginal; + } + } else { + this._defaults.minuteMin = this.minuteMinOriginal; + this._defaults.secondMin = this.secondMinOriginal; + this._defaults.millisecMin = this.millisecMinOriginal; + } + }else{ + this._defaults.hourMin = this.hourMinOriginal; + this._defaults.minuteMin = this.minuteMinOriginal; + this._defaults.secondMin = this.secondMinOriginal; + this._defaults.millisecMin = this.millisecMinOriginal; + } + } + + if($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date){ + var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'), + maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0); + + if(this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null){ + this.hourMaxOriginal = o.hourMax; + this.minuteMaxOriginal = o.minuteMax; + this.secondMaxOriginal = o.secondMax; + this.millisecMaxOriginal = o.millisecMax; + } + + if(dp_inst.settings.timeOnly || maxDateTimeDate.getTime() == dp_date.getTime()){ + this._defaults.hourMax = maxDateTime.getHours(); + if (this.hour >= this._defaults.hourMax) { + this.hour = this._defaults.hourMax; + this._defaults.minuteMax = maxDateTime.getMinutes(); + if (this.minute >= this._defaults.minuteMax) { + this.minute = this._defaults.minuteMax; + this._defaults.secondMax = maxDateTime.getSeconds(); + } else if (this.second >= this._defaults.secondMax) { + this.second = this._defaults.secondMax; + this._defaults.millisecMax = maxDateTime.getMilliseconds(); + } else { + if(this.millisec > this._defaults.millisecMax) this.millisec = this._defaults.millisecMax; + this._defaults.millisecMax = this.millisecMaxOriginal; + } + } else { + this._defaults.minuteMax = this.minuteMaxOriginal; + this._defaults.secondMax = this.secondMaxOriginal; + this._defaults.millisecMax = this.millisecMaxOriginal; + } + }else{ + this._defaults.hourMax = this.hourMaxOriginal; + this._defaults.minuteMax = this.minuteMaxOriginal; + this._defaults.secondMax = this.secondMaxOriginal; + this._defaults.millisecMax = this.millisecMaxOriginal; + } + } + + if(adjustSliders !== undefined && adjustSliders === true){ + var hourMax = parseInt((this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)) ,10), + minMax = parseInt((this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)) ,10), + secMax = parseInt((this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)) ,10), + millisecMax = parseInt((this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)) ,10); + + if(this.hour_slider) + this.hour_slider.slider("option", { min: this._defaults.hourMin, max: hourMax }).slider('value', this.hour); + if(this.minute_slider) + this.minute_slider.slider("option", { min: this._defaults.minuteMin, max: minMax }).slider('value', this.minute); + if(this.second_slider) + this.second_slider.slider("option", { min: this._defaults.secondMin, max: secMax }).slider('value', this.second); + if(this.millisec_slider) + this.millisec_slider.slider("option", { min: this._defaults.millisecMin, max: millisecMax }).slider('value', this.millisec); + } + + }, + + + //######################################################################## + // when a slider moves, set the internal time... + // on time change is also called when the time is updated in the text field + //######################################################################## + _onTimeChange: function() { + var hour = (this.hour_slider) ? this.hour_slider.slider('value') : false, + minute = (this.minute_slider) ? this.minute_slider.slider('value') : false, + second = (this.second_slider) ? this.second_slider.slider('value') : false, + millisec = (this.millisec_slider) ? this.millisec_slider.slider('value') : false, + timezone = (this.timezone_select) ? this.timezone_select.val() : false, + o = this._defaults; + + if (typeof(hour) == 'object') hour = false; + if (typeof(minute) == 'object') minute = false; + if (typeof(second) == 'object') second = false; + if (typeof(millisec) == 'object') millisec = false; + if (typeof(timezone) == 'object') timezone = false; + + if (hour !== false) hour = parseInt(hour,10); + if (minute !== false) minute = parseInt(minute,10); + if (second !== false) second = parseInt(second,10); + if (millisec !== false) millisec = parseInt(millisec,10); + + var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0]; + + // If the update was done in the input field, the input field should not be updated. + // If the update was done using the sliders, update the input field. + var hasChanged = (hour != this.hour || minute != this.minute + || second != this.second || millisec != this.millisec + || (this.ampm.length > 0 + && (hour < 12) != ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1)) + || timezone != this.timezone); + + if (hasChanged) { + + if (hour !== false)this.hour = hour; + if (minute !== false) this.minute = minute; + if (second !== false) this.second = second; + if (millisec !== false) this.millisec = millisec; + if (timezone !== false) this.timezone = timezone; + + if (!this.inst) this.inst = $.datepicker._getInst(this.$input[0]); + + this._limitMinMaxDateTime(this.inst, true); + } + if (o.ampm) this.ampm = ampm; + + //this._formatTime(); + this.formattedTime = $.datepicker.formatTime(this._defaults.timeFormat, this, this._defaults); + if (this.$timeObj) this.$timeObj.text(this.formattedTime + o.timeSuffix); + this.timeDefined = true; + if (hasChanged) this._updateDateTime(); + }, + + //######################################################################## + // call custom onSelect. + // bind to sliders slidestop, and grid click. + //######################################################################## + _onSelectHandler: function() { + var onSelect = this._defaults.onSelect; + var inputEl = this.$input ? this.$input[0] : null; + if (onSelect && inputEl) { + onSelect.apply(inputEl, [this.formattedDateTime, this]); + } + }, + + //######################################################################## + // left for any backwards compatibility + //######################################################################## + _formatTime: function(time, format) { + time = time || { hour: this.hour, minute: this.minute, second: this.second, millisec: this.millisec, ampm: this.ampm, timezone: this.timezone }; + var tmptime = (format || this._defaults.timeFormat).toString(); + + tmptime = $.datepicker.formatTime(tmptime, time, this._defaults); + + if (arguments.length) return tmptime; + else this.formattedTime = tmptime; + }, + + //######################################################################## + // update our input with the new date time.. + //######################################################################## + _updateDateTime: function(dp_inst) { + dp_inst = this.inst || dp_inst; + var dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)), + dateFmt = $.datepicker._get(dp_inst, 'dateFormat'), + formatCfg = $.datepicker._getFormatConfig(dp_inst), + timeAvailable = dt !== null && this.timeDefined; + this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg); + var formattedDateTime = this.formattedDate; + if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0)) + return; + + if (this._defaults.timeOnly === true) { + formattedDateTime = this.formattedTime; + } else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) { + formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix; + } + + this.formattedDateTime = formattedDateTime; + + if(!this._defaults.showTimepicker) { + this.$input.val(this.formattedDate); + } else if (this.$altInput && this._defaults.altFieldTimeOnly === true) { + this.$altInput.val(this.formattedTime); + this.$input.val(this.formattedDate); + } else if(this.$altInput) { + this.$altInput.val(formattedDateTime); + this.$input.val(formattedDateTime); + } else { + this.$input.val(formattedDateTime); + } + + this.$input.trigger("change"); + } + +}); + +$.fn.extend({ + //######################################################################## + // shorthand just to use timepicker.. + //######################################################################## + timepicker: function(o) { + o = o || {}; + var tmp_args = arguments; + + if (typeof o == 'object') tmp_args[0] = $.extend(o, { timeOnly: true }); + + return $(this).each(function() { + $.fn.datetimepicker.apply($(this), tmp_args); + }); + }, + + //######################################################################## + // extend timepicker to datepicker + //######################################################################## + datetimepicker: function(o) { + o = o || {}; + tmp_args = arguments; + + if (typeof(o) == 'string'){ + if(o == 'getDate') + return $.fn.datepicker.apply($(this[0]), tmp_args); + else + return this.each(function() { + var $t = $(this); + $t.datepicker.apply($t, tmp_args); + }); + } + else + return this.each(function() { + var $t = $(this); + $t.datepicker($.timepicker._newInst($t, o)._defaults); + }); + } +}); + +//######################################################################## +// format the time all pretty... +// format = string format of the time +// time = a {}, not a Date() for timezones +// options = essentially the regional[].. amNames, pmNames, ampm +//######################################################################## +$.datepicker.formatTime = function(format, time, options) { + options = options || {}; + options = $.extend($.timepicker._defaults, options); + time = $.extend({hour:0, minute:0, second:0, millisec:0, timezone:'+0000'}, time); + + var tmptime = format; + var ampmName = options['amNames'][0]; + + var hour = parseInt(time.hour, 10); + if (options.ampm) { + if (hour > 11){ + ampmName = options['pmNames'][0]; + if(hour > 12) + hour = hour % 12; + } + if (hour === 0) + hour = 12; + } + tmptime = tmptime.replace(/(?:hh?|mm?|ss?|[tT]{1,2}|[lz])/g, function(match) { + switch (match.toLowerCase()) { + case 'hh': return ('0' + hour).slice(-2); + case 'h': return hour; + case 'mm': return ('0' + time.minute).slice(-2); + case 'm': return time.minute; + case 'ss': return ('0' + time.second).slice(-2); + case 's': return time.second; + case 'l': return ('00' + time.millisec).slice(-3); + case 'z': return time.timezone; + case 't': case 'tt': + if (options.ampm) { + if (match.length == 1) + ampmName = ampmName.charAt(0); + return match.charAt(0) == 'T' ? ampmName.toUpperCase() : ampmName.toLowerCase(); + } + return ''; + } + }); + + tmptime = $.trim(tmptime); + return tmptime; +}; + +//######################################################################## +// the bad hack :/ override datepicker so it doesnt close on select +// inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378 +//######################################################################## +$.datepicker._base_selectDate = $.datepicker._selectDate; +$.datepicker._selectDate = function (id, dateStr) { + var inst = this._getInst($(id)[0]), + tp_inst = this._get(inst, 'timepicker'); + + if (tp_inst) { + tp_inst._limitMinMaxDateTime(inst, true); + inst.inline = inst.stay_open = true; + //This way the onSelect handler called from calendarpicker get the full dateTime + this._base_selectDate(id, dateStr); + inst.inline = inst.stay_open = false; + this._notifyChange(inst); + this._updateDatepicker(inst); + } + else this._base_selectDate(id, dateStr); +}; + +//############################################################################################# +// second bad hack :/ override datepicker so it triggers an event when changing the input field +// and does not redraw the datepicker on every selectDate event +//############################################################################################# +$.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker; +$.datepicker._updateDatepicker = function(inst) { + + // don't popup the datepicker if there is another instance already opened + var input = inst.input[0]; + if($.datepicker._curInst && + $.datepicker._curInst != inst && + $.datepicker._datepickerShowing && + $.datepicker._lastInput != input) { + return; + } + + if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) { + + this._base_updateDatepicker(inst); + + // Reload the time control when changing something in the input text field. + var tp_inst = this._get(inst, 'timepicker'); + if(tp_inst) tp_inst._addTimePicker(inst); + } +}; + +//####################################################################################### +// third bad hack :/ override datepicker so it allows spaces and colon in the input field +//####################################################################################### +$.datepicker._base_doKeyPress = $.datepicker._doKeyPress; +$.datepicker._doKeyPress = function(event) { + var inst = $.datepicker._getInst(event.target), + tp_inst = $.datepicker._get(inst, 'timepicker'); + + if (tp_inst) { + if ($.datepicker._get(inst, 'constrainInput')) { + var ampm = tp_inst._defaults.ampm, + dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')), + datetimeChars = tp_inst._defaults.timeFormat.toString() + .replace(/[hms]/g, '') + .replace(/TT/g, ampm ? 'APM' : '') + .replace(/Tt/g, ampm ? 'AaPpMm' : '') + .replace(/tT/g, ampm ? 'AaPpMm' : '') + .replace(/T/g, ampm ? 'AP' : '') + .replace(/tt/g, ampm ? 'apm' : '') + .replace(/t/g, ampm ? 'ap' : '') + + " " + + tp_inst._defaults.separator + + tp_inst._defaults.timeSuffix + + (tp_inst._defaults.showTimezone ? tp_inst._defaults.timezoneList.join('') : '') + + (tp_inst._defaults.amNames.join('')) + + (tp_inst._defaults.pmNames.join('')) + + dateChars, + chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode); + return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1); + } + } + + return $.datepicker._base_doKeyPress(event); +}; + +//####################################################################################### +// Override key up event to sync manual input changes. +//####################################################################################### +$.datepicker._base_doKeyUp = $.datepicker._doKeyUp; +$.datepicker._doKeyUp = function (event) { + var inst = $.datepicker._getInst(event.target), + tp_inst = $.datepicker._get(inst, 'timepicker'); + + if (tp_inst) { + if (tp_inst._defaults.timeOnly && (inst.input.val() != inst.lastVal)) { + try { + $.datepicker._updateDatepicker(inst); + } + catch (err) { + $.datepicker.log(err); + } + } + } + + return $.datepicker._base_doKeyUp(event); +}; + +//####################################################################################### +// override "Today" button to also grab the time. +//####################################################################################### +$.datepicker._base_gotoToday = $.datepicker._gotoToday; +$.datepicker._gotoToday = function(id) { + var inst = this._getInst($(id)[0]), + $dp = inst.dpDiv; + this._base_gotoToday(id); + var now = new Date(); + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst && tp_inst._defaults.showTimezone && tp_inst.timezone_select) { + var tzoffset = now.getTimezoneOffset(); // If +0100, returns -60 + var tzsign = tzoffset > 0 ? '-' : '+'; + tzoffset = Math.abs(tzoffset); + var tzmin = tzoffset % 60; + tzoffset = tzsign + ('0' + (tzoffset - tzmin) / 60).slice(-2) + ('0' + tzmin).slice(-2); + if (tp_inst._defaults.timezoneIso8609) + tzoffset = tzoffset.substring(0, 3) + ':' + tzoffset.substring(3); + tp_inst.timezone_select.val(tzoffset); + } + this._setTime(inst, now); + $( '.ui-datepicker-today', $dp).click(); +}; + +//####################################################################################### +// Disable & enable the Time in the datetimepicker +//####################################################################################### +$.datepicker._disableTimepickerDatepicker = function(target, date, withDate) { + var inst = this._getInst(target), + tp_inst = this._get(inst, 'timepicker'); + $(target).datepicker('getDate'); // Init selected[Year|Month|Day] + if (tp_inst) { + tp_inst._defaults.showTimepicker = false; + tp_inst._updateDateTime(inst); + } +}; + +$.datepicker._enableTimepickerDatepicker = function(target, date, withDate) { + var inst = this._getInst(target), + tp_inst = this._get(inst, 'timepicker'); + $(target).datepicker('getDate'); // Init selected[Year|Month|Day] + if (tp_inst) { + tp_inst._defaults.showTimepicker = true; + tp_inst._addTimePicker(inst); // Could be disabled on page load + tp_inst._updateDateTime(inst); + } +}; + +//####################################################################################### +// Create our own set time function +//####################################################################################### +$.datepicker._setTime = function(inst, date) { + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + var defaults = tp_inst._defaults, + // calling _setTime with no date sets time to defaults + hour = date ? date.getHours() : defaults.hour, + minute = date ? date.getMinutes() : defaults.minute, + second = date ? date.getSeconds() : defaults.second, + millisec = date ? date.getMilliseconds() : defaults.millisec; + + //check if within min/max times.. + if ((hour < defaults.hourMin || hour > defaults.hourMax) || (minute < defaults.minuteMin || minute > defaults.minuteMax) || (second < defaults.secondMin || second > defaults.secondMax) || (millisec < defaults.millisecMin || millisec > defaults.millisecMax)) { + hour = defaults.hourMin; + minute = defaults.minuteMin; + second = defaults.secondMin; + millisec = defaults.millisecMin; + } + + tp_inst.hour = hour; + tp_inst.minute = minute; + tp_inst.second = second; + tp_inst.millisec = millisec; + + if (tp_inst.hour_slider) tp_inst.hour_slider.slider('value', hour); + if (tp_inst.minute_slider) tp_inst.minute_slider.slider('value', minute); + if (tp_inst.second_slider) tp_inst.second_slider.slider('value', second); + if (tp_inst.millisec_slider) tp_inst.millisec_slider.slider('value', millisec); + + tp_inst._onTimeChange(); + tp_inst._updateDateTime(inst); + } +}; + +//####################################################################################### +// Create new public method to set only time, callable as $().datepicker('setTime', date) +//####################################################################################### +$.datepicker._setTimeDatepicker = function(target, date, withDate) { + var inst = this._getInst(target), + tp_inst = this._get(inst, 'timepicker'); + + if (tp_inst) { + this._setDateFromField(inst); + var tp_date; + if (date) { + if (typeof date == "string") { + tp_inst._parseTime(date, withDate); + tp_date = new Date(); + tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); + } + else tp_date = new Date(date.getTime()); + if (tp_date.toString() == 'Invalid Date') tp_date = undefined; + this._setTime(inst, tp_date); + } + } + +}; + +//####################################################################################### +// override setDate() to allow setting time too within Date object +//####################################################################################### +$.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker; +$.datepicker._setDateDatepicker = function(target, date) { + var inst = this._getInst(target), + tp_date = (date instanceof Date) ? new Date(date.getTime()) : date; + + this._updateDatepicker(inst); + this._base_setDateDatepicker.apply(this, arguments); + this._setTimeDatepicker(target, tp_date, true); +}; + +//####################################################################################### +// override getDate() to allow getting time too within Date object +//####################################################################################### +$.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker; +$.datepicker._getDateDatepicker = function(target, noDefault) { + var inst = this._getInst(target), + tp_inst = this._get(inst, 'timepicker'); + + if (tp_inst) { + this._setDateFromField(inst, noDefault); + var date = this._getDate(inst); + if (date && tp_inst._parseTime($(target).val(), tp_inst.timeOnly)) date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); + return date; + } + return this._base_getDateDatepicker(target, noDefault); +}; + +//####################################################################################### +// override parseDate() because UI 1.8.14 throws an error about "Extra characters" +// An option in datapicker to ignore extra format characters would be nicer. +//####################################################################################### +$.datepicker._base_parseDate = $.datepicker.parseDate; +$.datepicker.parseDate = function(format, value, settings) { + var date; + try { + date = this._base_parseDate(format, value, settings); + } catch (err) { + if (err.indexOf(":") >= 0) { + // Hack! The error message ends with a colon, a space, and + // the "extra" characters. We rely on that instead of + // attempting to perfectly reproduce the parsing algorithm. + date = this._base_parseDate(format, value.substring(0,value.length-(err.length-err.indexOf(':')-2)), settings); + } else { + // The underlying error was not related to the time + throw err; + } + } + return date; +}; + +//####################################################################################### +// override formatDate to set date with time to the input +//####################################################################################### +$.datepicker._base_formatDate = $.datepicker._formatDate; +$.datepicker._formatDate = function(inst, day, month, year){ + var tp_inst = this._get(inst, 'timepicker'); + if(tp_inst) { + tp_inst._updateDateTime(inst); + return tp_inst.$input.val(); + } + return this._base_formatDate(inst); +}; + +//####################################################################################### +// override options setter to add time to maxDate(Time) and minDate(Time). MaxDate +//####################################################################################### +$.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker; +$.datepicker._optionDatepicker = function(target, name, value) { + var inst = this._getInst(target), + tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + var min = null, max = null, onselect = null; + if (typeof name == 'string') { // if min/max was set with the string + if (name === 'minDate' || name === 'minDateTime' ) + min = value; + else if (name === 'maxDate' || name === 'maxDateTime') + max = value; + else if (name === 'onSelect') + onselect = value; + } else if (typeof name == 'object') { //if min/max was set with the JSON + if (name.minDate) + min = name.minDate; + else if (name.minDateTime) + min = name.minDateTime; + else if (name.maxDate) + max = name.maxDate; + else if (name.maxDateTime) + max = name.maxDateTime; + } + if(min) { //if min was set + if (min == 0) + min = new Date(); + else + min = new Date(min); + + tp_inst._defaults.minDate = min; + tp_inst._defaults.minDateTime = min; + } else if (max) { //if max was set + if(max==0) + max=new Date(); + else + max= new Date(max); + tp_inst._defaults.maxDate = max; + tp_inst._defaults.maxDateTime = max; + } else if (onselect) + tp_inst._defaults.onSelect = onselect; + } + if (value === undefined) + return this._base_optionDatepicker(target, name); + return this._base_optionDatepicker(target, name, value); +}; + +//####################################################################################### +// jQuery extend now ignores nulls! +//####################################################################################### +function extendRemove(target, props) { + $.extend(target, props); + for (var name in props) + if (props[name] === null || props[name] === undefined) + target[name] = props[name]; + return target; +}; + +$.timepicker = new Timepicker(); // singleton instance +$.timepicker.version = "1.0.0"; + +})(jQuery); diff --git a/rt/share/html/NoAuth/js/ui.timepickr.js b/rt/share/html/NoAuth/js/ui.timepickr.js deleted file mode 100644 index 3b2040a21..000000000 --- a/rt/share/html/NoAuth/js/ui.timepickr.js +++ /dev/null @@ -1,941 +0,0 @@ -/* - jQuery utils - @VERSION - http://code.google.com/p/jquery-utils/ - - (c) Maxime Haineault <haineault@gmail.com> - http://haineault.com - - MIT License (http://www.opensource.org/licenses/mit-license.php - -*/ - -(function($){ - $.extend($.expr[':'], { - // case insensitive version of :contains - icontains: function(a,i,m){return (a.textContent||a.innerText||jQuery(a).text()||"").toLowerCase().indexOf(m[3].toLowerCase())>=0;} - }); - - $.iterators = { - getText: function() { return $(this).text(); }, - parseInt: function(v){ return parseInt(v, 10); } - }; - - $.extend({ - - // Returns a range object - // Author: Matthias Miller - // Site: http://blog.outofhanwell.com/2006/03/29/javascript-range-function/ - range: function() { - if (!arguments.length) { return []; } - var min, max, step; - if (arguments.length == 1) { - min = 0; - max = arguments[0]-1; - step = 1; - } - else { - // default step to 1 if it's zero or undefined - min = arguments[0]; - max = arguments[1]-1; - step = arguments[2] || 1; - } - // convert negative steps to positive and reverse min/max - if (step < 0 && min >= max) { - step *= -1; - var tmp = min; - min = max; - max = tmp; - min += ((max-min) % step); - } - var a = []; - for (var i = min; i <= max; i += step) { a.push(i); } - return a; - }, - - // Taken from ui.core.js. - // Why are you keeping this gem for yourself guys ? :| - keyCode: { - BACKSPACE: 8, CAPS_LOCK: 20, COMMA: 188, CONTROL: 17, DELETE: 46, DOWN: 40, - END: 35, ENTER: 13, ESCAPE: 27, HOME: 36, INSERT: 45, LEFT: 37, - NUMPAD_ADD: 107, NUMPAD_DECIMAL: 110, NUMPAD_DIVIDE: 111, NUMPAD_ENTER: 108, - NUMPAD_MULTIPLY: 106, NUMPAD_SUBTRACT: 109, PAGE_DOWN: 34, PAGE_UP: 33, - PERIOD: 190, RIGHT: 39, SHIFT: 16, SPACE: 32, TAB: 9, UP: 38 - }, - - // Takes a keyboard event and return true if the keycode match the specified keycode - keyIs: function(k, e) { - return parseInt($.keyCode[k.toUpperCase()], 10) == parseInt((typeof(e) == 'number' )? e: e.keyCode, 10); - }, - - // Returns the key of an array - keys: function(arr) { - var o = []; - for (k in arr) { o.push(k); } - return o; - }, - - // Redirect to a specified url - redirect: function(url) { - window.location.href = url; - return url; - }, - - // Stop event shorthand - stop: function(e, preventDefault, stopPropagation) { - if (preventDefault) { e.preventDefault(); } - if (stopPropagation) { e.stopPropagation(); } - return preventDefault && false || true; - }, - - // Returns the basename of a path - basename: function(path) { - var t = path.split('/'); - return t[t.length] === '' && s || t.slice(0, t.length).join('/'); - }, - - // Returns the filename of a path - filename: function(path) { - return path.split('/').pop(); - }, - - // Returns a formated file size - filesizeformat: function(bytes, suffixes){ - var b = parseInt(bytes, 10); - var s = suffixes || ['byte', 'bytes', 'KB', 'MB', 'GB']; - if (isNaN(b) || b === 0) { return '0 ' + s[0]; } - if (b == 1) { return '1 ' + s[0]; } - if (b < 1024) { return b.toFixed(2) + ' ' + s[1]; } - if (b < 1048576) { return (b / 1024).toFixed(2) + ' ' + s[2]; } - if (b < 1073741824) { return (b / 1048576).toFixed(2) + ' '+ s[3]; } - else { return (b / 1073741824).toFixed(2) + ' '+ s[4]; } - }, - - fileExtension: function(s) { - var tokens = s.split('.'); - return tokens[tokens.length-1] || false; - }, - - // Returns true if an object is a String - isString: function(o) { - return typeof(o) == 'string' && true || false; - }, - - // Returns true if an object is a RegExp - isRegExp: function(o) { - return o && o.constructor.toString().indexOf('RegExp()') != -1 || false; - }, - - isObject: function(o) { - return (typeof(o) == 'object'); - }, - - // Convert input to currency (two decimal fixed number) - toCurrency: function(i) { - i = parseFloat(i, 10).toFixed(2); - return (i=='NaN') ? '0.00' : i; - }, - - /*-------------------------------------------------------------------- - * javascript method: "pxToEm" - * by: - Scott Jehl (scott@filamentgroup.com) - Maggie Wachs (maggie@filamentgroup.com) - http://www.filamentgroup.com - * - * Copyright (c) 2008 Filament Group - * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses. - * - * Description: pxToEm converts a pixel value to ems depending on inherited font size. - * Article: http://www.filamentgroup.com/lab/retaining_scalable_interfaces_with_pixel_to_em_conversion/ - * Demo: http://www.filamentgroup.com/examples/pxToEm/ - * - * Options: - scope: string or jQuery selector for font-size scoping - reverse: Boolean, true reverses the conversion to em-px - * Dependencies: jQuery library - * Usage Example: myPixelValue.pxToEm(); or myPixelValue.pxToEm({'scope':'#navigation', reverse: true}); - * - * Version: 2.1, 18.12.2008 - * Changelog: - * 08.02.2007 initial Version 1.0 - * 08.01.2008 - fixed font-size calculation for IE - * 18.12.2008 - removed native object prototyping to stay in jQuery's spirit, jsLinted (Maxime Haineault <haineault@gmail.com>) - --------------------------------------------------------------------*/ - - pxToEm: function(i, settings){ - //set defaults - settings = jQuery.extend({ - scope: 'body', - reverse: false - }, settings); - - var pxVal = (i === '') ? 0 : parseFloat(i); - var scopeVal; - var getWindowWidth = function(){ - var de = document.documentElement; - return self.innerWidth || (de && de.clientWidth) || document.body.clientWidth; - }; - - /* When a percentage-based font-size is set on the body, IE returns that percent of the window width as the font-size. - For example, if the body font-size is 62.5% and the window width is 1000px, IE will return 625px as the font-size. - When this happens, we calculate the correct body font-size (%) and multiply it by 16 (the standard browser font size) - to get an accurate em value. */ - - if (settings.scope == 'body' && $.browser.msie && (parseFloat($('body').css('font-size')) / getWindowWidth()).toFixed(1) > 0.0) { - var calcFontSize = function(){ - return (parseFloat($('body').css('font-size'))/getWindowWidth()).toFixed(3) * 16; - }; - scopeVal = calcFontSize(); - } - else { scopeVal = parseFloat(jQuery(settings.scope).css("font-size")); } - - var result = (settings.reverse === true) ? (pxVal * scopeVal).toFixed(2) + 'px' : (pxVal / scopeVal).toFixed(2) + 'em'; - return result; - } - }); - - $.extend($.fn, { - type: function() { - try { return $(this).get(0).nodeName.toLowerCase(); } - catch(e) { return false; } - }, - // Select a text range in a textarea - selectRange: function(start, end){ - // use only the first one since only one input can be focused - if ($(this).get(0).createTextRange) { - var range = $(this).get(0).createTextRange(); - range.collapse(true); - range.moveEnd('character', end); - range.moveStart('character', start); - range.select(); - } - else if ($(this).get(0).setSelectionRange) { - $(this).bind('focus', function(e){ - e.preventDefault(); - }).get(0).setSelectionRange(start, end); - } - return $(this); - }, - - /*-------------------------------------------------------------------- - * JQuery Plugin: "EqualHeights" - * by: Scott Jehl, Todd Parker, Maggie Costello Wachs (http://www.filamentgroup.com) - * - * Copyright (c) 2008 Filament Group - * Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php) - * - * Description: Compares the heights or widths of the top-level children of a provided element - and sets their min-height to the tallest height (or width to widest width). Sets in em units - by default if pxToEm() method is available. - * Dependencies: jQuery library, pxToEm method (article: - http://www.filamentgroup.com/lab/retaining_scalable_interfaces_with_pixel_to_em_conversion/) - * Usage Example: $(element).equalHeights(); - Optional: to set min-height in px, pass a true argument: $(element).equalHeights(true); - * Version: 2.1, 18.12.2008 - * - * Note: Changed pxToEm call to call $.pxToEm instead, jsLinted (Maxime Haineault <haineault@gmail.com>) - --------------------------------------------------------------------*/ - - equalHeights: function(px){ - $(this).each(function(){ - var currentTallest = 0; - $(this).children().each(function(i){ - if ($(this).height() > currentTallest) { currentTallest = $(this).height(); } - }); - if (!px || !$.pxToEm) { currentTallest = $.pxToEm(currentTallest); } //use ems unless px is specified - // for ie6, set height since min-height isn't supported - if ($.browser.msie && $.browser.version == 6.0) { $(this).children().css({'height': currentTallest}); } - $(this).children().css({'min-height': currentTallest}); - }); - return this; - }, - - // Copyright (c) 2009 James Padolsey - // http://james.padolsey.com/javascript/jquery-delay-plugin/ - delay: function(time, callback){ - jQuery.fx.step.delay = function(){}; - return this.animate({delay:1}, time, callback); - } - }); -})(jQuery); - -/* - jQuery strings - 0.4 - http://code.google.com/p/jquery-utils/ - - (c) Maxime Haineault <haineault@gmail.com> - http://haineault.com - - MIT License (http://www.opensource.org/licenses/mit-license.php) - - Implementation of Python3K advanced string formatting - http://www.python.org/dev/peps/pep-3101/ - - Documentation: http://code.google.com/p/jquery-utils/wiki/StringFormat - -*/ -(function($){ - var strings = { - strConversion: { - // tries to translate any objects type into string gracefully - __repr: function(i){ - switch(this.__getType(i)) { - case 'array':case 'date':case 'number': - return i.toString(); - case 'object': // Thanks to Richard Paul Lewis for the fix - var o = []; - var l = i.length; - for(var x=0;x<l;x++) { - o.push(x+': '+this.__repr(i[x])); - } - return o.join(', '); - case 'string': - return i; - default: - return i; - } - }, - // like typeof but less vague - __getType: function(i) { - if (!i || !i.constructor) { return typeof(i); } - var match = i.constructor.toString().match(/Array|Number|String|Object|Date/); - return match && match[0].toLowerCase() || typeof(i); - }, - // Jonas Raoni Soares Silva (http://jsfromhell.com/string/pad) - __pad: function(str, l, s, t){ - var p = s || ' '; - var o = str; - if (l - str.length > 0) { - o = new Array(Math.ceil(l / p.length)).join(p).substr(0, t = !t ? l : t == 1 ? 0 : Math.ceil(l / 2)) + str + p.substr(0, l - t); - } - return o; - }, - __getInput: function(arg, args) { - var key = arg.getKey(); - switch(this.__getType(args)){ - case 'object': // Thanks to Jonathan Works for the patch - var keys = key.split('.'); - var obj = args; - for(var subkey = 0; subkey < keys.length; subkey++){ - obj = obj[keys[subkey]]; - } - if (typeof(obj) != 'undefined') { - if (strings.strConversion.__getType(obj) == 'array') { - return arg.getFormat().match(/\.\*/) && obj[1] || obj; - } - return obj; - } - else { - // TODO: try by numerical index - } - break; - case 'array': - key = parseInt(key, 10); - if (arg.getFormat().match(/\.\*/) && typeof args[key+1] != 'undefined') { return args[key+1]; } - else if (typeof args[key] != 'undefined') { return args[key]; } - else { return key; } - break; - } - return '{'+key+'}'; - }, - __formatToken: function(token, args) { - var arg = new Argument(token, args); - return strings.strConversion[arg.getFormat().slice(-1)](this.__getInput(arg, args), arg); - }, - - // Signed integer decimal. - d: function(input, arg){ - var o = parseInt(input, 10); // enforce base 10 - var p = arg.getPaddingLength(); - if (p) { return this.__pad(o.toString(), p, arg.getPaddingString(), 0); } - else { return o; } - }, - // Signed integer decimal. - i: function(input, args){ - return this.d(input, args); - }, - // Unsigned octal - o: function(input, arg){ - var o = input.toString(8); - if (arg.isAlternate()) { o = this.__pad(o, o.length+1, '0', 0); } - return this.__pad(o, arg.getPaddingLength(), arg.getPaddingString(), 0); - }, - // Unsigned decimal - u: function(input, args) { - return Math.abs(this.d(input, args)); - }, - // Unsigned hexadecimal (lowercase) - x: function(input, arg){ - var o = parseInt(input, 10).toString(16); - o = this.__pad(o, arg.getPaddingLength(), arg.getPaddingString(),0); - return arg.isAlternate() ? '0x'+o : o; - }, - // Unsigned hexadecimal (uppercase) - X: function(input, arg){ - return this.x(input, arg).toUpperCase(); - }, - // Floating point exponential format (lowercase) - e: function(input, arg){ - return parseFloat(input, 10).toExponential(arg.getPrecision()); - }, - // Floating point exponential format (uppercase) - E: function(input, arg){ - return this.e(input, arg).toUpperCase(); - }, - // Floating point decimal format - f: function(input, arg){ - return this.__pad(parseFloat(input, 10).toFixed(arg.getPrecision()), arg.getPaddingLength(), arg.getPaddingString(),0); - }, - // Floating point decimal format (alias) - F: function(input, args){ - return this.f(input, args); - }, - // Floating point format. Uses exponential format if exponent is greater than -4 or less than precision, decimal format otherwise - g: function(input, arg){ - var o = parseFloat(input, 10); - return (o.toString().length > 6) ? Math.round(o.toExponential(arg.getPrecision())): o; - }, - // Floating point format. Uses exponential format if exponent is greater than -4 or less than precision, decimal format otherwise - G: function(input, args){ - return this.g(input, args); - }, - // Single character (accepts integer or single character string). - c: function(input, args) { - var match = input.match(/\w|\d/); - return match && match[0] || ''; - }, - // String (converts any JavaScript object to anotated format) - r: function(input, args) { - return this.__repr(input); - }, - // String (converts any JavaScript object using object.toString()) - s: function(input, args) { - return input.toString && input.toString() || ''+input; - } - }, - - format: function(str, args) { - var end = 0; - var start = 0; - var match = false; - var buffer = []; - var token = ''; - var tmp = (str||'').split(''); - for(start=0; start < tmp.length; start++) { - if (tmp[start] == '{' && tmp[start+1] !='{') { - end = str.indexOf('}', start); - token = tmp.slice(start+1, end).join(''); - if (tmp[start-1] != '{' && tmp[end+1] != '}') { - var tokenArgs = (typeof arguments[1] != 'object')? arguments2Array(arguments, 2): args || []; - buffer.push(strings.strConversion.__formatToken(token, tokenArgs)); - } - else { - buffer.push(token); - } - } - else if (start > end || buffer.length < 1) { buffer.push(tmp[start]); } - } - return (buffer.length > 1)? buffer.join(''): buffer[0]; - }, - - calc: function(str, args) { - return eval(format(str, args)); - }, - - repeat: function(s, n) { - return new Array(n+1).join(s); - }, - - UTF8encode: function(s) { - return unescape(encodeURIComponent(s)); - }, - - UTF8decode: function(s) { - return decodeURIComponent(escape(s)); - }, - - tpl: function() { - var out = ''; - var render = true; - // Set - // $.tpl('ui.test', ['<span>', helloWorld ,'</span>']); - if (arguments.length == 2 && $.isArray(arguments[1])) { - this[arguments[0]] = arguments[1].join(''); - return $(this[arguments[0]]); - } - // $.tpl('ui.test', '<span>hello world</span>'); - if (arguments.length == 2 && $.isString(arguments[1])) { - this[arguments[0]] = arguments[1]; - return $(this[arguments[0]]); - } - // Call - // $.tpl('ui.test'); - if (arguments.length == 1) { - return $(this[arguments[0]]); - } - // $.tpl('ui.test', false); - if (arguments.length == 2 && arguments[1] == false) { - return this[arguments[0]]; - } - // $.tpl('ui.test', {value:blah}); - if (arguments.length == 2 && $.isObject(arguments[1])) { - return $($.format(this[arguments[0]], arguments[1])); - } - // $.tpl('ui.test', {value:blah}, false); - if (arguments.length == 3 && $.isObject(arguments[1])) { - return (arguments[2] == true) - ? $.format(this[arguments[0]], arguments[1]) - : $($.format(this[arguments[0]], arguments[1])); - } - } - }; - - var Argument = function(arg, args) { - this.__arg = arg; - this.__args = args; - this.__max_precision = parseFloat('1.'+ (new Array(32)).join('1'), 10).toString().length-3; - this.__def_precision = 6; - this.getString = function(){ - return this.__arg; - }; - this.getKey = function(){ - return this.__arg.split(':')[0]; - }; - this.getFormat = function(){ - var match = this.getString().split(':'); - return (match && match[1])? match[1]: 's'; - }; - this.getPrecision = function(){ - var match = this.getFormat().match(/\.(\d+|\*)/g); - if (!match) { return this.__def_precision; } - else { - match = match[0].slice(1); - if (match != '*') { return parseInt(match, 10); } - else if(strings.strConversion.__getType(this.__args) == 'array') { - return this.__args[1] && this.__args[0] || this.__def_precision; - } - else if(strings.strConversion.__getType(this.__args) == 'object') { - return this.__args[this.getKey()] && this.__args[this.getKey()][0] || this.__def_precision; - } - else { return this.__def_precision; } - } - }; - this.getPaddingLength = function(){ - var match = false; - if (this.isAlternate()) { - match = this.getString().match(/0?#0?(\d+)/); - if (match && match[1]) { return parseInt(match[1], 10); } - } - match = this.getString().match(/(0|\.)(\d+|\*)/g); - return match && parseInt(match[0].slice(1), 10) || 0; - }; - this.getPaddingString = function(){ - var o = ''; - if (this.isAlternate()) { o = ' '; } - // 0 take precedence on alternate format - if (this.getFormat().match(/#0|0#|^0|\.\d+/)) { o = '0'; } - return o; - }; - this.getFlags = function(){ - var match = this.getString().matc(/^(0|\#|\-|\+|\s)+/); - return match && match[0].split('') || []; - }; - this.isAlternate = function() { - return !!this.getFormat().match(/^0?#/); - }; - }; - - var arguments2Array = function(args, shift) { - var o = []; - for (l=args.length, x=(shift || 0)-1; x<l;x++) { o.push(args[x]); } - return o; - }; - $.extend(strings); -})(jQuery); - -/* - jQuery ui.timepickr - @VERSION - http://code.google.com/p/jquery-utils/ - - (c) Maxime Haineault <haineault@gmail.com> - http://haineault.com - - MIT License (http://www.opensource.org/licenses/mit-license.php - - Note: if you want the original experimental plugin checkout the rev 224 - - Dependencies - ------------ - - jquery.utils.js - - jquery.strings.js - - jquery.ui.js - -*/ - -(function($) { - -$.tpl('timepickr.menu', '<div class="ui-helper-reset ui-timepickr ui-widget" />'); -$.tpl('timepickr.row', '<ol class="ui-timepickr-row ui-helper-clearfix" />'); -$.tpl('timepickr.button', '<li class="{className:s}"><span class="ui-state-default">{label:s}</span></li>'); - -$.widget('ui.timepickr', { - plugins: {}, - _create: function() { - this._dom = { - menu: $.tpl('timepickr.menu'), - row: $.tpl('timepickr.menu') - }; - this._trigger('initialize'); - this._trigger('initialized'); - }, - - _trigger: function(type, e, ui) { - var ui = ui || this; - $.ui.plugin.call(this, type, [e, ui]); - return $.Widget.prototype._trigger.call(this, type, e, ui); - }, - - _createButton: function(i, format, className) { - var o = format && $.format(format, i) || i; - var cn = className && 'ui-timepickr-button '+ className || 'ui-timepickr-button'; - return $.tpl('timepickr.button', {className: cn, label: o}).data('id', i) - .bind('mouseover', function(){ - $(this).siblings().find('span') - .removeClass('ui-state-hover').end().end() - .find('span').addClass('ui-state-hover'); - }); - - }, - - _addRow: function(range, format, className, insertAfter) { - var ui = this; - var btn = false; - var row = $.tpl('timepickr.row').bind('mouseover', function(){ - $(this).next().show(); - }); - $.each(range, function(idx, val){ - ui._createButton(val, format || false).appendTo(row); - }); - if (className) { - $(row).addClass(className); - } - if (this.options.corners) { - row.find('span').addClass('ui-corner-'+ this.options.corners); - } - if (insertAfter) { - row.insertAfter(insertAfter); - } - else { - ui._dom.menu.append(row); - } - return row; - }, - - _setVal: function(val) { - val = val || this._getVal(); - if (!(val.h==='' && val.m==='')) { - this.element.data('timepickr.initialValue', val); - this.element.val(this._formatVal(val)); - } - if(this._dom.menu.is(':hidden')) { - this.element.trigger('change'); - } - }, - - _getVal: function() { - var ols = this._dom.menu.find('ol'); - function get(unit) { - var u = ols.filter('.'+unit).find('.ui-state-hover:first').text(); - return u || ols.filter('.'+unit+'li:first span').text(); - } - return { - h: get('hours'), - m: get('minutes'), - s: get('seconds'), - a: get('prefix'), - z: get('suffix'), - f: this.options['format'+ this.c], - c: this.c - }; - }, - - _formatVal: function(ival) { - var val = ival || this._getVal(); - val.c = this.options.convention; - val.f = val.c === 12 && this.options.format12 || this.options.format24; - return (new Time(val)).getTime(); - }, - - blur: function() { - return this.element.blur(); - }, - - focus: function() { - return this.element.focus(); - }, - show: function() { - this._trigger('show'); - this.element.trigger(this.options.trigger); - }, - hide: function() { - this._trigger('hide'); - this._dom.menu.hide(); - } - -}); - -// These properties are shared accross every instances of timepickr -$.extend($.ui.timepickr.prototype, { - version: '@VERSION', - //eventPrefix: '', - //getter: '', - options: { - convention: 24, // 24, 12 - trigger: 'mouseover', - format12: '{h:02.d}:{m:02.d} {z:s}', - format24: '{h:02.d}:{m:02.d}', - hours: true, - prefix: ['am', 'pm'], - suffix: ['am', 'pm'], - prefixVal: false, - suffixVal: true, - rangeHour12: $.range(1, 13), - rangeHour24: [$.range(0, 12), $.range(12, 24)], - rangeMin: $.range(0, 60, 15), - rangeSec: $.range(0, 60, 15), - corners: 'all', - // plugins - core: true, - minutes: true, - seconds: false, - val: false, - updateLive: true, - resetOnBlur: true, - keyboardnav: true, - handle: false, - handleEvent: 'click' - } -}); - -$.ui.plugin.add('timepickr', 'core', { - initialized: function(e, ui) { - var menu = ui._dom.menu; - var pos = ui.element.position(); - - menu.insertAfter(ui.element).css('left', pos.left); - - if (!$.boxModel) { // IE alignement fix - menu.css('margin-top', ui.element.height() + 8); - } - - ui.element - .bind(ui.options.trigger, function() { - ui._dom.menu.show(); - ui._dom.menu.find('ol:first').show(); - ui._trigger('focus'); - if (ui.options.trigger != 'focus') { - ui.element.focus(); - } - ui._trigger('focus'); - }) - .bind('blur', function() { - ui.hide(); - ui._trigger('blur'); - }); - - menu.find('li').bind('mouseover.timepickr', function() { - ui._trigger('refresh'); - }); - }, - refresh: function(e, ui) { - // Realign each menu layers - ui._dom.menu.find('ol').each(function(){ - var p = $(this).prev('ol'); - try { // .. to not fuckup IE - $(this).css('left', p.position().left + p.find('.ui-state-hover').position().left); - } catch(e) {}; - }); - } -}); - -$.ui.plugin.add('timepickr', 'hours', { - initialize: function(e, ui) { - if (ui.options.convention === 24) { - // prefix is required in 24h mode - ui._dom.prefix = ui._addRow(ui.options.prefix, false, 'prefix'); - - // split-range - if ($.isArray(ui.options.rangeHour24[0])) { - var range = []; - $.merge(range, ui.options.rangeHour24[0]); - $.merge(range, ui.options.rangeHour24[1]); - ui._dom.hours = ui._addRow(range, '{0:0.2d}', 'hours'); - ui._dom.hours.find('li').slice(ui.options.rangeHour24[0].length, -1).hide(); - var lis = ui._dom.hours.find('li'); - - var show = [ - function() { - lis.slice(ui.options.rangeHour24[0].length).hide().end() - .slice(0, ui.options.rangeHour24[0].length).show() - .filter(':visible:first').trigger('mouseover'); - - }, - function() { - lis.slice(0, ui.options.rangeHour24[0].length).hide().end() - .slice(ui.options.rangeHour24[0].length).show() - .filter(':visible:first').trigger('mouseover'); - } - ]; - - ui._dom.prefix.find('li').bind('mouseover.timepickr', function(){ - var index = ui._dom.menu.find('.prefix li').index(this); - show[index].call(); - }); - } - else { - ui._dom.hours = ui._addRow(ui.options.rangeHour24, '{0:0.2d}', 'hours'); - ui._dom.hours.find('li').slice(12, -1).hide(); - } - } - else { - ui._dom.hours = ui._addRow(ui.options.rangeHour12, '{0:0.2d}', 'hours'); - // suffix is required in 12h mode - ui._dom.suffix = ui._addRow(ui.options.suffix, false, 'suffix'); - } - }}); - -$.ui.plugin.add('timepickr', 'minutes', { - initialize: function(e, ui) { - var p = ui._dom.hours && ui._dom.hours || false; - ui._dom.minutes = ui._addRow(ui.options.rangeMin, '{0:0.2d}', 'minutes', p); - } -}); - -$.ui.plugin.add('timepickr', 'seconds', { - initialize: function(e, ui) { - var p = ui._dom.minutes && ui._dom.minutes || false; - ui._dom.seconds = ui._addRow(ui.options.rangeSec, '{0:0.2d}', 'seconds', p); - } -}); - -$.ui.plugin.add('timepickr', 'val', { - initialized: function(e, ui) { - ui._setVal(ui.options.val); - } -}); - -$.ui.plugin.add('timepickr', 'updateLive', { - refresh: function(e, ui) { - ui._setVal(); - } -}); - -$.ui.plugin.add('timepickr', 'resetOnBlur', { - initialized: function(e, ui) { - ui.element.data('timepickr.initialValue', ui._getVal()); - ui._dom.menu.find('li > span').bind('mousedown.timepickr', function(){ - ui.element.data('timepickr.initialValue', ui._getVal()); - }); - }, - blur: function(e, ui) { - ui._setVal(ui.element.data('timepickr.initialValue')); - } -}); - -$.ui.plugin.add('timepickr', 'handle', { - initialized: function(e, ui) { - $(ui.options.handle).bind(ui.options.handleEvent + '.timepickr', function(){ - ui.show(); - }); - } -}); - -$.ui.plugin.add('timepickr', 'keyboardnav', { - initialized: function(e, ui) { - ui.element - .bind('keydown', function(e) { - if ($.keyIs('enter', e)) { - ui._setVal(); - ui.blur(); - } - else if ($.keyIs('escape', e)) { - ui.blur(); - } - }); - } -}); - -var Time = function() { // arguments: h, m, s, c, z, f || time string - if (!(this instanceof arguments.callee)) { - throw Error("Constructor called as a function"); - } - // arguments as literal object - if (arguments.length == 1 && $.isObject(arguments[0])) { - this.h = arguments[0].h || 0; - this.m = arguments[0].m || 0; - this.s = arguments[0].s || 0; - this.c = arguments[0].c && ($.inArray(arguments[0].c, [12, 24]) >= 0) && arguments[0].c || 24; - this.f = arguments[0].f || ((this.c == 12) && '{h:02.d}:{m:02.d} {z:02.d}' || '{h:02.d}:{m:02.d}'); - this.z = arguments[0].z || 'am'; - } - // arguments as string - else if (arguments.length < 4 && $.isString(arguments[1])) { - this.c = arguments[2] && ($.inArray(arguments[0], [12, 24]) >= 0) && arguments[0] || 24; - this.f = arguments[3] || ((this.c == 12) && '{h:02.d}:{m:02.d} {z:02.d}' || '{h:02.d}:{m:02.d}'); - this.z = arguments[4] || 'am'; - - this.h = arguments[1] || 0; // parse - this.m = arguments[1] || 0; // parse - this.s = arguments[1] || 0; // parse - } - // no arguments (now) - else if (arguments.length === 0) { - // now - } - // standards arguments - else { - this.h = arguments[0] || 0; - this.m = arguments[1] || 0; - this.s = arguments[2] || 0; - this.c = arguments[3] && ($.inArray(arguments[3], [12, 24]) >= 0) && arguments[3] || 24; - this.f = this.f || ((this.c == 12) && '{h:02.d}:{m:02.d} {z:02.d}' || '{h:02.d}:{m:02.d}'); - this.z = 'am'; - } - return this; -}; - -Time.prototype.get = function(p, f, u) { return u && this.h || $.format(f, this.h); }; -Time.prototype.getHours = function(unformated) { return this.get('h', '{0:02.d}', unformated); }; -Time.prototype.getMinutes = function(unformated) { return this.get('m', '{0:02.d}', unformated); }; -Time.prototype.getSeconds = function(unformated) { return this.get('s', '{0:02.d}', unformated); }; -Time.prototype.setFormat = function(format) { return this.f = format; }; -Time.prototype.getObject = function() { return { h: this.h, m: this.m, s: this.s, c: this.c, f: this.f, z: this.z }; }; -Time.prototype.getTime = function() { return $.format(this.f, {h: this.h, m: this.m, suffix: this.z}); }; // Thanks to Jackson for the fix. -Time.prototype.parse = function(str) { - // 12h formats - if (this.c === 12) { - // Supported formats: (can't find any *official* standards for 12h..) - // - [hh]:[mm]:[ss] [zz] | [hh]:[mm] [zz] | [hh] [zz] - // - [hh]:[mm]:[ss] [z.z.] | [hh]:[mm] [z.z.] | [hh] [z.z.] - this.tokens = str.split(/\s|:/); - this.h = this.tokens[0] || 0; - this.m = this.tokens[1] || 0; - this.s = this.tokens[2] || 0; - this.z = this.tokens[3] || ''; - return this.getObject(); - } - // 24h formats - else { - // Supported formats: - // - ISO 8601: [hh][mm][ss] | [hh][mm] | [hh] - // - ISO 8601 extended: [hh]:[mm]:[ss] | [hh]:[mm] | [hh] - this.tokens = /:/.test(str) && str.split(/:/) || str.match(/[0-9]{2}/g); - this.h = this.tokens[0] || 0; - this.m = this.tokens[1] || 0; - this.s = this.tokens[2] || 0; - this.z = this.tokens[3] || ''; - return this.getObject(); - } -}; - -})(jQuery); diff --git a/rt/share/html/NoAuth/js/util.js b/rt/share/html/NoAuth/js/util.js index 5bfce411d..fe5c0a3ff 100644 --- a/rt/share/html/NoAuth/js/util.js +++ b/rt/share/html/NoAuth/js/util.js @@ -222,35 +222,47 @@ function doOnLoad( js ) { } jQuery(function() { - jQuery(".ui-datepicker:not(.withtime)").datepicker( { - dateFormat: 'yy-mm-dd', - constrainInput: false - } ); - - jQuery(".ui-datepicker.withtime").datepicker( { + var opts = { dateFormat: 'yy-mm-dd', constrainInput: false, - onSelect: function( dateText, inst ) { - // trigger timepicker to get time - var button = document.createElement('input'); - button.setAttribute('type', 'button'); - jQuery(button).width('5em'); - jQuery(button).insertAfter(this); - jQuery(button).timepickr({val: '00:00'}); - var date_input = this; - - jQuery(button).blur( function() { - var time = jQuery(button).val(); - if ( ! time.match(/\d\d:\d\d/) ) { - time = '00:00'; - } - jQuery(date_input).val( dateText + ' ' + time + ':00' ); - jQuery(button).remove(); - } ); - - jQuery(button).focus(); - } - } ); + showButtonPanel: true, + changeMonth: true, + changeYear: true, + showOtherMonths: true, + selectOtherMonths: true + }; + jQuery(".ui-datepicker:not(.withtime)").datepicker(opts); + jQuery(".ui-datepicker.withtime").datetimepicker( jQuery.extend({}, opts, { + stepHour: 1, + // We fake this by snapping below for the minute slider + //stepMinute: 5, + hourGrid: 6, + minuteGrid: 15, + showSecond: false, + timeFormat: 'hh:mm:ss' + }) ).each(function(index, el) { + var tp = jQuery.datepicker._get( jQuery.datepicker._getInst(el), 'timepicker'); + if (!tp) return; + + // Hook after _injectTimePicker so we can modify the minute_slider + // right after it's first created + tp._base_injectTimePicker = tp._injectTimePicker; + tp._injectTimePicker = function() { + this._base_injectTimePicker.apply(this, arguments); + + // Now that we have minute_slider, modify it to be stepped for mouse movements + var slider = jQuery.data(this.minute_slider[0], "slider"); + slider._base_normValueFromMouse = slider._normValueFromMouse; + slider._normValueFromMouse = function() { + var value = this._base_normValueFromMouse.apply(this, arguments); + var old_step = this.options.step; + this.options.step = 5; + var aligned = this._trimAlignValue( value ); + this.options.step = old_step; + return aligned; + }; + }; + }); }); function textToHTML(value) { diff --git a/rt/share/html/Prefs/Other.html b/rt/share/html/Prefs/Other.html index 9f7e04a9c..b5d3edd95 100644 --- a/rt/share/html/Prefs/Other.html +++ b/rt/share/html/Prefs/Other.html @@ -53,6 +53,7 @@ % foreach my $section( RT->Config->Sections ) { <&|/Widgets/TitleBox, title => loc( $section ) &> % foreach my $option( RT->Config->Options( Section => $section ) ) { +% next if $option eq 'EmailFrequency' && !RT->Config->Get('RecordOutgoingEmail'); % my $meta = RT->Config->Meta( $option ); <& $meta->{'Widget'}, Default => 1, diff --git a/rt/share/html/REST/1.0/Forms/ticket/default b/rt/share/html/REST/1.0/Forms/ticket/default index 9a2212b55..016a50c73 100755 --- a/rt/share/html/REST/1.0/Forms/ticket/default +++ b/rt/share/html/REST/1.0/Forms/ticket/default @@ -167,6 +167,17 @@ else { elsif (lc $k eq 'text') { $text = delete $data{$k}; } + elsif ( lc $k ne 'id' ) { + $e = 1; + push @$o, $k; + push(@comments, "# $k: Unknown field"); + } + } + + if ( $e ) { + unshift @comments, "# Could not create ticket."; + $k = \%data; + goto DONE; } # people fields allow multiple values @@ -292,8 +303,10 @@ else { elsif (exists $simple{$key}) { $key = $simple{$key}; $set = "Set$key"; + my $current = $ticket->$key; + $current = '' unless defined $current; - next if (($val eq ($ticket->$key||''))|| ($ticket->$key =~ /^\d+$/ && $val =~ /^\d+$/ && $val == $ticket->$key)); + next if ($val eq $current) or ($current =~ /^\d+$/ && $val =~ /^\d+$/ && $val == $current); ($n, $s) = $ticket->$set("$val"); } elsif (exists $dates{$key}) { @@ -331,13 +344,6 @@ else { } } foreach $p (keys %new) { - # XXX: This is a stupid test. - unless ($p =~ /^[\w.+-]+\@([\w.-]+\.)*\w+.?$/) { - $s = 0; - $n = "$p is not a valid email address."; - push @msgs, [ $s, $n ]; - next; - } unless ($ticket->IsWatcher(Type => $type, Email => $p)) { ($s, $n) = $ticket->AddWatcher(Type => $type, Email => $p); diff --git a/rt/share/html/Search/Chart.html b/rt/share/html/Search/Chart.html index 070ce7cf7..571c3d3a0 100644 --- a/rt/share/html/Search/Chart.html +++ b/rt/share/html/Search/Chart.html @@ -98,14 +98,14 @@ my %query; for(@session_fields) { $query{$_} = $current->{$_} unless defined $query{$_}; - $query{$_} = $m->request_args->{$_} unless defined $query{$_}; + $query{$_} = $DECODED_ARGS->{$_} unless defined $query{$_}; } - if ($m->request_args->{'SavedSearchLoadSubmit'}) { - $query{'SavedChartSearchId'} = $m->request_args->{'SavedSearchLoad'}; + if ($DECODED_ARGS->{'SavedSearchLoadSubmit'}) { + $query{'SavedChartSearchId'} = $DECODED_ARGS->{'SavedSearchLoad'}; } - if ($m->request_args->{'SavedSearchSave'}) { + if ($DECODED_ARGS->{'SavedSearchSave'}) { $query{'SavedChartSearchId'} = $saved_search->{'SearchId'}; } diff --git a/rt/share/html/Search/Elements/SelectPersonType b/rt/share/html/Search/Elements/SelectPersonType index d07e49c22..bc2911196 100644 --- a/rt/share/html/Search/Elements/SelectPersonType +++ b/rt/share/html/Search/Elements/SelectPersonType @@ -62,7 +62,7 @@ <%INIT> my @types; -if ($Scope =~ 'queue') { +if ($Scope =~ /queue/) { @types = qw(Cc AdminCc); } elsif ($Suffix eq 'Group') { diff --git a/rt/share/html/Search/Results.html b/rt/share/html/Search/Results.html index 171b38d92..4fee86506 100755 --- a/rt/share/html/Search/Results.html +++ b/rt/share/html/Search/Results.html @@ -151,6 +151,7 @@ if ($ARGS{'TicketsRefreshInterval'}) { my $refresh = $session{'tickets_refresh_interval'} || RT->Config->Get('SearchResultsRefreshInterval', $session{'CurrentUser'} ); +# Check $m->request_args, not $DECODED_ARGS, to avoid creating a new CSRF token on each refresh if (RT->Config->Get('RestrictReferrer') and $refresh and not $m->request_args->{CSRF_Token}) { my $token = RT::Interface::Web::StoreRequestToken( $session{'CurrentSearchHash'} ); $m->notes->{RefreshURL} = RT->Config->Get('WebURL') diff --git a/rt/share/html/Search/Results.xls b/rt/share/html/Search/Results.xls index 52a05daed..8b94e22ba 100644 --- a/rt/share/html/Search/Results.xls +++ b/rt/share/html/Search/Results.xls @@ -54,6 +54,7 @@ $Format => undef <%INIT> use Spreadsheet::WriteExcel; +use OLE::Storage_Lite; use List::Util qw( max ); use Date::Format qw( time2str ); diff --git a/rt/share/html/Ticket/Attachment/dhandler b/rt/share/html/Ticket/Attachment/dhandler index 75efeffbc..eb291e4f5 100755 --- a/rt/share/html/Ticket/Attachment/dhandler +++ b/rt/share/html/Ticket/Attachment/dhandler @@ -48,7 +48,7 @@ <%perl> my ($ticket, $trans,$attach, $filename); my $arg = $m->dhandler_arg; # get rest of path - if ($arg =~ '^(\d+)/(\d+)') { + if ($arg =~ m{^(\d+)/(\d+)}) { $trans = $1; $attach = $2; } @@ -79,7 +79,12 @@ my $enc = $AttachmentObj->OriginalEncoding || 'utf-8'; my $iana = Encode::find_encoding( $enc ); $iana = $iana? $iana->mime_name : $enc; - $content_type .= ";charset=$iana"; + + require MIME::Types; + my $mimetype = MIME::Types->new->type($content_type); + unless ( $mimetype && $mimetype->isBinary ) { + $content_type .= ";charset=$iana"; + } $r->subprocess_env('no-gzip' => 1); # disable mod_deflate $r->content_type( $content_type ); diff --git a/rt/share/html/Ticket/Elements/AddCustomers b/rt/share/html/Ticket/Elements/AddCustomers index 3c2c82add..13fb2f010 100644 --- a/rt/share/html/Ticket/Elements/AddCustomers +++ b/rt/share/html/Ticket/Elements/AddCustomers @@ -58,7 +58,7 @@ my @Customers = (); if ( $CustomerString ) { @Customers = &RT::URI::freeside::smart_search( 'search' => $CustomerString, - 'no_fuzzy_on_exact' => 1, #pref? + 'no_fuzzy_on_exact' => ! $FS::CurrentUser::CurrentUser->option('enable_fuzzy_on_exact'), ); } diff --git a/rt/share/html/Ticket/Elements/ShowMembers b/rt/share/html/Ticket/Elements/ShowMembers index c17c6e7b8..1ffbda2a1 100755 --- a/rt/share/html/Ticket/Elements/ShowMembers +++ b/rt/share/html/Ticket/Elements/ShowMembers @@ -48,8 +48,9 @@ <ul> % while (my $link = $members->Next) { <li><& /Elements/ShowLink, URI => $link->BaseURI &><br /> +% next if $link->BaseObj and $checked->{$link->BaseObj->id}; % if ($depth < 8) { -<& /Ticket/Elements/ShowMembers, Ticket => $link->BaseObj, depth => ($depth+1) &> +<& /Ticket/Elements/ShowMembers, Ticket => $link->BaseObj, depth => ($depth+1), checked => $checked &> % } </li> % } @@ -61,9 +62,13 @@ return unless $Ticket; my $members = $Ticket->Members; return unless $members->Count; +return if $checked->{$Ticket->id}; + +$checked->{$Ticket->id} = 1; </%INIT> <%ARGS> $Ticket => undef $depth => 1 +$checked => {} </%ARGS> diff --git a/rt/share/html/Ticket/Elements/ShowTransactionAttachments b/rt/share/html/Ticket/Elements/ShowTransactionAttachments index 877201f55..95a23411b 100644 --- a/rt/share/html/Ticket/Elements/ShowTransactionAttachments +++ b/rt/share/html/Ticket/Elements/ShowTransactionAttachments @@ -144,6 +144,8 @@ my $render_attachment = sub { my $message = shift; my $name = defined $message->Filename && length $message->Filename ? $message->Filename : ''; + my $content_type = lc $message->ContentType; + # if it has a content-disposition: attachment, don't show inline my $disposition = $message->GetHeader('Content-Disposition'); @@ -154,7 +156,7 @@ my $render_attachment = sub { } # If it's text - if ( $message->ContentType =~ m{^(text|message)}i ) { + if ( $content_type =~ m{^(text|message)/} ) { my $max_size = RT->Config->Get( 'MaxInlineBody', $session{'CurrentUser'} ); if ( $disposition ne 'inline' ) { $m->out('<p>'. loc( 'Message body is not shown because sender requested not to inline it.' ) .'</p>'); @@ -175,16 +177,16 @@ my $render_attachment = sub { !$ParentObj # or its parent isn't a multipart alternative - || ( $ParentObj->ContentType !~ m{^multipart/alternative$}i ) + || ( $ParentObj->ContentType !~ m{^multipart/(?:alternative|related)$}i ) # or it's of our prefered alterative type || ( ( RT->Config->Get('PreferRichText') - && ( $message->ContentType =~ m{^text/(?:html|enriched)$} ) + && ( $content_type =~ m{^text/(?:html|enriched)$} ) ) || ( !RT->Config->Get('PreferRichText') - && ( $message->ContentType !~ m{^text/(?:html|enriched)$} ) + && ( $content_type !~ m{^text/(?:html|enriched)$} ) ) ) ) { @@ -198,7 +200,6 @@ my $render_attachment = sub { $content = $message->Content; } - my $content_type = lc $message->ContentType; $RT::Logger->debug( "Rendering attachment #". $message->id ." of '$content_type' type" @@ -231,9 +232,8 @@ my $render_attachment = sub { $m->out( $content ); } - # if it's a text/plain show the body - elsif ( $message->ContentType =~ m{^(text|message)}i ) { - + # It's a text type we don't have special handling for + else { unless ( length $name ) { eval { require Text::Quoted; $content = Text::Quoted::extract($content); }; if ($@) { $RT::Logger->warning( "Text::Quoted failed: $@" ) } @@ -250,7 +250,7 @@ my $render_attachment = sub { } # if it's an image, show it as an image - elsif ( RT->Config->Get('ShowTransactionImages') and $message->ContentType =~ /^image\//i ) { + elsif ( RT->Config->Get('ShowTransactionImages') and $content_type =~ m{^image/} ) { if ( $disposition ne 'inline' ) { $m->out('<p>'. loc( 'Message body is not shown because sender requested not to inline it.' ) .'</p>'); return; diff --git a/rt/share/html/m/_elements/raw_style b/rt/share/html/m/_elements/raw_style index 8c1997743..a34982958 100644 --- a/rt/share/html/m/_elements/raw_style +++ b/rt/share/html/m/_elements/raw_style @@ -33,6 +33,7 @@ div.buttons { background-color: #ccc; -moz-border-radius: 0.25em; -webkit-border-radius: 0.25em; + border-radius: 0.25em; -webkit-box-shadow: #333 0px 0px 5px; -moz-box-shadow: #333 0px 0px 5px; box-shadow: #333 0px 0px 5px; @@ -85,6 +86,7 @@ ul.menu li#active a div.titlebox, #bpscredits, .ticket_menu{ -moz-border-radius: 1em; -webkit-border-radius: 1em; + border-radius: 1em; margin: 0.5em; background-color: #fff; padding-top: 1em; @@ -336,6 +338,7 @@ input[type=submit], input[type=button], button, #paging a { padding-right: 0.6em; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; + border-radius: 0.5em; background-color: #006699; color: #fff; } @@ -353,6 +356,7 @@ form { border-bottom: 1px solid black; -moz-border-radius-bottomleft: 1em; -webkit-border-bottom-left-radius: 1em; + border-bottom-left-radius: 1em; padding: 0.5em; background-color: #fff; } diff --git a/rt/share/html/m/_elements/wrapper b/rt/share/html/m/_elements/wrapper index 794385db4..1891079bd 100644 --- a/rt/share/html/m/_elements/wrapper +++ b/rt/share/html/m/_elements/wrapper @@ -3,7 +3,7 @@ $title => '' $show_home_button => 1 </%args> <%init> -if ($m->request_args->{'NotMobile'}) { +if ($DECODED_ARGS->{'NotMobile'}) { $session{'NotMobile'} = 1; RT::Interface::Web::Redirect(RT->Config->Get('WebURL')); $m->abort(); diff --git a/rt/t/api/config.t b/rt/t/api/config.t index a986c3c4f..62b77dffa 100644 --- a/rt/t/api/config.t +++ b/rt/t/api/config.t @@ -1,7 +1,8 @@ use strict; use warnings; use RT; -use RT::Test nodb => 1, tests => 9; +use RT::Test nodb => 1, tests => 11; +use Test::Warn; ok( RT::Config->AddOption( @@ -31,3 +32,12 @@ is( $meta->{Widget}, '/Widgets/Form/Boolean', 'widget is updated to boolean' ); ok( RT::Config->DeleteOption( Name => 'foo' ), 'removed option foo' ); is( RT::Config->Meta('foo'), undef, 'foo is indeed deleted' ); +# Test EmailInputEncodings PostLoadCheck code +RT::Config->Set('EmailInputEncodings', qw(utf-8 iso-8859-1 us-ascii foo)); +my @encodings = qw(utf-8-strict iso-8859-1 ascii); + +warning_is {RT::Config->PostLoadCheck} "Unknown encoding 'foo' in \@EmailInputEncodings option", + 'Correct warning for encoding foo'; + +my @canonical_encodings = RT::Config->Get('EmailInputEncodings'); +is_deeply(\@encodings, \@canonical_encodings, 'Got correct encoding list'); diff --git a/rt/t/api/template-insert.t b/rt/t/api/template-insert.t deleted file mode 100644 index 1bf5fc390..000000000 --- a/rt/t/api/template-insert.t +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/perl - -use warnings; -use strict; - - -use RT; -use RT::Test tests => 7; - - - -# This tiny little test script triggers an interaction bug between DBD::Oracle 1.16, SB 1.15 and RT 3.4 - -use_ok('RT::Template'); -my $template = RT::Template->new(RT->SystemUser); - -isa_ok($template, 'RT::Template'); -my ($val,$msg) = $template->Create(Queue => 1, - Name => 'InsertTest', - Content => 'This is template content'); -ok($val,$msg); -is($template->Name, 'InsertTest'); -is($template->Content, 'This is template content', "We created the object right"); -($val, $msg) = $template->SetContent( 'This is new template content'); -ok($val,$msg); -is($template->Content, 'This is new template content', "We managed to _Set_ the content"); diff --git a/rt/t/api/template-simple.t b/rt/t/api/template-simple.t deleted file mode 100644 index bbdebb31f..000000000 --- a/rt/t/api/template-simple.t +++ /dev/null @@ -1,275 +0,0 @@ -use strict; -use warnings; -use RT; -use RT::Test tests => 231; -use Test::Warn; - -my $queue = RT::Queue->new(RT->SystemUser); -$queue->Load("General"); - -my $ticket_cf = RT::CustomField->new(RT->SystemUser); -$ticket_cf->Create( - Name => 'Department', - Queue => '0', - Type => 'FreeformSingle', -); - -my $txn_cf = RT::CustomField->new(RT->SystemUser); -$txn_cf->Create( - Name => 'Category', - LookupType => RT::Transaction->CustomFieldLookupType, - Type => 'FreeformSingle', -); -$txn_cf->AddToObject($queue); - -my $ticket = RT::Ticket->new(RT->SystemUser); -my ($id, $msg) = $ticket->Create( - Subject => "template testing", - Queue => "General", - Owner => 'root@localhost', - Requestor => ["dom\@example.com"], - "CustomField-" . $txn_cf->id => "Special", -); -ok($id, "Created ticket: $msg"); -my $txn = $ticket->Transactions->First; - -$ticket->AddCustomFieldValue( - Field => 'Department', - Value => 'Coolio', -); - -TemplateTest( - Content => "\ntest", - PerlOutput => "test", - SimpleOutput => "test", -); - -TemplateTest( - Content => "\ntest { 5 * 5 }", - PerlOutput => "test 25", - SimpleOutput => "test { 5 * 5 }", -); - -TemplateTest( - Content => "\ntest { \$Requestor }", - PerlOutput => "test dom\@example.com", - SimpleOutput => "test dom\@example.com", -); - -TemplateTest( - Content => "\ntest { \$TicketSubject }", - PerlOutput => "test ", - SimpleOutput => "test template testing", -); - -SimpleTemplateTest( - Content => "\ntest { \$TicketQueueId }", - Output => "test 1", -); - -SimpleTemplateTest( - Content => "\ntest { \$TicketQueueName }", - Output => "test General", -); - -SimpleTemplateTest( - Content => "\ntest { \$TicketOwnerId }", - Output => "test 12", -); - -SimpleTemplateTest( - Content => "\ntest { \$TicketOwnerName }", - Output => "test root", -); - -SimpleTemplateTest( - Content => "\ntest { \$TicketOwnerEmailAddress }", - Output => "test root\@localhost", -); - -SimpleTemplateTest( - Content => "\ntest { \$TicketStatus }", - Output => "test new", -); - -SimpleTemplateTest( - Content => "\ntest #{ \$TicketId }", - Output => "test #" . $ticket->id, -); - -SimpleTemplateTest( - Content => "\ntest { \$TicketCFDepartment }", - Output => "test Coolio", -); - -SimpleTemplateTest( - Content => "\ntest #{ \$TransactionId }", - Output => "test #" . $txn->id, -); - -SimpleTemplateTest( - Content => "\ntest { \$TransactionType }", - Output => "test Create", -); - -SimpleTemplateTest( - Content => "\ntest { \$TransactionCFCategory }", - Output => "test Special", -); - -SimpleTemplateTest( - Content => "\ntest { \$TicketDelete }", - Output => "test { \$TicketDelete }", -); - -SimpleTemplateTest( - Content => "\ntest { \$Nonexistent }", - Output => "test { \$Nonexistent }", -); - -warning_like { - TemplateTest( - Content => "\ntest { \$Ticket->Nonexistent }", - PerlOutput => undef, - SimpleOutput => "test { \$Ticket->Nonexistent }", - ); -} qr/RT::Ticket::Nonexistent Unimplemented/; - -warning_like { - TemplateTest( - Content => "\ntest { \$Nonexistent->Nonexistent }", - PerlOutput => undef, - SimpleOutput => "test { \$Nonexistent->Nonexistent }", - ); -} qr/Can't call method "Nonexistent" on an undefined value/; - -TemplateTest( - Content => "\ntest { \$Ticket->OwnerObj->Name }", - PerlOutput => "test root", - SimpleOutput => "test { \$Ticket->OwnerObj->Name }", -); - -warning_like { - TemplateTest( - Content => "\ntest { *!( }", - SyntaxError => 1, - PerlOutput => undef, - SimpleOutput => "test { *!( }", - ); -} qr/Template parsing error: syntax error/; - -TemplateTest( - Content => "\ntest { \$rtname ", - SyntaxError => 1, - PerlOutput => undef, - SimpleOutput => undef, -); - -is($ticket->Status, 'new', "test setup"); -SimpleTemplateTest( - Content => "\ntest { \$Ticket->SetStatus('resolved') }", - Output => "test { \$Ticket->SetStatus('resolved') }", -); -is($ticket->Status, 'new', "simple templates can't call ->SetStatus"); - -# Make sure changing the template's type works -my $template = RT::Template->new(RT->SystemUser); -$template->Create( - Name => "type chameleon", - Type => "Perl", - Content => "\ntest { 10 * 7 }", -); -ok($id = $template->id, "Created template"); -$template->Parse; -is($template->MIMEObj->stringify_body, "test 70", "Perl output"); - -$template = RT::Template->new(RT->SystemUser); -$template->Load($id); -is($template->Name, "type chameleon"); - -$template->SetType('Simple'); -$template->Parse; -is($template->MIMEObj->stringify_body, "test { 10 * 7 }", "Simple output"); - -$template = RT::Template->new(RT->SystemUser); -$template->Load($id); -is($template->Name, "type chameleon"); - -$template->SetType('Perl'); -$template->Parse; -is($template->MIMEObj->stringify_body, "test 70", "Perl output"); - -undef $ticket; - -my $counter = 0; -sub IndividualTemplateTest { - local $Test::Builder::Level = $Test::Builder::Level + 1; - - my %args = ( - Name => "Test-" . ++$counter, - Type => "Perl", - @_, - ); - - my $t = RT::Template->new(RT->SystemUser); - $t->Create( - Name => $args{Name}, - Type => $args{Type}, - Content => $args{Content}, - ); - - ok($t->id, "Created $args{Type} template"); - is($t->Name, $args{Name}, "$args{Type} template name"); - is($t->Content, $args{Content}, "$args{Type} content"); - is($t->Type, $args{Type}, "template type"); - - # this should never blow up! - my ($ok, $msg) = $t->CompileCheck; - - # we don't need to syntax check simple templates since if you mess them up - # it's safe to just use the input directly as the template's output - if ($args{SyntaxError} && $args{Type} eq 'Perl') { - ok(!$ok, "got a syntax error"); - } - else { - ok($ok, $msg); - } - - ($ok, $msg) = $t->Parse( - TicketObj => $ticket, - TransactionObj => $txn, - ); - if (defined $args{Output}) { - ok($ok, $msg); - is($t->MIMEObj->stringify_body, $args{Output}, "$args{Type} template's output"); - } - else { - ok(!$ok, "expected a failure"); - } -} - -sub TemplateTest { - local $Test::Builder::Level = $Test::Builder::Level + 1; - my %args = @_; - - for my $type ('Perl', 'Simple') { - next if $args{"Skip$type"}; - - IndividualTemplateTest( - %args, - Type => $type, - Output => $args{$type . 'Output'}, - ); - } -} - -sub SimpleTemplateTest { - local $Test::Builder::Level = $Test::Builder::Level + 1; - my %args = @_; - - IndividualTemplateTest( - %args, - Type => 'Simple', - ); -} - diff --git a/rt/t/api/template.t b/rt/t/api/template.t index 2fadede38..331d9f996 100644 --- a/rt/t/api/template.t +++ b/rt/t/api/template.t @@ -1,25 +1,34 @@ -use strict; use warnings; -use RT; -use RT::Test tests => 2; - - -{ - -ok(require RT::Template); +use strict; +use RT; +use RT::Test tests => 10; -} +my $queue = RT::Test->load_or_create_queue( Name => 'Templates' ); +ok $queue && $queue->id, "loaded or created a queue"; { - -my $t = RT::Template->new(RT->SystemUser); -$t->Create(Name => "Foo", Queue => 1); -my $t2 = RT::Template->new(RT->Nobody); -$t2->Load($t->Id); -ok($t2->QueueObj->id, "Got the template's queue objet"); - - + my $template = RT::Template->new( RT->SystemUser ); + isa_ok($template, 'RT::Template'); + my ($val,$msg) = $template->Create( + Queue => $queue->id, + Name => 'InsertTest', + Content => 'This is template content' + ); + ok $val, "created a template" or diag "error: $msg"; + ok my $id = $template->id, "id is defined"; + is $template->Name, 'InsertTest'; + is $template->Content, 'This is template content', "We created the object right"; + + ($val, $msg) = $template->SetContent( 'This is new template content'); + ok $val, "changed content" or diag "error: $msg"; + + is $template->Content, 'This is new template content', "We managed to _Set_ the content"; + + ($val, $msg) = $template->Delete; + ok $val, "deleted template"; + + $template->Load($id); + ok !$template->id, "can not load template after deletion"; } - diff --git a/rt/t/articles/search-interface.t b/rt/t/articles/search-interface.t index eb3a4f763..bcba3116b 100644 --- a/rt/t/articles/search-interface.t +++ b/rt/t/articles/search-interface.t @@ -3,7 +3,7 @@ use strict; use warnings; -use RT::Test tests => 23; +use RT::Test tests => 44; use RT::CustomField; use RT::Queue; @@ -67,7 +67,12 @@ my %cvals = ('article1q' => 'Some question about swallows', 'article3q' => 'Why should I eat my supper?', 'article3a' => 'There are starving children in Africa', 'article4q' => 'What did Brian originally write?', - 'article4a' => 'Romanes eunt domus'); + 'article4a' => 'This is an answer that is longer than 255 ' + . 'characters so these tests will be sure to use the LargeContent ' + . 'SQL as well as the normal SQL that would be generated if this ' + . 'was an answer that was shorter than 255 characters. This second ' + . 'sentence has a few extra characters to get this string to go ' + . 'over the 255 character boundary. Lorem ipsum.'); # Create an article or two with our custom field values. @@ -108,6 +113,52 @@ isa_ok($m, 'Test::WWW::Mechanize'); ok($m->login, 'logged in'); $m->follow_link_ok( { text => 'Articles', url_regex => qr!^/Articles/! }, 'UI -> Articles' ); -$m->follow_link_ok( {text => 'Search'}, 'Articles -> Search'); -$m->follow_link_ok( {text => 'in class '.$class->Name}, 'Articles in class '.$class->Name); -$m->content_contains($article1->Name); + +# In all of the search results below, the results page should +# have the summary text of the article it occurs in. + +# Case sensitive search on small field. +DoArticleSearch($m, $class->Name, 'Africa'); +$m->text_contains('Search results'); # Did we do a search? +$m->text_contains('blah blah 1'); + +# Case insensitive search on small field. +DoArticleSearch($m, $class->Name, 'africa'); +$m->text_contains('Search results'); # Did we do a search? +$m->text_contains('blah blah 1'); + +# Case sensitive search on large field. +DoArticleSearch($m, $class->Name, 'ipsum'); +$m->text_contains('Search results'); # Did we do a search? +$m->text_contains('hoi polloi 4'); + +# Case insensitive search on large field. +DoArticleSearch($m, $class->Name, 'lorem'); +$m->text_contains('Search results'); # Did we do a search? +TODO:{ + local $TODO = 'Case insensitive search on LONGBLOB not available in MySQL' + if RT->Config->Get('DatabaseType') eq 'mysql'; + $m->text_contains('hoi polloi 4'); +} + +# When you send $m to this sub, it must be on a page with +# a Search link. +sub DoArticleSearch{ + my $m = shift; + my $class_name = shift; + my $search_text = shift; + + $m->follow_link_ok( {text => 'Search'}, 'Articles -> Search'); + $m->follow_link_ok( {text => 'in class '. $class_name}, 'Articles in class '. $class_name); + $m->text_contains('First article'); + + $m->submit_form_ok( { + form_number => 3, + fields => { + 'Article~' => $search_text + }, + }, "Search for $search_text" + ); + return; +} + diff --git a/rt/t/articles/uri-a.t b/rt/t/articles/uri-a.t index 82d0f1b01..5c1fdaf36 100644 --- a/rt/t/articles/uri-a.t +++ b/rt/t/articles/uri-a.t @@ -3,7 +3,7 @@ use strict; use warnings; -use RT::Test tests => 7; +use RT::Test tests => 15; use_ok("RT::URI::a"); my $uri = RT::URI::a->new($RT::SystemUser); @@ -26,3 +26,39 @@ is(ref($uri->Object), "RT::Article", "Object loaded is an article"); is($uri->Object->Id, $article->Id, "Object loaded has correct ID"); is($article->URI, 'fsck.com-article://example.com/article/'.$article->Id, "URI object has correct URI string"); + +{ + my $aid = $article->id; + my $ticket = RT::Ticket->new( RT->SystemUser ); + my ($id, $msg) = $ticket->Create( + Queue => 1, + Subject => 'test ticket', + ); + ok $id, "Created a test ticket"; + + # Try searching + my $tickets = RT::Tickets->new( RT->SystemUser ); + $tickets->FromSQL(" RefersTo = 'a:$aid' "); + is $tickets->Count, 0, "No results yet"; + + # try with the full uri + $tickets->FromSQL(" RefersTo = '@{[ $article->URI ]}' "); + is $tickets->Count, 0, "Still no results"; + + # add the link + $ticket->AddLink( Type => 'RefersTo', Target => "a:$aid" ); + + # verify the ticket has it + my @links = @{$ticket->RefersTo->ItemsArrayRef}; + is scalar @links, 1, "Has one RefersTo link"; + is ref $links[0]->TargetObj, "RT::Article", "Link points to an article"; + is $links[0]->TargetObj->id, $aid, "Link points to the article we specified"; + + # search again + $tickets->FromSQL(" RefersTo = 'a:$aid' "); + is $tickets->Count, 1, "Found one ticket with short URI"; + + # search with the full uri + $tickets->FromSQL(" RefersTo = '@{[ $article->URI ]}' "); + is $tickets->Count, 1, "Found one ticket with full URI"; +} diff --git a/rt/t/data/configs/apache2.2+fastcgi.conf.in b/rt/t/data/configs/apache2.2+fastcgi.conf.in index 3ec36dd0f..03eaa9a70 100644 --- a/rt/t/data/configs/apache2.2+fastcgi.conf.in +++ b/rt/t/data/configs/apache2.2+fastcgi.conf.in @@ -12,6 +12,7 @@ Group @WEB_GROUP@ </IfModule> </IfModule> +ServerName localhost Listen %%LISTEN%% ErrorLog "%%LOG_FILE%%" diff --git a/rt/t/data/configs/apache2.2+mod_perl.conf.in b/rt/t/data/configs/apache2.2+mod_perl.conf.in index 3b1f3f618..20d2f44e5 100644 --- a/rt/t/data/configs/apache2.2+mod_perl.conf.in +++ b/rt/t/data/configs/apache2.2+mod_perl.conf.in @@ -30,6 +30,7 @@ Group @WEB_GROUP@ </IfModule> </IfModule> +ServerName localhost Listen %%LISTEN%% ErrorLog "%%LOG_FILE%%" diff --git a/rt/t/mail/dashboard-chart-with-utf8.t b/rt/t/mail/dashboard-chart-with-utf8.t index 6d07b963b..79f5f0e11 100644 --- a/rt/t/mail/dashboard-chart-with-utf8.t +++ b/rt/t/mail/dashboard-chart-with-utf8.t @@ -1,7 +1,17 @@ use strict; use warnings; -use RT::Test tests => 15; +BEGIN { + require RT::Test; + + if (eval { require GD }) { + RT::Test->import(tests => 15); + } + else { + RT::Test->import(skip_all => 'GD required.'); + } +} + use utf8; my $root = RT::Test->load_or_create_user( Name => 'root' ); diff --git a/rt/t/mail/dashboards.t b/rt/t/mail/dashboards.t index 7a7a54ce6..00cfc6acd 100644 --- a/rt/t/mail/dashboards.t +++ b/rt/t/mail/dashboards.t @@ -2,7 +2,7 @@ use strict; use warnings; -use RT::Test tests => 187; +use RT::Test tests => 181; use Test::Warn; use RT::Dashboard::Mailer; @@ -138,17 +138,6 @@ sub delete_dashboard { # {{{ ok($ok, $msg); } # }}} -sub delete_subscriptions { # {{{ - my $subscription_id = shift; - # delete the dashboard and make sure we get exactly one subscription failure - # notice - my $user = RT::User->new(RT->SystemUser); - $user->Load('root'); - for my $subscription ($user->Attributes->Named('Subscription')) { - $subscription->Delete; - } -} # }}} - my $good_time = 1290423660; # 6:01 EST on a monday my $bad_time = 1290427260; # 7:01 EST on a monday @@ -223,21 +212,9 @@ SKIP: { delete_dashboard($dashboard_id); -warning_like { - RT::Dashboard::Mailer->MailDashboards(All => 1); -} qr/Unable to load dashboard $dashboard_id of subscription $subscription_id for user root/; - -@mails = RT::Test->fetch_caught_mails; -is(@mails, 1, "one mail for subscription failure"); -$mail = parse_mail($mails[0]); -is($mail->head->get('Subject'), "[example.com] Missing dashboard!\n"); -is($mail->head->get('From'), "dashboard\@example.com\n"); -is($mail->head->get('X-RT-Dashboard-Id'), "$dashboard_id\n"); -is($mail->head->get('X-RT-Dashboard-Subscription-Id'), "$subscription_id\n"); - RT::Dashboard::Mailer->MailDashboards(All => 1); @mails = RT::Test->fetch_caught_mails; -is(@mails, 0, "no mail because the subscription notice happens only once"); +is(@mails, 0, "no mail because the subscription is deleted"); RT::Test->stop_server; RT::Test->clean_caught_mails; @@ -277,7 +254,6 @@ RT->Config->Set('EmailDashboardRemove' => (qr/My dashboards/, "Testing!")); ($baseurl, $m) = RT::Test->started_ok; delete_dashboard($dashboard_id); -delete_subscriptions(); RT::Test->clean_caught_mails; @@ -330,7 +306,6 @@ RT->Config->Set('EmailDashboardRemove' => (qr/My dashboards/, "Testing!")); ($baseurl, $m) = RT::Test->started_ok; delete_dashboard($dashboard_id); -delete_subscriptions(); RT::Test->clean_caught_mails; @@ -373,7 +348,6 @@ RT->Config->Set('EmailDashboardRemove' => (qr/My dashboards/, "Testing!")); ($baseurl, $m) = RT::Test->started_ok; delete_dashboard($dashboard_id); -delete_subscriptions(); RT::Test->clean_caught_mails; diff --git a/rt/t/mail/gateway.t b/rt/t/mail/gateway.t index 9f0e669a3..98eabd56e 100644 --- a/rt/t/mail/gateway.t +++ b/rt/t/mail/gateway.t @@ -57,7 +57,7 @@ use strict; use warnings; -use RT::Test config => 'Set( $UnsafeEmailCommands, 1);', tests => 221, actual_server => 1; +use RT::Test config => 'Set( $UnsafeEmailCommands, 1);', tests => 228, actual_server => 1; my ($baseurl, $m) = RT::Test->started_ok; use RT::Tickets; @@ -608,6 +608,35 @@ EOF $m->no_warnings_ok; } +diag "make sure we check that UTF-8 is really UTF-8"; +{ + my $text = <<EOF; +From: root\@localhost +To: rtemail\@@{[RT->Config->Get('rtname')]} +Subject: This is test wrong utf-8 chars +Content-Type: text/plain; charset="utf-8" + +utf-8: informaci\303\263n confidencial +latin1: informaci\363n confidencial + +bye +EOF + my ($status, $id) = RT::Test->send_via_mailgate_and_http($text); + is ($status >> 8, 0, "The mail gateway exited normally"); + ok ($id, "created ticket"); + + my $tick = RT::Test->last_ticket; + is ($tick->Id, $id, "correct ticket"); + + my $content = $tick->Transactions->First->Content; + Encode::_utf8_off($content); + + like $content, qr{informaci\303\263n confidencial}; + like $content, qr{informaci\357\277\275n confidencial}; + + $m->no_warnings_ok; +} + diag "check that mailgate doesn't suffer from empty Reply-To:"; { my $text = <<EOF; diff --git a/rt/t/shredder/01ticket.t b/rt/t/shredder/01ticket.t index 7dff16df3..a7abeef6e 100644 --- a/rt/t/shredder/01ticket.t +++ b/rt/t/shredder/01ticket.t @@ -78,7 +78,11 @@ cmp_deeply( dump_current_and_savepoint('clean'), "current DB equal to savepoint" my $shredder = shredder_new(); $shredder->PutObjects( Objects => $child ); $shredder->WipeoutAll; - cmp_deeply( dump_current_and_savepoint('parent_ticket'), "current DB equal to savepoint"); + + TODO: { + local $TODO = "Shredder doesn't delete all links and transactions"; + cmp_deeply( dump_current_and_savepoint('parent_ticket'), "current DB equal to savepoint"); + } $shredder->PutObjects( Objects => $parent ); $shredder->WipeoutAll; diff --git a/rt/t/shredder/03plugin_tickets.t b/rt/t/shredder/03plugin_tickets.t index 092b57052..e63eef8fd 100644 --- a/rt/t/shredder/03plugin_tickets.t +++ b/rt/t/shredder/03plugin_tickets.t @@ -34,6 +34,7 @@ use_ok('RT::Tickets'); my $child = RT::Ticket->new( RT->SystemUser ); my ($cid) = $child->Create( Subject => 'child', Queue => 1, MemberOf => $pid ); ok( $cid, "created new ticket" ); + $_->ApplyTransactionBatch for $parent, $child; my $plugin = RT::Shredder::Plugin::Tickets->new; isa_ok($plugin, 'RT::Shredder::Plugin::Tickets'); @@ -77,6 +78,8 @@ cmp_deeply( dump_current_and_savepoint('clean'), "current DB equal to savepoint" my ($status, $msg) = $child->AddLink( Target => $pid, Type => 'DependsOn' ); ok($status, "added reqursive link") or diag "error: $msg"; + $_->ApplyTransactionBatch for $parent, $child; + my $plugin = RT::Shredder::Plugin::Tickets->new; isa_ok($plugin, 'RT::Shredder::Plugin::Tickets'); @@ -121,6 +124,8 @@ cmp_deeply( dump_current_and_savepoint('clean'), "current DB equal to savepoint" ok( $cid2, "created new ticket" ); $child2->SetStatus('resolved'); + $_->ApplyTransactionBatch for $parent, $child1, $child2; + my $plugin = RT::Shredder::Plugin::Tickets->new; isa_ok($plugin, 'RT::Shredder::Plugin::Tickets'); diff --git a/rt/t/shredder/03plugin_users.t b/rt/t/shredder/03plugin_users.t index 4f4ecc89c..1f4cb4934 100644 --- a/rt/t/shredder/03plugin_users.t +++ b/rt/t/shredder/03plugin_users.t @@ -5,8 +5,8 @@ use warnings; use Test::Deep; use File::Spec; -use Test::More tests => 9; -use RT::Test nodb => 1; +use Test::More tests => 21; +use RT::Test (); BEGIN { my $shredder_utils = RT::Test::get_relocatable_file('utils.pl', File::Spec->curdir()); @@ -38,3 +38,61 @@ use_ok('RT::Shredder::Plugin::Users'); ok(!$status, "bad 'status' arg value"); } +init_db(); + +RT::Test->set_rights( + { Principal => 'Everyone', Right => [qw(CreateTicket)] }, +); + +create_savepoint('clean'); + +{ # Create two users and a ticket. Shred second user and replace relations with first user + my ($uidA, $uidB, $msg); + my $userA = RT::User->new( RT->SystemUser ); + ($uidA, $msg) = $userA->Create( Name => 'userA', Privileged => 1, Disabled => 0 ); + ok( $uidA, "created user A" ) or diag "error: $msg"; + + my $userB = RT::User->new( RT->SystemUser ); + ($uidB, $msg) = $userB->Create( Name => 'userB', Privileged => 1, Disabled => 0 ); + ok( $uidB, "created user B" ) or diag "error: $msg"; + + my ($tid, $trid); + my $ticket = RT::Ticket->new( RT::CurrentUser->new($userB) ); + ($tid, $trid, $msg) = $ticket->Create( Subject => 'UserB Ticket', Queue => 1 ); + ok( $tid, "created new ticket") or diag "error: $msg"; + + my $transaction = RT::Transaction->new( RT->SystemUser ); + $transaction->Load($trid); + is ( $transaction->Creator, $uidB, "ticket creator is user B" ); + + my $plugin = RT::Shredder::Plugin::Users->new; + isa_ok($plugin, 'RT::Shredder::Plugin::Users'); + + my $status; + ($status, $msg) = $plugin->TestArgs( status => 'any', name => 'userB', replace_relations => $uidA ); + ok($status, "plugin arguments are ok") or diag "error: $msg"; + + my @objs; + ($status, @objs) = $plugin->Run; + ok($status, "executed plugin successfully") or diag "error: @objs"; + @objs = RT::Shredder->CastObjectsToRecords( Objects => \@objs ); + is(scalar @objs, 1, "one object in the result set"); + + my $shredder = shredder_new(); + + ($status, $msg) = $plugin->SetResolvers( Shredder => $shredder ); + ok($status, "set conflicts resolver") or diag "error: $msg"; + + $shredder->PutObjects( Objects => \@objs ); + $shredder->WipeoutAll; + + $ticket->Load( $tid ); + is($ticket->id, $tid, 'loaded ticket'); + + $transaction->Load($trid); + is ( $transaction->Creator, $uidA, "ticket creator is now user A" ); + + $shredder->Wipeout( Object => $ticket ); + $shredder->Wipeout( Object => $userA ); +} +cmp_deeply( dump_current_and_savepoint('clean'), "current DB equal to savepoint"); diff --git a/rt/t/shredder/utils.pl b/rt/t/shredder/utils.pl index 5f5c1822f..9b848c662 100644 --- a/rt/t/shredder/utils.pl +++ b/rt/t/shredder/utils.pl @@ -283,7 +283,7 @@ sub dump_sqlite my $old_fhkn = $dbh->{'FetchHashKeyName'}; $dbh->{'FetchHashKeyName'} = 'NAME_lc'; - my $sth = $dbh->table_info( '', '', '%', 'TABLE' ) || die $DBI::err; + my $sth = $dbh->table_info( '', '%', '%', 'TABLE' ) || die $DBI::err; my @tables = keys %{$sth->fetchall_hashref( 'table_name' )}; my $res = {}; diff --git a/rt/t/ticket/search_by_watcher.t b/rt/t/ticket/search_by_watcher.t index 809450b56..cfc7b1c22 100644 --- a/rt/t/ticket/search_by_watcher.t +++ b/rt/t/ticket/search_by_watcher.t @@ -142,8 +142,8 @@ sub run_auto_tests { @conditions = ( 'Cc = "not@exist"' => sub { 0 }, 'Cc != "not@exist"' => sub { 1 }, - 'Cc IS NULL' => sub { $_[0] =~ 'c:-;' }, - 'Cc IS NOT NULL' => sub { $_[0] !~ 'c:-;' }, + 'Cc IS NULL' => sub { $_[0] =~ /c:-;/ }, + 'Cc IS NOT NULL' => sub { $_[0] !~ /c:-;/ }, 'Cc = "x@foo.com"' => sub { $_[0] =~ /c:[^;]*x/ }, 'Cc != "x@foo.com"' => sub { $_[0] !~ /c:[^;]*x/ }, 'Cc LIKE "@bar.com"' => sub { $_[0] =~ /c:[^;]*(?:y|z)/ }, @@ -152,8 +152,8 @@ sub run_auto_tests { 'Requestor = "not@exist"' => sub { 0 }, 'Requestor != "not@exist"' => sub { 1 }, - 'Requestor IS NULL' => sub { $_[0] =~ 'r:-;' }, - 'Requestor IS NOT NULL' => sub { $_[0] !~ 'r:-;' }, + 'Requestor IS NULL' => sub { $_[0] =~ /r:-;/ }, + 'Requestor IS NOT NULL' => sub { $_[0] !~ /r:-;/ }, 'Requestor = "x@foo.com"' => sub { $_[0] =~ /r:[^;]*x/ }, 'Requestor != "x@foo.com"' => sub { $_[0] !~ /r:[^;]*x/ }, 'Requestor LIKE "@bar.com"' => sub { $_[0] =~ /r:[^;]*(?:y|z)/ }, @@ -174,7 +174,7 @@ sub run_auto_tests { 'Subject LIKE "ne"' => sub { 0 }, 'Subject NOT LIKE "ne"' => sub { 1 }, 'Subject = "r:x;c:y;"' => sub { $_[0] eq 'r:x;c:y;' }, - 'Subject LIKE "x"' => sub { $_[0] =~ 'x' }, + 'Subject LIKE "x"' => sub { $_[0] =~ /x/ }, ); @tickets = generate_tix(); diff --git a/rt/t/web/attachments.t b/rt/t/web/attachments.t index 8c75f6caf..784cbbe88 100644 --- a/rt/t/web/attachments.t +++ b/rt/t/web/attachments.t @@ -1,10 +1,11 @@ #!/usr/bin/perl -w use strict; -use RT::Test tests => 25; +use RT::Test tests => 33; use constant LogoFile => $RT::MasonComponentRoot .'/NoAuth/images/bpslogo.png'; use constant FaviconFile => $RT::MasonComponentRoot .'/NoAuth/images/favicon.png'; +use constant TextFile => $RT::MasonComponentRoot .'/NoAuth/css/print.css'; my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; @@ -30,9 +31,18 @@ $m->content_contains('Attachments test', 'we have subject on the page'); $m->content_contains('Some content', 'and content'); $m->content_contains('Download bpslogo.png', 'page has file name'); +open LOGO, "<", LogoFile or die "Can't open logo file: $!"; +binmode LOGO; +my $logo_contents = do {local $/; <LOGO>}; +close LOGO; +$m->follow_link_ok({text => "Download bpslogo.png"}); +is($m->content_type, "image/png"); +is($m->content, $logo_contents, "Binary content matches"); + +$m->back; $m->follow_link_ok({text => 'Reply'}, "reply to the ticket"); $m->form_name('TicketUpdate'); -$m->field('Attach', LogoFile); +$m->field('Attach', TextFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); @@ -44,7 +54,16 @@ is($m->status, 200, "request successful"); $m->content_contains('Download bpslogo.png', 'page has file name'); $m->content_contains('Download favicon.png', 'page has file name'); +$m->content_contains('Download print.css', 'page has file name'); + +$m->follow_link_ok( { text => 'Download bpslogo.png' } ); +is( $m->response->header('Content-Type'), 'image/png', 'Content-Type of png lacks charset' ); + +$m->back; +$m->follow_link_ok( { text => 'Download print.css' } ); +is( $m->response->header('Content-Type'), + 'text/css;charset=UTF-8', 'Content-Type of text has charset' ); diag "test mobile ui"; $m->get_ok( $baseurl . '/m/ticket/create?Queue=' . $qid ); diff --git a/rt/t/web/command_line.t b/rt/t/web/command_line.t index 1fed8e69e..394daaba9 100644 --- a/rt/t/web/command_line.t +++ b/rt/t/web/command_line.t @@ -3,7 +3,7 @@ use strict; use File::Spec (); use Test::Expect; -use RT::Test tests => 303, actual_server => 1; +use RT::Test tests => 315, actual_server => 1; my ($baseurl, $m) = RT::Test->started_ok; use RT::User; @@ -480,6 +480,8 @@ expect_like(qr/Merged into ticket #$merge_ticket_A by root/, 'Merge recorded in expect_like(qr/Created link $link1_id $reln $link2_id/, 'Linked'); expect_send("show -s ticket/$link1_id/links", "Checking creation of $reln..."); expect_like(qr/$display_relns{$reln}: [\w\d\.\-]+:\/\/[\w\d\.]+\/ticket\/$link2_id/, "Created link $reln"); + expect_send("show ticket/$link1_id/links", "Checking show links without format"); + expect_like(qr/$display_relns{$reln}: [\w\d\.\-]+:\/\/[\w\d\.]+\/ticket\/$link2_id/, "Found link $reln"); # delete link expect_send("link -d $link1_id $reln $link2_id", "Delete $reln..."); diff --git a/rt/t/web/command_line_with_unknown_field.t b/rt/t/web/command_line_with_unknown_field.t index 736be4d1c..d63956be3 100644 --- a/rt/t/web/command_line_with_unknown_field.t +++ b/rt/t/web/command_line_with_unknown_field.t @@ -3,7 +3,7 @@ use strict; use File::Spec (); use Test::Expect; -use RT::Test tests => 14, actual_server => 1; +use RT::Test tests => 17, actual_server => 1; my ($baseurl, $m) = RT::Test->started_ok; my $rt_tool_path = "$RT::BinPath/rt"; @@ -19,6 +19,11 @@ expect_run( prompt => 'rt> ', quit => 'quit', ); + +expect_send( q{create -t ticket set foo=bar}, "create ticket with unknown field" ); +expect_like(qr/foo: Unknown field/, 'foo is unknown field'); +expect_like(qr/Could not create ticket/, 'ticket is not created'); + expect_send(q{create -t ticket set subject='new ticket' add cc=foo@example.com}, "Creating a ticket..."); expect_like(qr/Ticket \d+ created/, "Created the ticket"); diff --git a/rt/t/web/crypt-gnupg.t b/rt/t/web/crypt-gnupg.t index 6bdefdac7..8c0eb570d 100644 --- a/rt/t/web/crypt-gnupg.t +++ b/rt/t/web/crypt-gnupg.t @@ -53,6 +53,7 @@ RT::Test->clean_caught_mails; $m->goto_create_ticket( $queue ); $m->form_name('TicketCreate'); +$m->field('Requestors', 'recipient@example.com'); $m->field('Subject', 'Encryption test'); $m->field('Content', 'Some content'); ok($m->value('Encrypt', 2), "encrypt tick box is checked"); @@ -122,6 +123,7 @@ RT::Test->clean_caught_mails; $m->goto_create_ticket( $queue ); $m->form_name('TicketCreate'); +$m->field('Requestors', 'recipient@example.com'); $m->field('Subject', 'Signing test'); $m->field('Content', 'Some other content'); ok(!$m->value('Encrypt', 2), "encrypt tick box is unchecked"); @@ -195,6 +197,7 @@ RT::Test->clean_caught_mails; $m->goto_create_ticket( $queue ); $m->form_name('TicketCreate'); +$m->field('Requestors', 'recipient@example.com'); $m->field('Subject', 'Crypt+Sign test'); $m->field('Content', 'Some final? content'); ok($m->value('Encrypt', 2), "encrypt tick box is checked"); @@ -260,6 +263,7 @@ RT::Test->clean_caught_mails; $m->goto_create_ticket( $queue ); $m->form_name('TicketCreate'); +$m->field('Requestors', 'recipient@example.com'); $m->field('Subject', 'Test crypt-off on encrypted queue'); $m->field('Content', 'Thought you had me figured out didya'); $m->field(Encrypt => undef, 2); # turn off encryption diff --git a/rt/t/web/googleish_search.t b/rt/t/web/googleish_search.t index e2a4e9116..f4c8fa4b6 100644 --- a/rt/t/web/googleish_search.t +++ b/rt/t/web/googleish_search.t @@ -2,7 +2,8 @@ use strict; use warnings; -use RT::Test tests => 96, config => 'Set( %FullTextSearch, Enable => 1, Indexed => 0 );'; +use RT::Test tests => undef, + config => 'Set( %FullTextSearch, Enable => 1, Indexed => 0 );'; my ($baseurl, $m) = RT::Test->started_ok; my $url = $m->rt_base_url; @@ -57,6 +58,7 @@ ok $two_words_queue && $two_words_queue->id, 'loaded or created a queue'; is $parser->QueryToSQL("'me'"), "$active AND ( Subject LIKE 'me' )", "correct parsing"; is $parser->QueryToSQL("owner:me"), "( Owner.id = '__CurrentUser__' ) AND $active", "correct parsing"; is $parser->QueryToSQL("owner:'me'"), "( Owner = 'me' ) AND $active", "correct parsing"; + is $parser->QueryToSQL('owner:root@localhost'), "( Owner.EmailAddress = 'root\@localhost' ) AND $active", "Email address as owner"; is $parser->QueryToSQL("resolved me"), "( Owner.id = '__CurrentUser__' ) AND ( Status = 'resolved' )", "correct parsing"; is $parser->QueryToSQL("resolved active me"), "( Owner.id = '__CurrentUser__' ) AND ( Status = 'resolved' OR Status = 'new' OR Status = 'open' OR Status = 'stalled' )", "correct parsing"; @@ -217,3 +219,5 @@ for my $quote ( q{'}, q{"} ) { } } +undef $m; +done_testing; diff --git a/rt/t/web/query_builder_queue_limits.t b/rt/t/web/query_builder_queue_limits.t index a3b976524..f583d64cc 100644 --- a/rt/t/web/query_builder_queue_limits.t +++ b/rt/t/web/query_builder_queue_limits.t @@ -11,6 +11,9 @@ $lifecycles->{foo} = { }; +# explicitly Set so RT::Test can catch our change +RT->Config->Set( Lifecycles => %$lifecycles ); + RT::Lifecycle->FillCache(); my $general = RT::Test->load_or_create_queue( Name => 'General' ); diff --git a/rt/t/web/search_simple.t b/rt/t/web/search_simple.t index 1efc9a566..a1a3ce806 100644 --- a/rt/t/web/search_simple.t +++ b/rt/t/web/search_simple.t @@ -1,7 +1,7 @@ use strict; use warnings; -use RT::Test tests => 16; +use RT::Test tests => 30; my ( $baseurl, $m ) = RT::Test->started_ok; RT::Test->create_tickets( @@ -19,4 +19,58 @@ $m->content_contains( 'Show Results', "has page menu" ); $m->title_is( 'Found 1 ticket', 'title' ); $m->content_contains( 'ticket foo', 'has ticket foo' ); +# Test searches on custom fields +my $cf1 = RT::Test->load_or_create_custom_field( + Name => 'Location', + Queue => 'General', + Type => 'FreeformSingle', ); +isa_ok( $cf1, 'RT::CustomField' ); + +my $cf2 = RT::Test->load_or_create_custom_field( + Name => 'Server-name', + Queue => 'General', + Type => 'FreeformSingle', ); +isa_ok( $cf2, 'RT::CustomField' ); + +my $t = RT::Ticket->new(RT->SystemUser); + +{ + my ($id,undef,$msg) = $t->Create( + Queue => 'General', + Subject => 'Test searching CFs'); + ok( $id, "Created ticket - $msg" ); +} + +{ + my ($status, $msg) = $t->AddCustomFieldValue( + Field => $cf1->id, + Value => 'Downtown'); + ok( $status, "Added CF value - $msg" ); +} + +{ + my ($status, $msg) = $t->AddCustomFieldValue( + Field => $cf2->id, + Value => 'Proxy'); + ok( $status, "Added CF value - $msg" ); +} + +# Regular search +my $search = 'cf.Location:Downtown'; +$m->get_ok("/Search/Simple.html?q=$search"); +$m->title_is( 'Found 1 ticket', 'Found 1 ticket' ); +$m->text_contains( 'Test searching CFs', "Found test CF ticket with $search" ); + +# Case insensitive +$search = "cf.Location:downtown"; +$m->get_ok("/Search/Simple.html?q=$search"); +$m->title_is( 'Found 1 ticket', 'Found 1 ticket' ); +$m->text_contains( 'Test searching CFs', "Found test CF ticket with $search" ); + +# With dash in CF name +$search = "cf.Server-name:Proxy"; +$m->get_ok("/Search/Simple.html?q=$search"); +$m->title_is( 'Found 1 ticket', 'Found 1 ticket' ); +$m->text_contains( 'Test searching CFs', "Found test CF ticket with $search" ); + # TODO more simple search tests diff --git a/rt/t/web/ticket_modify_all.t b/rt/t/web/ticket_modify_all.t index c9dd7e7cd..2f0c4d1b3 100644 --- a/rt/t/web/ticket_modify_all.t +++ b/rt/t/web/ticket_modify_all.t @@ -1,7 +1,7 @@ use strict; use warnings; -use RT::Test tests => 15; +use RT::Test tests => 22; my $ticket = RT::Test->create_ticket( Subject => 'test bulk update', @@ -40,5 +40,44 @@ $m->click('SubmitTicket'); $m->form_name('TicketModifyAll'); is($m->value('Owner'), 'root', 'owner was successfully changed to root'); -# XXX TODO test other parts, i.e. basic, dates, people and links +$m->get_ok($url . "/Ticket/ModifyAll.html?id=" . $ticket->id); +$m->form_name('TicketModifyAll'); +$m->field('Starts_Date' => "2013-01-01 00:00:00"); +$m->click('SubmitTicket'); +$m->text_contains("Starts: (Tue Jan 01 00:00:00 2013)", 'start date successfully updated'); + +$m->form_name('TicketModifyAll'); +$m->field('Started_Date' => "2014-01-01 00:00:00"); +$m->click('SubmitTicket'); +$m->text_contains("Started: (Wed Jan 01 00:00:00 2014)", 'started date successfully updated'); + +$m->form_name('TicketModifyAll'); +$m->field('Told_Date' => "2015-01-01 00:00:00"); +$m->click('SubmitTicket'); +$m->text_contains("Last Contact: (Thu Jan 01 00:00:00 2015)", 'told date successfully updated'); + +$m->form_name('TicketModifyAll'); +$m->field('Due_Date' => "2016-01-01 00:00:00"); +$m->click('SubmitTicket'); +$m->text_contains("Due: (Fri Jan 01 00:00:00 2016)", 'due date successfully updated'); + +$m->get( $url . '/Ticket/ModifyAll.html?id=' . $ticket->id ); +$m->form_name('TicketModifyAll'); +$m->field(WatcherTypeEmail => 'Requestor'); +$m->field(WatcherAddressEmail => 'root@localhost'); +$m->click('SubmitTicket'); +$m->text_contains( + "Added principal as a Requestor for this ticket", + 'watcher is added', +); +$m->form_name('TicketModifyAll'); +$m->field(WatcherTypeEmail => 'Requestor'); +$m->field(WatcherAddressEmail => 'root@localhost'); +$m->click('SubmitTicket'); +$m->text_contains( + "That principal is already a Requestor for this ticket", + 'no duplicate watchers', +); + +# XXX TODO test other parts, i.e. links diff --git a/rt/t/web/transaction_batch.t b/rt/t/web/transaction_batch.t index ae04e1fca..12d01fba4 100644 --- a/rt/t/web/transaction_batch.t +++ b/rt/t/web/transaction_batch.t @@ -12,7 +12,14 @@ my ($val, $msg) =$s1->Create( Queue => $q->Id, ScripAction => 'User Defined', CustomIsApplicableCode => 'return ($self->TransactionObj->Field||"") eq "TimeEstimated"', CustomPrepareCode => 'return 1', - CustomCommitCode => '$self->TicketObj->SetPriority($self->TicketObj->Priority + 2); return 1;', + CustomCommitCode => ' +if ( $self->TicketObj->CurrentUser->Name ne "RT_System" ) { + warn "Ticket obj has incorrect CurrentUser (should be RT_System) ".$self->TicketObj->CurrentUser->Name +} +if ( $self->TicketObj->QueueObj->CurrentUser->Name ne "RT_System" ) { + warn "Queue obj has incorrect CurrentUser (should be RT_System) ".$self->TicketObj->QueueObj->CurrentUser->Name +} +$self->TicketObj->SetPriority($self->TicketObj->Priority + 2); return 1;', Template => 'Blank', Stage => 'TransactionBatch', ); |